From 4744b57766d1121af064ab7370a81f83aef90572 Mon Sep 17 00:00:00 2001 From: aymhce Date: Tue, 14 Jul 2015 17:17:38 +0200 Subject: [PATCH 1/3] ampache version 3.8.0 with add sql correction correct docker sed /etc/hosts issue compatibilty with nginx 1.6 --- conf/admin.sql | 106 + conf/ampache.cfg.php | 440 +- conf/ampache.cfg.php.old | 977 ++ conf/nginx.conf | 1 + scripts/install | 3 +- sources/.gitattributes | 63 + sources/.gitignore | 12 + sources/.maintenance.example | 22 + sources/.php_cs | 16 + sources/.scrutinizer.yml | 59 + sources/.tgitconfig | 6 + sources/.travis.yml | 17 + sources/.tx/config | 8 + sources/CNAME | 1 + sources/CONTRIBUTING.md | 6 + sources/README.md | 53 +- sources/admin/access.php | 2 +- sources/admin/catalog.php | 190 +- sources/admin/duplicates.php | 2 +- sources/admin/export.php | 2 +- sources/admin/index.php | 2 +- sources/admin/license.php | 65 + sources/admin/mail.php | 7 +- sources/admin/modules.php | 18 +- sources/admin/shout.php | 8 +- sources/admin/system.php | 16 +- sources/admin/users.php | 93 +- sources/albums.php | 155 +- sources/artists.php | 36 +- sources/arts.php | 169 + sources/batch.php | 143 +- sources/bin/.htaccess | 12 +- sources/bin/broadcast.inc | 45 + sources/bin/calculate_art_size.inc | 48 + sources/bin/catalog_update.inc | 149 +- sources/bin/channel_run.inc | 625 +- sources/bin/delete_disabled.inc | 2 +- sources/bin/dump_album_art.inc | 2 +- sources/bin/fix_filenames.inc | 10 +- sources/bin/install/.htaccess | 1 - sources/bin/install/add_user.inc | 2 +- sources/bin/install/install_db.inc | 2 +- sources/bin/install/update_db.inc | 2 +- sources/bin/migrate_config.inc | 2 +- sources/bin/print_tags.inc | 4 +- sources/bin/sort_files.inc | 15 +- sources/bin/websocket_run.inc | 2 +- sources/bin/write_playlists.inc | 4 +- sources/broadcast.php | 9 +- sources/browse.php | 72 +- sources/channel.php | 12 +- sources/channel/{.htaccess => .htaccess.dist} | 2 +- sources/channel/index.php | 2 +- sources/channel/style.css | 4 - sources/config/.gitignore | 1 - sources/config/.htaccess | 12 +- sources/config/ampache.cfg.php.dist | 394 +- sources/cookie_disclaimer.php | 29 + sources/daap/.htaccess | 6 + sources/daap/index.php | 67 + sources/democratic.php | 2 +- sources/docs/ACKNOWLEDGEMENTS | 10 +- sources/docs/CHANGELOG.md | 202 +- sources/docs/PLUGINS | 10 +- sources/graph.php | 69 + sources/image.php | 97 +- .../{themes/reborn => }/images/background.png | Bin sources/images/fileupload-border.png | Bin 0 -> 1353 bytes sources/images/fileupload-icons.png | Bin 0 -> 1393 bytes sources/images/icon_clean.png | Bin 0 -> 859 bytes sources/images/icon_file_refresh.png | Bin 0 -> 990 bytes sources/images/icon_play_add.png | Bin 635 -> 593 bytes sources/images/icon_play_next.png | Bin 0 -> 635 bytes sources/images/icon_replaygain.png | Bin 0 -> 144 bytes sources/images/icon_share_facebook.png | Bin 0 -> 573 bytes sources/images/icon_share_googleplus.png | Bin 0 -> 1047 bytes sources/images/icon_share_twitter.png | Bin 0 -> 1172 bytes sources/images/icon_sort.png | Bin 0 -> 577 bytes sources/index.php | 10 +- sources/install.php | 102 +- sources/labels.php | 111 + sources/lib/.htaccess | 12 +- sources/lib/batch.lib.php | 108 +- sources/lib/class/access.class.php | 99 +- sources/lib/class/ajax.class.php | 70 +- sources/lib/class/album.class.php | 613 +- sources/lib/class/ampache_rss.class.php | 177 +- sources/lib/class/ampconfig.class.php | 11 +- sources/lib/class/api.class.php | 217 +- sources/lib/class/art.class.php | 1276 ++- sources/lib/class/artist.class.php | 511 +- sources/lib/class/artist_event.class.php | 10 +- sources/lib/class/auth.class.php | 42 +- sources/lib/class/autoupdate.class.php | 82 +- sources/lib/class/broadcast.class.php | 241 +- sources/lib/class/broadcast_server.class.php | 149 +- sources/lib/class/browse.class.php | 214 +- sources/lib/class/catalog.class.php | 1086 ++- sources/lib/class/channel.class.php | 193 +- sources/lib/class/clip.class.php | 141 + sources/lib/class/core.class.php | 81 +- sources/lib/class/daap_api.class.php | 741 ++ .../lib/class/database_object.abstract.php | 2 +- sources/lib/class/dba.class.php | 11 +- sources/lib/class/democratic.class.php | 102 +- sources/lib/class/error.class.php | 15 +- sources/lib/class/graph.class.php | 511 + sources/lib/class/label.class.php | 489 + sources/lib/class/library_item.interface.php | 45 + sources/lib/class/license.class.php | 154 + ...{radio.class.php => live_stream.class.php} | 147 +- sources/lib/class/localplay.class.php | 4 +- .../class/localplay_controller.abstract.php | 2 +- sources/lib/class/mailer.class.php | 12 +- sources/lib/class/media.interface.php | 23 +- sources/lib/class/memory_object.class.php | 2 +- sources/lib/class/movie.class.php | 164 + sources/lib/class/openid.class.php | 4 +- sources/lib/class/personal_video.class.php | 119 + sources/lib/class/playable_item.interface.php | 81 + sources/lib/class/playlist.class.php | 159 +- .../lib/class/playlist_object.abstract.php | 120 +- sources/lib/class/plex_api.class.php | 755 +- sources/lib/class/plex_xml_data.class.php | 1113 ++- sources/lib/class/plugin.class.php | 23 +- sources/lib/class/preference.class.php | 117 +- sources/lib/class/privatemsg.class.php | 211 + sources/lib/class/query.class.php | 747 +- sources/lib/class/random.class.php | 12 +- sources/lib/class/rating.class.php | 19 +- sources/lib/class/recommendation.class.php | 35 +- sources/lib/class/registration.class.php | 27 +- sources/lib/class/scrobbler.class.php | 2 +- sources/lib/class/scrobbler_async.class.php | 4 +- sources/lib/class/search.class.php | 393 +- sources/lib/class/session.class.php | 180 +- sources/lib/class/share.class.php | 106 +- sources/lib/class/shoutbox.class.php | 131 +- sources/lib/class/slideshow.class.php | 2 +- sources/lib/class/song.class.php | 1199 ++- sources/lib/class/song_preview.class.php | 88 +- sources/lib/class/stats.class.php | 127 +- sources/lib/class/stream.class.php | 205 +- sources/lib/class/stream_playlist.class.php | 96 +- sources/lib/class/stream_url.class.php | 32 +- sources/lib/class/subsonic_api.class.php | 706 +- sources/lib/class/subsonic_xml_data.class.php | 261 +- sources/lib/class/tag.class.php | 336 +- sources/lib/class/tmp_playlist.class.php | 25 +- sources/lib/class/tvshow.class.php | 440 + sources/lib/class/tvshow_episode.class.php | 255 + sources/lib/class/tvshow_season.class.php | 349 + sources/lib/class/ui.class.php | 60 +- sources/lib/class/update.class.php | 1309 ++- sources/lib/class/upload.class.php | 221 + sources/lib/class/upnp_api.class.php | 1220 +++ sources/lib/class/user.class.php | 496 +- sources/lib/class/userflag.class.php | 58 +- sources/lib/class/vainfo.class.php | 671 +- sources/lib/class/video.class.php | 958 +- sources/lib/class/wanted.class.php | 282 +- sources/lib/class/waveform.class.php | 28 +- sources/lib/class/webdav_auth.class.php | 38 + sources/lib/class/webdav_catalog.class.php | 85 + sources/lib/class/webdav_directory.class.php | 102 + sources/lib/class/webdav_file.class.php | 74 + sources/lib/class/webplayer.class.php | 183 +- sources/lib/class/xml_data.class.php | 271 +- sources/lib/debug.lib.php | 78 +- sources/lib/general.lib.php | 97 +- sources/lib/i18n.php | 2 +- sources/lib/init-tiny.php | 22 +- sources/lib/init.php | 33 +- sources/lib/install.lib.php | 121 +- sources/lib/javascript/.htaccess | 10 +- sources/lib/javascript/ajax.js | 2 +- sources/lib/javascript/base.js | 76 +- sources/lib/javascript/dynamicpage.js | 127 + .../javascript}/jplayer.playlist.ext.js | 26 +- sources/lib/javascript/search-data.php | 4 +- sources/lib/javascript/search.js | 2 +- sources/lib/javascript/tabledata.js | 11 +- sources/lib/javascript/tools.js | 207 +- sources/lib/log.lib.php | 2 +- sources/lib/login.php | 16 +- sources/lib/preferences.php | 31 +- sources/lib/rating.lib.php | 2 +- sources/lib/themes.php | 2 +- sources/lib/ui.lib.php | 211 +- sources/locale/ar_SA/LC_MESSAGES/messages.mo | Bin 12300 -> 12144 bytes sources/locale/ar_SA/LC_MESSAGES/messages.po | 6790 +++++++++----- sources/locale/base/gather-messages.sh | 178 +- sources/locale/base/messages.pot | 5631 +++++++---- sources/locale/base/translation-words.txt | 257 +- sources/locale/ca_ES/LC_MESSAGES/messages.mo | Bin 34210 -> 32304 bytes sources/locale/ca_ES/LC_MESSAGES/messages.po | 6800 +++++++++----- sources/locale/cs_CZ/LC_MESSAGES/messages.mo | Bin 35138 -> 33218 bytes sources/locale/cs_CZ/LC_MESSAGES/messages.po | 6792 +++++++++----- sources/locale/de_DE/LC_MESSAGES/messages.mo | Bin 88510 -> 123885 bytes sources/locale/de_DE/LC_MESSAGES/messages.po | 6242 +++++++++---- sources/locale/el_GR/LC_MESSAGES/messages.mo | Bin 31314 -> 30028 bytes sources/locale/el_GR/LC_MESSAGES/messages.po | 6854 +++++++++----- sources/locale/en_GB/LC_MESSAGES/messages.mo | Bin 2654 -> 1899 bytes sources/locale/en_GB/LC_MESSAGES/messages.po | 5735 ++++++++---- sources/locale/es_ES/LC_MESSAGES/messages.mo | Bin 23040 -> 22181 bytes sources/locale/es_ES/LC_MESSAGES/messages.po | 6718 +++++++++----- sources/locale/fa_IR/LC_MESSAGES/messages.mo | Bin 25618 -> 24247 bytes sources/locale/fa_IR/LC_MESSAGES/messages.po | 6756 +++++++++----- sources/locale/fr_FR/LC_MESSAGES/messages.mo | Bin 62578 -> 122516 bytes sources/locale/fr_FR/LC_MESSAGES/messages.po | 7686 ++++++++------- sources/locale/it_IT/LC_MESSAGES/messages.mo | Bin 11041 -> 10619 bytes sources/locale/it_IT/LC_MESSAGES/messages.po | 6937 +++++++++----- sources/locale/ja_JP/LC_MESSAGES/messages.mo | Bin 48924 -> 45514 bytes sources/locale/ja_JP/LC_MESSAGES/messages.po | 6520 ++++++++----- sources/locale/nb_NO/LC_MESSAGES/messages.mo | Bin 31779 -> 30144 bytes sources/locale/nb_NO/LC_MESSAGES/messages.po | 6798 +++++++++----- sources/locale/nl_NL/LC_MESSAGES/messages.mo | Bin 33305 -> 31724 bytes sources/locale/nl_NL/LC_MESSAGES/messages.po | 7417 +++++++++------ sources/locale/pl_PL/LC_MESSAGES/messages.mo | Bin 35140 -> 32998 bytes sources/locale/pl_PL/LC_MESSAGES/messages.po | 6810 +++++++++----- sources/locale/pt_BR/messages.mo | Bin 0 -> 74568 bytes sources/locale/pt_BR/messages.po | 8216 +++++++++++++++++ sources/locale/ru_RU/LC_MESSAGES/messages.mo | Bin 44600 -> 42115 bytes sources/locale/ru_RU/LC_MESSAGES/messages.po | 6795 +++++++++----- sources/locale/sv_SE/LC_MESSAGES/messages.mo | Bin 34000 -> 32094 bytes sources/locale/sv_SE/LC_MESSAGES/messages.po | 6788 +++++++++----- sources/localplay.php | 3 +- sources/login.php | 17 +- sources/logout.php | 3 +- sources/lostpassword.php | 2 +- sources/modules/.htaccess | 21 +- .../modules/{php-openid => }/Auth/OpenID.php | 0 .../{php-openid => }/Auth/OpenID/AX.php | 0 .../Auth/OpenID/Association.php | 0 .../{php-openid => }/Auth/OpenID/BigMath.php | 0 .../{php-openid => }/Auth/OpenID/Consumer.php | 0 .../Auth/OpenID/CryptUtil.php | 0 .../Auth/OpenID/DatabaseConnection.php | 0 .../Auth/OpenID/DiffieHellman.php | 0 .../{php-openid => }/Auth/OpenID/Discover.php | 0 .../Auth/OpenID/DumbStore.php | 0 .../Auth/OpenID/Extension.php | 0 .../Auth/OpenID/FileStore.php | 2 +- .../{php-openid => }/Auth/OpenID/HMAC.php | 0 .../Auth/OpenID/Interface.php | 0 .../{php-openid => }/Auth/OpenID/KVForm.php | 0 .../Auth/OpenID/MDB2Store.php | 0 .../Auth/OpenID/MemcachedStore.php | 0 .../{php-openid => }/Auth/OpenID/Message.php | 0 .../Auth/OpenID/MySQLStore.php | 0 .../{php-openid => }/Auth/OpenID/Nonce.php | 0 .../{php-openid => }/Auth/OpenID/PAPE.php | 0 .../{php-openid => }/Auth/OpenID/Parse.php | 0 .../Auth/OpenID/PostgreSQLStore.php | 0 .../Auth/OpenID/PredisStore.php | 0 .../{php-openid => }/Auth/OpenID/SQLStore.php | 0 .../Auth/OpenID/SQLiteStore.php | 0 .../{php-openid => }/Auth/OpenID/SReg.php | 0 .../{php-openid => }/Auth/OpenID/Server.php | 0 .../Auth/OpenID/ServerRequest.php | 0 .../Auth/OpenID/TrustRoot.php | 0 .../{php-openid => }/Auth/OpenID/URINorm.php | 0 .../Auth/Yadis/HTTPFetcher.php | 0 .../{php-openid => }/Auth/Yadis/Manager.php | 0 .../{php-openid => }/Auth/Yadis/Misc.php | 0 .../Auth/Yadis/ParanoidHTTPFetcher.php | 0 .../{php-openid => }/Auth/Yadis/ParseHTML.php | 0 .../Auth/Yadis/PlainHTTPFetcher.php | 0 .../{php-openid => }/Auth/Yadis/XML.php | 0 .../{php-openid => }/Auth/Yadis/XRDS.php | 0 .../{php-openid => }/Auth/Yadis/XRI.php | 0 .../{php-openid => }/Auth/Yadis/XRIRes.php | 0 .../{php-openid => }/Auth/Yadis/Yadis.php | 0 sources/modules/Beets/Catalog.php | 335 + sources/modules/Beets/CliHandler.php | 200 + sources/modules/Beets/Handler.php | 95 + sources/modules/Beets/JsonHandler.php | 194 + sources/modules/Dropbox/AppInfo.php | 4 +- sources/modules/Dropbox/ArrayEntryStore.php | 5 +- sources/modules/Dropbox/AuthBase.php | 75 + sources/modules/Dropbox/AuthInfo.php | 4 +- sources/modules/Dropbox/Client.php | 258 +- sources/modules/Dropbox/Curl.php | 79 +- sources/modules/Dropbox/CurlStreamRelay.php | 2 - sources/modules/Dropbox/OAuth1AccessToken.php | 61 + sources/modules/Dropbox/OAuth1Upgrader.php | 104 + sources/modules/Dropbox/Path.php | 13 + sources/modules/Dropbox/RequestUtil.php | 75 +- sources/modules/Dropbox/SSLTester.php | 132 + sources/modules/Dropbox/Security.php | 12 +- sources/modules/Dropbox/WebAuth.php | 13 +- sources/modules/Dropbox/WebAuthBase.php | 107 +- .../Dropbox/WebAuthException/BadState.php | 2 - sources/modules/Dropbox/WebAuthNoRedirect.php | 10 +- .../modules/Dropbox/certs/trusted-certs.crt | 1396 +++ sources/modules/Dropbox/strict.php | 13 + sources/modules/Dropbox/trusted-certs.crt | 341 - sources/modules/Moinax/TvDb/Actor.php | 56 + sources/modules/Moinax/TvDb/Banner.php | 96 + sources/modules/Moinax/TvDb/Client.php | 429 + sources/modules/Moinax/TvDb/CurlException.php | 9 + sources/modules/Moinax/TvDb/Episode.php | 126 + sources/modules/Moinax/TvDb/Exception.php | 9 + sources/modules/Moinax/TvDb/Serie.php | 162 + sources/modules/Moinax/TvDb/XmlException.php | 9 + sources/modules/SabreDAV/autoload.php | 7 + .../modules/SabreDAV/composer/ClassLoader.php | 413 + .../SabreDAV/composer/autoload_classmap.php | 9 + .../SabreDAV/composer/autoload_namespaces.php | 9 + .../SabreDAV/composer/autoload_psr4.php | 16 + .../SabreDAV/composer/autoload_real.php | 50 + .../modules/SabreDAV/composer/installed.json | 248 + sources/modules/SabreDAV/sabre/dav/.gitignore | 39 + .../modules/SabreDAV/sabre/dav/.travis.yml | 34 + .../SabreDAV/sabre/dav/CONTRIBUTING.md | 84 + .../modules/SabreDAV/sabre/dav/bin/build.php | 168 + .../sabre/dav/bin/googlecode_upload.py | 248 + .../SabreDAV/sabre/dav/bin/migrateto17.php | 284 + .../SabreDAV/sabre/dav/bin/migrateto20.php | 449 + .../SabreDAV/sabre/dav/bin/migrateto21.php | 182 + .../SabreDAV/sabre/dav/bin/naturalselection | 140 + .../modules/SabreDAV/sabre/dav/bin/sabredav | 2 + .../SabreDAV/sabre/dav/bin/sabredav.php | 53 + .../modules/SabreDAV/sabre/dav/composer.json | 59 + .../lib/CalDAV/Backend/AbstractBackend.php | 221 + .../lib/CalDAV/Backend/BackendInterface.php | 268 + .../CalDAV/Backend/NotificationSupport.php | 47 + .../sabre/dav/lib/CalDAV/Backend/PDO.php | 1220 +++ .../lib/CalDAV/Backend/SchedulingSupport.php | 65 + .../dav/lib/CalDAV/Backend/SharingSupport.php | 243 + .../CalDAV/Backend/SubscriptionSupport.php | 89 + .../dav/lib/CalDAV/Backend/SyncSupport.php | 81 + .../sabre/dav/lib/CalDAV/Calendar.php | 527 ++ .../sabre/dav/lib/CalDAV/CalendarHome.php | 426 + .../sabre/dav/lib/CalDAV/CalendarObject.php | 291 + .../dav/lib/CalDAV/CalendarQueryParser.php | 299 + .../dav/lib/CalDAV/CalendarQueryValidator.php | 375 + .../sabre/dav/lib/CalDAV/CalendarRoot.php | 80 + .../sabre/dav/lib/CalDAV/CalendarRootNode.php | 18 + .../CalDAV/Exception/InvalidComponentType.php | 35 + .../sabre/dav/lib/CalDAV/ICSExportPlugin.php | 313 + .../sabre/dav/lib/CalDAV/ICalendar.php | 19 + .../sabre/dav/lib/CalDAV/ICalendarObject.php | 20 + .../lib/CalDAV/ICalendarObjectContainer.php | 39 + .../dav/lib/CalDAV/IShareableCalendar.php | 48 + .../sabre/dav/lib/CalDAV/ISharedCalendar.php | 36 + .../lib/CalDAV/Notifications/Collection.php | 173 + .../lib/CalDAV/Notifications/ICollection.php | 23 + .../dav/lib/CalDAV/Notifications/INode.php | 38 + .../Notifications/INotificationType.php | 44 + .../dav/lib/CalDAV/Notifications/Node.php | 192 + .../Notifications/Notification/Invite.php | 324 + .../Notification/InviteReply.php | 218 + .../Notification/SystemStatus.php | 182 + .../dav/lib/CalDAV/Notifications/Plugin.php | 162 + .../SabreDAV/sabre/dav/lib/CalDAV/Plugin.php | 1040 +++ .../dav/lib/CalDAV/Principal/Collection.php | 32 + .../dav/lib/CalDAV/Principal/IProxyRead.php | 19 + .../dav/lib/CalDAV/Principal/IProxyWrite.php | 19 + .../dav/lib/CalDAV/Principal/ProxyRead.php | 180 + .../dav/lib/CalDAV/Principal/ProxyWrite.php | 180 + .../sabre/dav/lib/CalDAV/Principal/User.php | 134 + .../CalDAV/Property/AllowedSharingModes.php | 74 + .../lib/CalDAV/Property/EmailAddressSet.php | 73 + .../sabre/dav/lib/CalDAV/Property/Invite.php | 228 + .../Property/ScheduleCalendarTransp.php | 103 + .../SupportedCalendarComponentSet.php | 89 + .../CalDAV/Property/SupportedCalendarData.php | 44 + .../CalDAV/Property/SupportedCollationSet.php | 45 + .../sabre/dav/lib/CalDAV/Schedule/IInbox.php | 15 + .../dav/lib/CalDAV/Schedule/IMipPlugin.php | 177 + .../sabre/dav/lib/CalDAV/Schedule/IOutbox.php | 15 + .../lib/CalDAV/Schedule/ISchedulingObject.php | 13 + .../sabre/dav/lib/CalDAV/Schedule/Inbox.php | 268 + .../sabre/dav/lib/CalDAV/Schedule/Outbox.php | 183 + .../sabre/dav/lib/CalDAV/Schedule/Plugin.php | 880 ++ .../lib/CalDAV/Schedule/SchedulingObject.php | 165 + .../dav/lib/CalDAV/ShareableCalendar.php | 72 + .../sabre/dav/lib/CalDAV/SharedCalendar.php | 148 + .../sabre/dav/lib/CalDAV/SharingPlugin.php | 502 + .../CalDAV/Subscriptions/ISubscription.php | 40 + .../dav/lib/CalDAV/Subscriptions/Plugin.php | 85 + .../lib/CalDAV/Subscriptions/Subscription.php | 277 + .../sabre/dav/lib/CalDAV/UserCalendars.php | 19 + .../sabre/dav/lib/CardDAV/AddressBook.php | 432 + .../lib/CardDAV/AddressBookQueryParser.php | 221 + .../sabre/dav/lib/CardDAV/AddressBookRoot.php | 80 + .../lib/CardDAV/Backend/AbstractBackend.php | 38 + .../lib/CardDAV/Backend/BackendInterface.php | 187 + .../sabre/dav/lib/CardDAV/Backend/PDO.php | 557 ++ .../dav/lib/CardDAV/Backend/SyncSupport.php | 81 + .../SabreDAV/sabre/dav/lib/CardDAV/Card.php | 265 + .../sabre/dav/lib/CardDAV/IAddressBook.php | 18 + .../SabreDAV/sabre/dav/lib/CardDAV/ICard.php | 20 + .../sabre/dav/lib/CardDAV/IDirectory.php | 20 + .../SabreDAV/sabre/dav/lib/CardDAV/Plugin.php | 913 ++ .../CardDAV/Property/SupportedAddressData.php | 73 + .../Property/SupportedCollationSet.php | 45 + .../dav/lib/CardDAV/UserAddressBooks.php | 261 + .../sabre/dav/lib/CardDAV/VCFExportPlugin.php | 111 + .../lib/DAV/Auth/Backend/AbstractBasic.php | 84 + .../lib/DAV/Auth/Backend/AbstractDigest.php | 96 + .../sabre/dav/lib/DAV/Auth/Backend/Apache.php | 66 + .../lib/DAV/Auth/Backend/BackendInterface.php | 36 + .../lib/DAV/Auth/Backend/BasicCallBack.php | 62 + .../sabre/dav/lib/DAV/Auth/Backend/File.php | 77 + .../sabre/dav/lib/DAV/Auth/Backend/PDO.php | 61 + .../sabre/dav/lib/DAV/Auth/Plugin.php | 122 + .../dav/lib/DAV/Browser/GuessContentType.php | 100 + .../dav/lib/DAV/Browser/MapGetToPropFind.php | 61 + .../sabre/dav/lib/DAV/Browser/Plugin.php | 675 ++ .../sabre/dav/lib/DAV/Browser/PropFindAll.php | 132 + .../dav/lib/DAV/Browser/assets/favicon.ico | Bin 0 -> 4286 bytes .../Browser/assets/openiconic/ICON-LICENSE | 21 + .../Browser/assets/openiconic/open-iconic.css | 510 + .../Browser/assets/openiconic/open-iconic.eot | Bin 0 -> 23144 bytes .../Browser/assets/openiconic/open-iconic.otf | Bin 0 -> 21048 bytes .../Browser/assets/openiconic/open-iconic.svg | 543 ++ .../Browser/assets/openiconic/open-iconic.ttf | Bin 0 -> 25568 bytes .../assets/openiconic/open-iconic.woff | Bin 0 -> 12404 bytes .../dav/lib/DAV/Browser/assets/sabredav.css | 222 + .../dav/lib/DAV/Browser/assets/sabredav.png | Bin 0 -> 2825 bytes .../SabreDAV/sabre/dav/lib/DAV/Client.php | 474 + .../SabreDAV/sabre/dav/lib/DAV/Collection.php | 110 + .../SabreDAV/sabre/dav/lib/DAV/CorePlugin.php | 894 ++ .../SabreDAV/sabre/dav/lib/DAV/Exception.php | 64 + .../dav/lib/DAV/Exception/BadRequest.php | 28 + .../sabre/dav/lib/DAV/Exception/Conflict.php | 28 + .../dav/lib/DAV/Exception/ConflictingLock.php | 36 + .../dav/lib/DAV/Exception/FileNotFound.php | 19 + .../sabre/dav/lib/DAV/Exception/Forbidden.php | 27 + .../lib/DAV/Exception/InsufficientStorage.php | 27 + .../lib/DAV/Exception/InvalidResourceType.php | 33 + .../lib/DAV/Exception/InvalidSyncToken.php | 38 + .../dav/lib/DAV/Exception/LengthRequired.php | 30 + .../Exception/LockTokenMatchesRequestUri.php | 41 + .../sabre/dav/lib/DAV/Exception/Locked.php | 73 + .../lib/DAV/Exception/MethodNotAllowed.php | 45 + .../lib/DAV/Exception/NotAuthenticated.php | 30 + .../sabre/dav/lib/DAV/Exception/NotFound.php | 28 + .../dav/lib/DAV/Exception/NotImplemented.php | 27 + .../dav/lib/DAV/Exception/PaymentRequired.php | 30 + .../lib/DAV/Exception/PreconditionFailed.php | 71 + .../lib/DAV/Exception/ReportNotSupported.php | 32 + .../RequestedRangeNotSatisfiable.php | 31 + .../lib/DAV/Exception/ServiceUnavailable.php | 30 + .../dav/lib/DAV/Exception/TooManyMatches.php | 38 + .../DAV/Exception/UnsupportedMediaType.php | 28 + .../sabre/dav/lib/DAV/FS/Directory.php | 140 + .../SabreDAV/sabre/dav/lib/DAV/FS/File.php | 91 + .../SabreDAV/sabre/dav/lib/DAV/FS/Node.php | 84 + .../sabre/dav/lib/DAV/FSExt/Directory.php | 196 + .../SabreDAV/sabre/dav/lib/DAV/FSExt/File.php | 143 + .../SabreDAV/sabre/dav/lib/DAV/FSExt/Node.php | 226 + .../SabreDAV/sabre/dav/lib/DAV/File.php | 85 + .../sabre/dav/lib/DAV/ICollection.php | 77 + .../sabre/dav/lib/DAV/IExtendedCollection.php | 28 + .../SabreDAV/sabre/dav/lib/DAV/IFile.php | 82 + .../sabre/dav/lib/DAV/IMoveTarget.php | 44 + .../SabreDAV/sabre/dav/lib/DAV/IMultiGet.php | 35 + .../SabreDAV/sabre/dav/lib/DAV/INode.php | 46 + .../sabre/dav/lib/DAV/IProperties.php | 48 + .../SabreDAV/sabre/dav/lib/DAV/IQuota.php | 27 + .../lib/DAV/Locks/Backend/AbstractBackend.php | 21 + .../DAV/Locks/Backend/BackendInterface.php | 51 + .../sabre/dav/lib/DAV/Locks/Backend/FS.php | 194 + .../sabre/dav/lib/DAV/Locks/Backend/File.php | 185 + .../sabre/dav/lib/DAV/Locks/Backend/PDO.php | 185 + .../sabre/dav/lib/DAV/Locks/LockInfo.php | 81 + .../sabre/dav/lib/DAV/Locks/Plugin.php | 595 ++ .../sabre/dav/lib/DAV/Mount/Plugin.php | 86 + .../SabreDAV/sabre/dav/lib/DAV/Node.php | 55 + .../sabre/dav/lib/DAV/PartialUpdate/IFile.php | 39 + .../lib/DAV/PartialUpdate/IPatchSupport.php | 48 + .../dav/lib/DAV/PartialUpdate/Plugin.php | 232 + .../SabreDAV/sabre/dav/lib/DAV/PropFind.php | 336 + .../SabreDAV/sabre/dav/lib/DAV/PropPatch.php | 344 + .../SabreDAV/sabre/dav/lib/DAV/Property.php | 32 + .../dav/lib/DAV/Property/GetLastModified.php | 77 + .../sabre/dav/lib/DAV/Property/Href.php | 100 + .../sabre/dav/lib/DAV/Property/HrefList.php | 106 + .../sabre/dav/lib/DAV/Property/IHref.php | 25 + .../dav/lib/DAV/Property/LockDiscovery.php | 94 + .../dav/lib/DAV/Property/ResourceType.php | 124 + .../sabre/dav/lib/DAV/Property/Response.php | 222 + .../dav/lib/DAV/Property/ResponseList.php | 134 + .../dav/lib/DAV/Property/SupportedLock.php | 78 + .../lib/DAV/Property/SupportedMethodSet.php | 68 + .../lib/DAV/Property/SupportedReportSet.php | 126 + .../sabre/dav/lib/DAV/PropertyInterface.php | 39 + .../Backend/BackendInterface.php | 77 + .../lib/DAV/PropertyStorage/Backend/PDO.php | 172 + .../dav/lib/DAV/PropertyStorage/Plugin.php | 145 + .../SabreDAV/sabre/dav/lib/DAV/Server.php | 1741 ++++ .../sabre/dav/lib/DAV/ServerPlugin.php | 90 + .../sabre/dav/lib/DAV/SimpleCollection.php | 107 + .../SabreDAV/sabre/dav/lib/DAV/SimpleFile.php | 121 + .../SabreDAV/sabre/dav/lib/DAV/StringUtil.php | 91 + .../dav/lib/DAV/Sync/ISyncCollection.php | 89 + .../sabre/dav/lib/DAV/Sync/Plugin.php | 337 + .../dav/lib/DAV/TemporaryFileFilterPlugin.php | 296 + .../SabreDAV/sabre/dav/lib/DAV/Tree.php | 339 + .../SabreDAV/sabre/dav/lib/DAV/URLUtil.php | 22 + .../SabreDAV/sabre/dav/lib/DAV/UUIDUtil.php | 64 + .../SabreDAV/sabre/dav/lib/DAV/Version.php | 19 + .../SabreDAV/sabre/dav/lib/DAV/XMLUtil.php | 194 + .../DAVACL/AbstractPrincipalCollection.php | 181 + .../dav/lib/DAVACL/Exception/AceConflict.php | 35 + .../lib/DAVACL/Exception/NeedPrivileges.php | 83 + .../dav/lib/DAVACL/Exception/NoAbstract.php | 35 + .../Exception/NotRecognizedPrincipal.php | 35 + .../Exception/NotSupportedPrivilege.php | 35 + .../SabreDAV/sabre/dav/lib/DAVACL/IACL.php | 74 + .../sabre/dav/lib/DAVACL/IPrincipal.php | 77 + .../dav/lib/DAVACL/IPrincipalCollection.php | 62 + .../SabreDAV/sabre/dav/lib/DAVACL/Plugin.php | 1424 +++ .../sabre/dav/lib/DAVACL/Principal.php | 291 + .../PrincipalBackend/AbstractBackend.php | 53 + .../PrincipalBackend/BackendInterface.php | 141 + .../dav/lib/DAVACL/PrincipalBackend/PDO.php | 386 + .../dav/lib/DAVACL/PrincipalCollection.php | 33 + .../sabre/dav/lib/DAVACL/Property/Acl.php | 212 + .../lib/DAVACL/Property/AclRestrictions.php | 34 + .../Property/CurrentUserPrivilegeSet.php | 136 + .../dav/lib/DAVACL/Property/Principal.php | 162 + .../DAVACL/Property/SupportedPrivilegeSet.php | 105 + .../Sabre/CalDAV/Backend/AbstractPDOTest.php | 875 ++ .../Sabre/CalDAV/Backend/AbstractTest.php | 178 + .../dav/tests/Sabre/CalDAV/Backend/Mock.php | 214 + .../Sabre/CalDAV/Backend/MockScheduling.php | 93 + .../Sabre/CalDAV/Backend/MockSharing.php | 170 + .../Backend/MockSubscriptionSupport.php | 156 + .../Sabre/CalDAV/Backend/PDOMySQLTest.php | 39 + .../Sabre/CalDAV/Backend/PDOSqliteTest.php | 37 + .../CalDAV/CalendarHomeNotificationsTest.php | 53 + .../CalendarHomeSharedCalendarsTest.php | 89 + .../CalDAV/CalendarHomeSubscriptionsTest.php | 85 + .../tests/Sabre/CalDAV/CalendarHomeTest.php | 206 + .../tests/Sabre/CalDAV/CalendarObjectTest.php | 395 + .../Sabre/CalDAV/CalendarQueryParserTest.php | 540 ++ .../Sabre/CalDAV/CalendarQueryVAlarmTest.php | 122 + .../CalDAV/CalendarQueryValidatorTest.php | 823 ++ .../dav/tests/Sabre/CalDAV/CalendarTest.php | 289 + .../ExpandEventsDTSTARTandDTENDTest.php | 110 + .../ExpandEventsDTSTARTandDTENDbyDayTest.php | 103 + .../CalDAV/ExpandEventsDoubleEventsTest.php | 104 + .../CalDAV/ExpandEventsFloatingTimeTest.php | 210 + .../tests/Sabre/CalDAV/FreeBusyReportTest.php | 176 + .../Sabre/CalDAV/GetEventsByTimerangeTest.php | 96 + .../Sabre/CalDAV/ICSExportPluginTest.php | 535 ++ .../dav/tests/Sabre/CalDAV/Issue166Test.php | 63 + .../dav/tests/Sabre/CalDAV/Issue172Test.php | 135 + .../dav/tests/Sabre/CalDAV/Issue203Test.php | 138 + .../dav/tests/Sabre/CalDAV/Issue205Test.php | 97 + .../dav/tests/Sabre/CalDAV/Issue211Test.php | 89 + .../dav/tests/Sabre/CalDAV/Issue220Test.php | 99 + .../dav/tests/Sabre/CalDAV/Issue228Test.php | 78 + .../tests/Sabre/CalDAV/JCalTransformTest.php | 253 + .../CalDAV/Notifications/CollectionTest.php | 90 + .../Sabre/CalDAV/Notifications/NodeTest.php | 101 + .../Notification/InviteReplyTest.php | 134 + .../Notifications/Notification/InviteTest.php | 230 + .../Notification/SystemStatusTest.php | 61 + .../Sabre/CalDAV/Notifications/PluginTest.php | 165 + .../dav/tests/Sabre/CalDAV/PluginTest.php | 1057 +++ .../Sabre/CalDAV/Principal/CollectionTest.php | 19 + .../Sabre/CalDAV/Principal/ProxyReadTest.php | 101 + .../Sabre/CalDAV/Principal/ProxyWriteTest.php | 39 + .../tests/Sabre/CalDAV/Principal/UserTest.php | 126 + .../Property/AllowedSharingModesTest.php | 47 + .../CalDAV/Property/EmailAddressSetTest.php | 43 + .../Sabre/CalDAV/Property/InviteTest.php | 197 + .../Property/ScheduleCalendarTranspTest.php | 99 + .../SupportedCalendarComponentSetTest.php | 67 + .../Property/SupportedCalendarDataTest.php | 46 + .../Property/SupportedCollationSetTest.php | 47 + .../CalDAV/Schedule/DeliverNewEventTest.php | 93 + .../CalDAV/Schedule/FreeBusyRequestTest.php | 424 + .../Sabre/CalDAV/Schedule/IMip/MockPlugin.php | 50 + .../Sabre/CalDAV/Schedule/IMipPluginTest.php | 211 + .../tests/Sabre/CalDAV/Schedule/InboxTest.php | 186 + .../Sabre/CalDAV/Schedule/OutboxPostTest.php | 136 + .../Sabre/CalDAV/Schedule/OutboxTest.php | 89 + .../Sabre/CalDAV/Schedule/PluginBasicTest.php | 25 + .../CalDAV/Schedule/PluginPropertiesTest.php | 65 + ...PluginPropertiesWithSharedCalendarTest.php | 75 + .../CalDAV/Schedule/ScheduleDeliverTest.php | 664 ++ .../CalDAV/Schedule/SchedulingObjectTest.php | 398 + .../Sabre/CalDAV/ShareableCalendarTest.php | 60 + .../tests/Sabre/CalDAV/SharedCalendarTest.php | 203 + .../tests/Sabre/CalDAV/SharingPluginTest.php | 385 + .../Subscriptions/CreateSubscriptionTest.php | 123 + .../Sabre/CalDAV/Subscriptions/PluginTest.php | 45 + .../CalDAV/Subscriptions/SubscriptionTest.php | 141 + .../sabre/dav/tests/Sabre/CalDAV/TestUtil.php | 208 + .../tests/Sabre/CalDAV/ValidateICalTest.php | 281 + .../Sabre/CardDAV/AbstractPluginTest.php | 43 + .../CardDAV/AddressBookQueryParserTest.php | 329 + .../Sabre/CardDAV/AddressBookQueryTest.php | 310 + .../Sabre/CardDAV/AddressBookRootTest.php | 31 + .../tests/Sabre/CardDAV/AddressBookTest.php | 209 + .../Sabre/CardDAV/Backend/AbstractPDOTest.php | 350 + .../dav/tests/Sabre/CardDAV/Backend/Mock.php | 148 + .../Sabre/CardDAV/Backend/PDOMySQLTest.php | 36 + .../Sabre/CardDAV/Backend/PDOSqliteTest.php | 45 + .../dav/tests/Sabre/CardDAV/CardTest.php | 215 + .../tests/Sabre/CardDAV/IDirectoryTest.php | 30 + .../dav/tests/Sabre/CardDAV/MultiGetTest.php | 99 + .../dav/tests/Sabre/CardDAV/PluginTest.php | 174 + .../Property/SupportedAddressDataTest.php | 46 + .../Property/SupportedCollationSetTest.php | 46 + .../CardDAV/SogoStripContentTypeTest.php | 56 + .../dav/tests/Sabre/CardDAV/TestUtil.php | 68 + .../Sabre/CardDAV/UserAddressBooksTest.php | 162 + .../dav/tests/Sabre/CardDAV/VCFExportTest.php | 75 + .../Sabre/CardDAV/ValidateFilterTest.php | 204 + .../tests/Sabre/CardDAV/ValidateVCardTest.php | 172 + .../dav/tests/Sabre/DAV/AbstractServer.php | 64 + .../DAV/Auth/Backend/AbstractBasicTest.php | 91 + .../DAV/Auth/Backend/AbstractDigestTest.php | 149 + .../DAV/Auth/Backend/AbstractPDOTest.php | 35 + .../Sabre/DAV/Auth/Backend/ApacheTest.php | 63 + .../DAV/Auth/Backend/BasicCallBackTest.php | 34 + .../tests/Sabre/DAV/Auth/Backend/FileTest.php | 42 + .../dav/tests/Sabre/DAV/Auth/Backend/Mock.php | 37 + .../Sabre/DAV/Auth/Backend/PDOMySQLTest.php | 31 + .../Sabre/DAV/Auth/Backend/PDOSqliteTest.php | 28 + .../dav/tests/Sabre/DAV/Auth/PluginTest.php | 94 + .../dav/tests/Sabre/DAV/BasicNodeTest.php | 235 + .../DAV/Browser/GuessContentTypeTest.php | 69 + .../DAV/Browser/MapGetToPropFindTest.php | 44 + .../tests/Sabre/DAV/Browser/PluginTest.php | 157 + .../Sabre/DAV/Browser/PropFindAllTest.php | 70 + .../sabre/dav/tests/Sabre/DAV/ClientMock.php | 34 + .../sabre/dav/tests/Sabre/DAV/ClientTest.php | 261 + .../sabre/dav/tests/Sabre/DAV/CopyTest.php | 37 + .../tests/Sabre/DAV/Exception/LockedTest.php | 68 + .../DAV/Exception/PaymentRequiredTest.php | 14 + .../DAV/Exception/ServiceUnavailableTest.php | 14 + .../DAV/Exception/TooManyMatchesTest.php | 36 + .../dav/tests/Sabre/DAV/ExceptionTest.php | 30 + .../dav/tests/Sabre/DAV/FSExt/FileTest.php | 95 + .../dav/tests/Sabre/DAV/FSExt/NodeTest.php | 191 + .../dav/tests/Sabre/DAV/FSExt/ServerTest.php | 244 + .../tests/Sabre/DAV/GetIfConditionsTest.php | 337 + .../tests/Sabre/DAV/HTTPPreferParsingTest.php | 199 + .../dav/tests/Sabre/DAV/HttpDeleteTest.php | 137 + .../sabre/dav/tests/Sabre/DAV/HttpPutTest.php | 349 + .../sabre/dav/tests/Sabre/DAV/Issue33Test.php | 106 + .../Sabre/DAV/Locks/Backend/AbstractTest.php | 196 + .../tests/Sabre/DAV/Locks/Backend/FSTest.php | 35 + .../Sabre/DAV/Locks/Backend/FileTest.php | 24 + .../tests/Sabre/DAV/Locks/Backend/Mock.php | 139 + .../Sabre/DAV/Locks/Backend/PDOMySQLTest.php | 32 + .../tests/Sabre/DAV/Locks/Backend/PDOTest.php | 29 + .../dav/tests/Sabre/DAV/Locks/MSWordTest.php | 124 + .../dav/tests/Sabre/DAV/Locks/Plugin2Test.php | 69 + .../dav/tests/Sabre/DAV/Locks/PluginTest.php | 983 ++ .../dav/tests/Sabre/DAV/Mock/Collection.php | 164 + .../sabre/dav/tests/Sabre/DAV/Mock/File.php | 143 + .../Sabre/DAV/Mock/PropertiesCollection.php | 96 + .../dav/tests/Sabre/DAV/Mount/PluginTest.php | 58 + .../dav/tests/Sabre/DAV/ObjectTreeTest.php | 100 + .../Sabre/DAV/PartialUpdate/FileMock.php | 79 + .../Sabre/DAV/PartialUpdate/PluginTest.php | 144 + .../DAV/PartialUpdate/SpecificationTest.php | 89 + .../dav/tests/Sabre/DAV/PropFindTest.php | 76 + .../dav/tests/Sabre/DAV/PropPatchTest.php | 357 + .../DAV/Property/GetLastModifiedTest.php | 75 + .../tests/Sabre/DAV/Property/HrefListTest.php | 91 + .../dav/tests/Sabre/DAV/Property/HrefTest.php | 119 + .../Sabre/DAV/Property/ResourceTypeTest.php | 105 + .../Sabre/DAV/Property/ResponseListTest.php | 19 + .../tests/Sabre/DAV/Property/ResponseTest.php | 230 + .../DAV/Property/SupportedMethodSetTest.php | 56 + .../DAV/Property/SupportedReportSetTest.php | 116 + .../Backend/AbstractPDOTest.php | 147 + .../DAV/PropertyStorage/Backend/Mock.php | 114 + .../PropertyStorage/Backend/PDOMysqlTest.php | 31 + .../PropertyStorage/Backend/PDOSqliteTest.php | 37 + .../Sabre/DAV/PropertyStorage/PluginTest.php | 108 + .../tests/Sabre/DAV/ServerCopyMoveTest.php | 277 + .../dav/tests/Sabre/DAV/ServerEventsTest.php | 92 + .../dav/tests/Sabre/DAV/ServerMKCOLTest.php | 378 + .../dav/tests/Sabre/DAV/ServerPluginTest.php | 100 + .../Sabre/DAV/ServerPreconditionTest.php | 346 + .../DAV/ServerPropsInfiniteDepthTest.php | 184 + .../dav/tests/Sabre/DAV/ServerPropsTest.php | 370 + .../dav/tests/Sabre/DAV/ServerRangeTest.php | 277 + .../dav/tests/Sabre/DAV/ServerSimpleTest.php | 632 ++ .../Sabre/DAV/ServerUpdatePropertiesTest.php | 127 + .../dav/tests/Sabre/DAV/SimpleFileTest.php | 19 + .../dav/tests/Sabre/DAV/StringUtilTest.php | 122 + .../Sabre/DAV/Sync/MockSyncCollection.php | 169 + .../dav/tests/Sabre/DAV/Sync/PluginTest.php | 543 ++ .../tests/Sabre/DAV/SyncTokenPropertyTest.php | 100 + .../Sabre/DAV/TemporaryFileFilterTest.php | 252 + .../sabre/dav/tests/Sabre/DAV/TestPlugin.php | 38 + .../sabre/dav/tests/Sabre/DAV/TreeTest.php | 241 + .../dav/tests/Sabre/DAV/UUIDUtilTest.php | 25 + .../sabre/dav/tests/Sabre/DAV/XMLUtilTest.php | 284 + .../dav/tests/Sabre/DAVACL/ACLMethodTest.php | 324 + .../tests/Sabre/DAVACL/AllowAccessTest.php | 131 + .../tests/Sabre/DAVACL/BlockAccessTest.php | 206 + .../DAVACL/Exception/AceConflictTest.php | 39 + .../Exception/NeedPrivilegesExceptionTest.php | 49 + .../Sabre/DAVACL/Exception/NoAbstractTest.php | 39 + .../Exception/NotRecognizedPrincipalTest.php | 39 + .../Exception/NotSupportedPrivilegeTest.php | 39 + .../Sabre/DAVACL/ExpandPropertiesTest.php | 310 + .../dav/tests/Sabre/DAVACL/MockACLNode.php | 56 + .../dav/tests/Sabre/DAVACL/MockPrincipal.php | 66 + .../tests/Sabre/DAVACL/PluginAdminTest.php | 85 + .../Sabre/DAVACL/PluginPropertiesTest.php | 357 + .../DAVACL/PluginUpdatePropertiesTest.php | 111 + .../PrincipalBackend/AbstractPDOTest.php | 182 + .../Sabre/DAVACL/PrincipalBackend/Mock.php | 163 + .../DAVACL/PrincipalBackend/PDOMySQLTest.php | 45 + .../DAVACL/PrincipalBackend/PDOSqliteTest.php | 42 + .../Sabre/DAVACL/PrincipalCollectionTest.php | 61 + .../DAVACL/PrincipalPropertySearchTest.php | 396 + .../DAVACL/PrincipalSearchPropertySetTest.php | 139 + .../dav/tests/Sabre/DAVACL/PrincipalTest.php | 208 + .../DAVACL/Property/ACLRestrictionsTest.php | 36 + .../tests/Sabre/DAVACL/Property/ACLTest.php | 336 + .../Property/CurrentUserPrivilegeSetTest.php | 67 + .../Sabre/DAVACL/Property/PrincipalTest.php | 181 + .../Property/SupportedPrivilegeSetTest.php | 107 + .../tests/Sabre/DAVACL/SimplePluginTest.php | 322 + .../sabre/dav/tests/Sabre/DAVServerTest.php | 239 + .../dav/tests/Sabre/HTTP/ResponseMock.php | 22 + .../sabre/dav/tests/Sabre/HTTP/SapiMock.php | 29 + .../sabre/dav/tests/Sabre/TestUtil.php | 58 + .../SabreDAV/sabre/dav/tests/bootstrap.php | 29 + .../sabre/dav/tests/phpcs/ruleset.xml | 57 + .../SabreDAV/sabre/dav/tests/phpunit.xml | 40 + .../modules/SabreDAV/sabre/event/.gitignore | 9 + .../modules/SabreDAV/sabre/event/.travis.yml | 10 + .../modules/SabreDAV/sabre/event/ChangeLog.md | 43 + sources/modules/SabreDAV/sabre/event/LICENSE | 27 + .../modules/SabreDAV/sabre/event/README.md | 34 + .../SabreDAV/sabre/event/composer.json | 27 + .../SabreDAV/sabre/event/lib/EventEmitter.php | 18 + .../sabre/event/lib/EventEmitterInterface.php | 101 + .../sabre/event/lib/EventEmitterTrait.php | 211 + .../SabreDAV/sabre/event/lib/Promise.php | 242 + .../lib/PromiseAlreadyResolvedException.php | 15 + .../SabreDAV/sabre/event/lib/Version.php | 19 + .../SabreDAV/sabre/event/phpunit.xml.dist | 18 + .../event/tests/ContinueCallbackTest.php | 76 + .../sabre/event/tests/EventEmitterTest.php | 329 + .../sabre/event/tests/PromiseTest.php | 228 + .../sabre/event/tests/benchmark/bench.php | 116 + .../modules/SabreDAV/sabre/http/.gitignore | 13 + .../modules/SabreDAV/sabre/http/.travis.yml | 12 + .../modules/SabreDAV/sabre/http/ChangeLog.md | 184 + sources/modules/SabreDAV/sabre/http/LICENSE | 27 + sources/modules/SabreDAV/sabre/http/README.md | 746 ++ .../modules/SabreDAV/sabre/http/bin/.empty | 0 .../modules/SabreDAV/sabre/http/composer.json | 39 + .../sabre/http/examples/asyncclient.php | 68 + .../sabre/http/examples/basicauth.php | 59 + .../SabreDAV/sabre/http/examples/client.php | 41 + .../sabre/http/examples/reverseproxy.php | 51 + .../sabre/http/examples/stringify.php | 54 + .../SabreDAV/sabre/http/lib/Auth/AWS.php | 234 + .../sabre/http/lib/Auth/AbstractAuth.php | 74 + .../SabreDAV/sabre/http/lib/Auth/Basic.php | 57 + .../SabreDAV/sabre/http/lib/Auth/Digest.php | 232 + .../SabreDAV/sabre/http/lib/Client.php | 588 ++ .../sabre/http/lib/ClientException.php | 15 + .../sabre/http/lib/ClientHttpException.php | 58 + .../SabreDAV/sabre/http/lib/HttpException.php | 30 + .../SabreDAV/sabre/http/lib/Message.php | 309 + .../sabre/http/lib/MessageDecoratorTrait.php | 250 + .../sabre/http/lib/MessageInterface.php | 177 + .../SabreDAV/sabre/http/lib/Request.php | 312 + .../sabre/http/lib/RequestDecorator.php | 231 + .../sabre/http/lib/RequestInterface.php | 147 + .../SabreDAV/sabre/http/lib/Response.php | 193 + .../sabre/http/lib/ResponseDecorator.php | 84 + .../sabre/http/lib/ResponseInterface.php | 45 + .../modules/SabreDAV/sabre/http/lib/Sapi.php | 176 + .../SabreDAV/sabre/http/lib/URLUtil.php | 218 + .../modules/SabreDAV/sabre/http/lib/Util.php | 266 + .../SabreDAV/sabre/http/lib/Version.php | 19 + .../sabre/http/tests/HTTP/Auth/AWSTest.php | 238 + .../sabre/http/tests/HTTP/Auth/BasicTest.php | 57 + .../sabre/http/tests/HTTP/Auth/DigestTest.php | 192 + .../sabre/http/tests/HTTP/ClientTest.php | 421 + .../http/tests/HTTP/MessageDecoratorTest.php | 90 + .../sabre/http/tests/HTTP/MessageTest.php | 197 + .../http/tests/HTTP/RequestDecoratorTest.php | 112 + .../sabre/http/tests/HTTP/RequestTest.php | 167 + .../http/tests/HTTP/ResponseDecoratorTest.php | 37 + .../sabre/http/tests/HTTP/ResponseTest.php | 48 + .../sabre/http/tests/HTTP/SapiTest.php | 120 + .../sabre/http/tests/HTTP/URLUtilTest.php | 193 + .../sabre/http/tests/HTTP/UtilTest.php | 219 + .../SabreDAV/sabre/http/tests/bootstrap.php | 9 + .../sabre/http/tests/phpcs/ruleset.xml | 57 + .../SabreDAV/sabre/http/tests/phpunit.xml | 18 + .../modules/SabreDAV/sabre/vobject/.gitignore | 18 + .../SabreDAV/sabre/vobject/.travis.yml | 17 + .../SabreDAV/sabre/vobject/ChangeLog.md | 537 ++ .../modules/SabreDAV/sabre/vobject/LICENSE | 27 + .../modules/SabreDAV/sabre/vobject/README.md | 50 + .../SabreDAV/sabre/vobject/bin/bench.php | 12 + .../sabre/vobject/bin/fetch_windows_zones.php | 47 + .../sabre/vobject/bin/generate_vcards | 241 + .../vobject/bin/generateicalendardata.php | 91 + .../SabreDAV/sabre/vobject/bin/rrulebench.php | 32 + .../SabreDAV/sabre/vobject/bin/vobject | 27 + .../SabreDAV/sabre/vobject/composer.json | 50 + .../SabreDAV/sabre/vobject/lib/Cli.php | 761 ++ .../SabreDAV/sabre/vobject/lib/Component.php | 595 ++ .../sabre/vobject/lib/Component/Available.php | 108 + .../sabre/vobject/lib/Component/VAlarm.php | 137 + .../vobject/lib/Component/VAvailability.php | 99 + .../sabre/vobject/lib/Component/VCalendar.php | 526 ++ .../sabre/vobject/lib/Component/VCard.php | 452 + .../sabre/vobject/lib/Component/VEvent.php | 140 + .../sabre/vobject/lib/Component/VFreeBusy.php | 103 + .../sabre/vobject/lib/Component/VJournal.php | 91 + .../sabre/vobject/lib/Component/VTimeZone.php | 68 + .../sabre/vobject/lib/Component/VTodo.php | 177 + .../sabre/vobject/lib/DateTimeParser.php | 431 + .../SabreDAV/sabre/vobject/lib/Document.php | 261 + .../sabre/vobject/lib/ElementList.php | 172 + .../sabre/vobject/lib/EofException.php | 15 + .../sabre/vobject/lib/FreeBusyGenerator.php | 363 + .../sabre/vobject/lib/ITip/Broker.php | 956 ++ .../sabre/vobject/lib/ITip/ITipException.php | 15 + .../sabre/vobject/lib/ITip/Message.php | 141 + ...SameOrganizerForAllComponentsException.php | 18 + .../SabreDAV/sabre/vobject/lib/Node.php | 226 + .../SabreDAV/sabre/vobject/lib/Parameter.php | 373 + .../sabre/vobject/lib/ParseException.php | 13 + .../sabre/vobject/lib/Parser/Json.php | 194 + .../sabre/vobject/lib/Parser/MimeDir.php | 628 ++ .../sabre/vobject/lib/Parser/Parser.php | 77 + .../SabreDAV/sabre/vobject/lib/Property.php | 518 ++ .../sabre/vobject/lib/Property/Binary.php | 127 + .../sabre/vobject/lib/Property/Boolean.php | 63 + .../sabre/vobject/lib/Property/FlatText.php | 49 + .../sabre/vobject/lib/Property/Float.php | 104 + .../lib/Property/ICalendar/CalAddress.php | 61 + .../vobject/lib/Property/ICalendar/Date.php | 18 + .../lib/Property/ICalendar/DateTime.php | 388 + .../lib/Property/ICalendar/Duration.php | 86 + .../vobject/lib/Property/ICalendar/Period.php | 129 + .../vobject/lib/Property/ICalendar/Recur.php | 203 + .../sabre/vobject/lib/Property/Integer.php | 72 + .../sabre/vobject/lib/Property/Text.php | 333 + .../sabre/vobject/lib/Property/Time.php | 94 + .../sabre/vobject/lib/Property/Unknown.php | 50 + .../sabre/vobject/lib/Property/Uri.php | 95 + .../sabre/vobject/lib/Property/UtcOffset.php | 37 + .../sabre/vobject/lib/Property/VCard/Date.php | 33 + .../lib/Property/VCard/DateAndOrTime.php | 317 + .../vobject/lib/Property/VCard/DateTime.php | 33 + .../lib/Property/VCard/LanguageTag.php | 59 + .../vobject/lib/Property/VCard/TimeStamp.php | 69 + .../SabreDAV/sabre/vobject/lib/Reader.php | 73 + .../sabre/vobject/lib/Recur/EventIterator.php | 503 + .../lib/Recur/NoInstancesException.php | 18 + .../sabre/vobject/lib/Recur/RDateIterator.php | 174 + .../sabre/vobject/lib/Recur/RRuleIterator.php | 901 ++ .../sabre/vobject/lib/RecurrenceIterator.php | 21 + .../sabre/vobject/lib/Splitter/ICalendar.php | 116 + .../lib/Splitter/SplitterInterface.php | 39 + .../sabre/vobject/lib/Splitter/VCard.php | 79 + .../SabreDAV/sabre/vobject/lib/StringUtil.php | 65 + .../sabre/vobject/lib/TimeZoneUtil.php | 265 + .../SabreDAV/sabre/vobject/lib/UUIDUtil.php | 67 + .../sabre/vobject/lib/VCardConverter.php | 459 + .../SabreDAV/sabre/vobject/lib/Version.php | 19 + .../lib/timezonedata/exchangezones.php | 93 + .../vobject/lib/timezonedata/lotuszones.php | 101 + .../sabre/vobject/lib/timezonedata/php-bc.php | 153 + .../lib/timezonedata/php-workaround.php | 45 + .../vobject/lib/timezonedata/windowszones.php | 118 + .../vobject/tests/VObject/AttachIssueTest.php | 22 + .../sabre/vobject/tests/VObject/CliTest.php | 650 ++ .../tests/VObject/Component/VAlarmTest.php | 179 + .../VObject/Component/VAvailabilityTest.php | 385 + .../tests/VObject/Component/VCalendarTest.php | 696 ++ .../tests/VObject/Component/VCardTest.php | 288 + .../tests/VObject/Component/VEventTest.php | 76 + .../tests/VObject/Component/VFreeBusyTest.php | 66 + .../tests/VObject/Component/VJournalTest.php | 101 + .../tests/VObject/Component/VTimeZoneTest.php | 57 + .../tests/VObject/Component/VTodoTest.php | 180 + .../vobject/tests/VObject/ComponentTest.php | 528 ++ .../tests/VObject/DateTimeParserTest.php | 417 + .../vobject/tests/VObject/DocumentTest.php | 70 + .../vobject/tests/VObject/ElementListTest.php | 33 + .../vobject/tests/VObject/EmClientTest.php | 55 + .../tests/VObject/EmptyParameterTest.php | 69 + .../tests/VObject/EmptyValueIssueTest.php | 31 + .../tests/VObject/FreeBusyGeneratorTest.php | 394 + .../tests/VObject/GoogleColonEscapingTest.php | 31 + .../VObject/ICalendar/AttachParseTest.php | 31 + .../VObject/ITip/BrokerAttendeeReplyTest.php | 1037 +++ .../VObject/ITip/BrokerDeleteEventTest.php | 193 + .../tests/VObject/ITip/BrokerNewEventTest.php | 507 + .../VObject/ITip/BrokerProcessMessageTest.php | 168 + .../VObject/ITip/BrokerProcessReplyTest.php | 425 + .../tests/VObject/ITip/BrokerTester.php | 103 + .../VObject/ITip/BrokerUpdateEventTest.php | 810 ++ .../tests/VObject/ITip/EvolutionTest.php | 2653 ++++++ .../tests/VObject/ITip/MessageTest.php | 32 + .../vobject/tests/VObject/Issue153Test.php | 14 + .../vobject/tests/VObject/Issue26Test.php | 36 + .../tests/VObject/Issue36WorkAroundTest.php | 39 + .../vobject/tests/VObject/Issue40Test.php | 30 + .../vobject/tests/VObject/Issue64Test.php | 19 + .../vobject/tests/VObject/Issue96Test.php | 24 + .../sabre/vobject/tests/VObject/JCalTest.php | 150 + .../sabre/vobject/tests/VObject/JCardTest.php | 195 + .../tests/VObject/LineFoldingIssueTest.php | 23 + .../vobject/tests/VObject/ParameterTest.php | 135 + .../vobject/tests/VObject/Parser/JsonTest.php | 395 + .../tests/VObject/Parser/MimeDirTest.php | 21 + .../VObject/Parser/QuotedPrintableTest.php | 108 + .../tests/VObject/Property/BinaryTest.php | 19 + .../tests/VObject/Property/BooleanTest.php | 22 + .../tests/VObject/Property/CompoundTest.php | 50 + .../tests/VObject/Property/FloatTest.php | 30 + .../Property/ICalendar/CalAddressTest.php | 32 + .../Property/ICalendar/DateTimeTest.php | 359 + .../Property/ICalendar/DurationTest.php | 20 + .../VObject/Property/ICalendar/RecurTest.php | 46 + .../tests/VObject/Property/TextTest.php | 96 + .../Property/VCard/DateAndOrTimeTest.php | 233 + .../Property/VCard/LanguageTagTest.php | 27 + .../vobject/tests/VObject/PropertyTest.php | 360 + .../vobject/tests/VObject/ReaderTest.php | 449 + .../EventIterator/ByMonthInDailyTest.php | 58 + .../EventIterator/ExpandFloatingTimesTest.php | 119 + .../EventIterator/FifthTuesdayProblemTest.php | 54 + .../EventIterator/IncorrectExpandTest.php | 62 + .../EventIterator/InfiniteLoopProblemTest.php | 99 + .../Recur/EventIterator/Issue48Test.php | 49 + .../Recur/EventIterator/Issue50Test.php | 128 + .../VObject/Recur/EventIterator/MainTest.php | 1427 +++ .../EventIterator/MissingOverriddenTest.php | 63 + .../Recur/EventIterator/NoInstancesTest.php | 40 + .../EventIterator/OverrideFirstEventTest.php | 122 + .../tests/VObject/Recur/RDateIteratorTest.php | 56 + .../tests/VObject/Recur/RRuleIteratorTest.php | 698 ++ .../UntilRespectsTimezoneTest.ics | 39 + .../vobject/tests/VObject/SlashRTest.php | 20 + .../tests/VObject/Splitter/ICalendarTest.php | 325 + .../tests/VObject/Splitter/VCardTest.php | 195 + .../vobject/tests/VObject/StringUtilTest.php | 55 + .../sabre/vobject/tests/VObject/TestCase.php | 50 + .../tests/VObject/TimeZoneUtilTest.php | 369 + .../vobject/tests/VObject/UUIDUtilTest.php | 37 + .../vobject/tests/VObject/VCard21Test.php | 52 + .../tests/VObject/VCardConverterTest.php | 531 ++ .../vobject/tests/VObject/VersionTest.php | 14 + .../sabre/vobject/tests/VObject/issue153.vcf | 352 + .../sabre/vobject/tests/VObject/issue64.vcf | 351 + .../sabre/vobject/tests/bootstrap.php | 25 + .../sabre/vobject/tests/phpcs/ruleset.xml | 57 + .../SabreDAV/sabre/vobject/tests/phpunit.xml | 21 + sources/modules/Tmdb/Api/AbstractApi.php | 175 + sources/modules/Tmdb/Api/Account.php | 117 + sources/modules/Tmdb/Api/ApiInterface.php | 21 + sources/modules/Tmdb/Api/Authentication.php | 156 + sources/modules/Tmdb/Api/Certifications.php | 36 + sources/modules/Tmdb/Api/Changes.php | 61 + sources/modules/Tmdb/Api/Collections.php | 53 + sources/modules/Tmdb/Api/Companies.php | 47 + sources/modules/Tmdb/Api/Configuration.php | 47 + sources/modules/Tmdb/Api/Credits.php | 44 + sources/modules/Tmdb/Api/Discover.php | 46 + sources/modules/Tmdb/Api/Find.php | 48 + sources/modules/Tmdb/Api/Genres.php | 81 + sources/modules/Tmdb/Api/GuestSession.php | 42 + sources/modules/Tmdb/Api/Jobs.php | 33 + sources/modules/Tmdb/Api/Keywords.php | 47 + sources/modules/Tmdb/Api/Lists.php | 120 + sources/modules/Tmdb/Api/Movies.php | 280 + sources/modules/Tmdb/Api/Networks.php | 37 + sources/modules/Tmdb/Api/People.php | 159 + sources/modules/Tmdb/Api/Reviews.php | 34 + sources/modules/Tmdb/Api/Search.php | 126 + sources/modules/Tmdb/Api/Timezones.php | 31 + sources/modules/Tmdb/Api/Tv.php | 157 + sources/modules/Tmdb/Api/TvEpisode.php | 166 + sources/modules/Tmdb/Api/TvSeason.php | 91 + sources/modules/Tmdb/ApiToken.php | 57 + sources/modules/Tmdb/Client.php | 539 ++ .../modules/Tmdb/Common/ObjectHydrator.php | 87 + .../Exception/InvalidArgumentException.php | 21 + .../Exception/MissingArgumentException.php | 21 + .../MissingSessionTokenException.php | 21 + .../Exception/NotImplementedException.php | 21 + .../Tmdb/Exception/RuntimeException.php | 21 + .../Tmdb/Exception/TmdbApiException.php | 26 + .../UnauthorizedRequestTokenException.php | 21 + .../modules/Tmdb/Factory/AbstractFactory.php | 119 + .../modules/Tmdb/Factory/AccountFactory.php | 134 + .../Tmdb/Factory/AuthenticationFactory.php | 118 + .../Tmdb/Factory/CertificationFactory.php | 60 + .../modules/Tmdb/Factory/ChangesFactory.php | 62 + .../Tmdb/Factory/CollectionFactory.php | 129 + .../Tmdb/Factory/Common/ChangeFactory.php | 75 + .../Common/GenericCollectionFactory.php | 56 + .../Tmdb/Factory/Common/VideoFactory.php | 72 + .../modules/Tmdb/Factory/CompanyFactory.php | 76 + .../Tmdb/Factory/ConfigurationFactory.php | 40 + .../modules/Tmdb/Factory/CreditsFactory.php | 149 + sources/modules/Tmdb/Factory/FindFactory.php | 135 + sources/modules/Tmdb/Factory/GenreFactory.php | 51 + .../Tmdb/Factory/GuestSessionFactory.php | 36 + sources/modules/Tmdb/Factory/ImageFactory.php | 202 + sources/modules/Tmdb/Factory/JobsFactory.php | 49 + .../modules/Tmdb/Factory/KeywordFactory.php | 51 + sources/modules/Tmdb/Factory/ListFactory.php | 146 + .../Tmdb/Factory/Lists/ListItemFactory.php | 97 + .../Factory/Movie/AlternativeTitleFactory.php | 48 + .../Tmdb/Factory/Movie/ListItemFactory.php | 72 + sources/modules/Tmdb/Factory/MovieFactory.php | 431 + .../modules/Tmdb/Factory/NetworkFactory.php | 51 + .../Tmdb/Factory/People/CastFactory.php | 52 + .../Tmdb/Factory/People/CrewFactory.php | 52 + .../modules/Tmdb/Factory/PeopleFactory.php | 206 + .../modules/Tmdb/Factory/ReviewFactory.php | 40 + .../modules/Tmdb/Factory/TimezoneFactory.php | 56 + .../modules/Tmdb/Factory/TvEpisodeFactory.php | 207 + sources/modules/Tmdb/Factory/TvFactory.php | 325 + .../modules/Tmdb/Factory/TvSeasonFactory.php | 234 + sources/modules/Tmdb/GuestSessionToken.php | 21 + sources/modules/Tmdb/Helper/ImageHelper.php | 97 + .../modules/Tmdb/HttpClient/HttpClient.php | 380 + .../Tmdb/HttpClient/HttpClientInterface.php | 108 + .../Plugin/AcceptJsonHeaderPlugin.php | 33 + .../HttpClient/Plugin/AdultFilterPlugin.php | 44 + .../Tmdb/HttpClient/Plugin/ApiTokenPlugin.php | 48 + .../Plugin/LanguageFilterPlugin.php | 44 + .../HttpClient/Plugin/SessionTokenPlugin.php | 53 + sources/modules/Tmdb/Model/AbstractModel.php | 27 + sources/modules/Tmdb/Model/Account.php | 176 + .../modules/Tmdb/Model/Account/ListItem.php | 253 + sources/modules/Tmdb/Model/Certification.php | 79 + .../Certification/CountryCertification.php | 100 + sources/modules/Tmdb/Model/Change.php | 76 + sources/modules/Tmdb/Model/Collection.php | 255 + .../modules/Tmdb/Model/Collection/Changes.php | 21 + .../Model/Collection/CreditsCollection.php | 81 + .../CreditsCollection/CombinedCredits.php | 23 + .../CreditsCollection/MovieCredits.php | 23 + .../CreditsCollection/TvCredits.php | 23 + .../modules/Tmdb/Model/Collection/Genres.php | 60 + .../modules/Tmdb/Model/Collection/Images.php | 214 + .../modules/Tmdb/Model/Collection/Jobs.php | 102 + .../Tmdb/Model/Collection/Keywords.php | 54 + .../modules/Tmdb/Model/Collection/People.php | 55 + .../Tmdb/Model/Collection/People/Cast.php | 44 + .../Tmdb/Model/Collection/People/Crew.php | 44 + .../Collection/People/PersonInterface.php | 27 + .../QueryParameter/AppendToResponse.php | 24 + .../Collection/QueryParametersCollection.php | 23 + .../Model/Collection/ResultCollection.php | 103 + .../Tmdb/Model/Collection/Timezones.php | 60 + .../modules/Tmdb/Model/Collection/Videos.php | 54 + .../Tmdb/Model/Common/AbstractTrailer.php | 29 + sources/modules/Tmdb/Model/Common/Change.php | 79 + .../modules/Tmdb/Model/Common/Change/Item.php | 129 + sources/modules/Tmdb/Model/Common/Country.php | 69 + .../modules/Tmdb/Model/Common/ExternalIds.php | 152 + .../Tmdb/Model/Common/GenericCollection.php | 385 + .../Model/Common/QueryParameter/Adult.php | 43 + .../QueryParameter/AppendToResponse.php | 30 + .../Model/Common/QueryParameter/Language.php | 43 + .../QueryParameterInterface.php | 30 + .../Type/CollectionToCommaSeperatedString.php | 45 + .../Tmdb/Model/Common/SpokenLanguage.php | 69 + .../Tmdb/Model/Common/Trailer/Youtube.php | 124 + .../modules/Tmdb/Model/Common/Translation.php | 49 + sources/modules/Tmdb/Model/Common/Video.php | 236 + .../Tmdb/Model/Common/Video/Youtube.php | 28 + sources/modules/Tmdb/Model/Company.php | 193 + sources/modules/Tmdb/Model/Configuration.php | 73 + sources/modules/Tmdb/Model/Credits.php | 206 + sources/modules/Tmdb/Model/Credits/Media.php | 174 + .../modules/Tmdb/Model/Filter/AdultFilter.php | 22 + .../Tmdb/Model/Filter/CountryFilter.php | 25 + .../modules/Tmdb/Model/Filter/ImageFilter.php | 21 + .../Tmdb/Model/Filter/LanguageFilter.php | 22 + sources/modules/Tmdb/Model/Find.php | 95 + sources/modules/Tmdb/Model/Genre.php | 66 + sources/modules/Tmdb/Model/Image.php | 224 + .../Tmdb/Model/Image/BackdropImage.php | 23 + .../modules/Tmdb/Model/Image/LogoImage.php | 23 + .../modules/Tmdb/Model/Image/PosterImage.php | 23 + .../modules/Tmdb/Model/Image/ProfileImage.php | 23 + .../modules/Tmdb/Model/Image/StillImage.php | 23 + sources/modules/Tmdb/Model/Job.php | 69 + sources/modules/Tmdb/Model/Keyword.php | 66 + sources/modules/Tmdb/Model/Lists.php | 279 + .../modules/Tmdb/Model/Lists/ItemStatus.php | 78 + sources/modules/Tmdb/Model/Lists/ListItem.php | 278 + sources/modules/Tmdb/Model/Lists/Result.php | 78 + .../Tmdb/Model/Lists/ResultWithListId.php | 53 + sources/modules/Tmdb/Model/Movie.php | 981 ++ .../Tmdb/Model/Movie/AccountStates.php | 124 + .../Tmdb/Model/Movie/AlternativeTitle.php | 69 + sources/modules/Tmdb/Model/Movie/ListItem.php | 225 + .../Movie/QueryParameter/AppendToResponse.php | 35 + sources/modules/Tmdb/Model/Movie/Rating.php | 50 + sources/modules/Tmdb/Model/Movie/Release.php | 94 + sources/modules/Tmdb/Model/Network.php | 80 + sources/modules/Tmdb/Model/Person.php | 502 + .../Tmdb/Model/Person/AbstractMember.php | 135 + .../modules/Tmdb/Model/Person/CastMember.php | 128 + .../modules/Tmdb/Model/Person/CrewMember.php | 103 + .../modules/Tmdb/Model/Person/MovieCredit.php | 254 + .../QueryParameter/AppendToResponse.php | 29 + .../modules/Tmdb/Model/Query/ChangesQuery.php | 61 + .../Query/Discover/DiscoverMoviesQuery.php | 287 + .../Model/Query/Discover/DiscoverTvQuery.php | 227 + .../modules/Tmdb/Model/Query/FindQuery.php | 24 + sources/modules/Tmdb/Model/Review.php | 192 + .../modules/Tmdb/Model/Search/SearchQuery.php | 48 + .../SearchQuery/CollectionSearchQuery.php | 35 + .../Search/SearchQuery/CompanySearchQuery.php | 23 + .../Search/SearchQuery/KeywordSearchQuery.php | 23 + .../Search/SearchQuery/ListSearchQuery.php | 35 + .../Search/SearchQuery/MovieSearchQuery.php | 96 + .../Search/SearchQuery/PersonSearchQuery.php | 53 + .../Search/SearchQuery/TvSearchQuery.php | 70 + .../Tmdb/Model/Timezone/CountryTimezone.php | 96 + sources/modules/Tmdb/Model/Tv.php | 817 ++ sources/modules/Tmdb/Model/Tv/Episode.php | 429 + .../QueryParameter/AppendToResponse.php | 27 + .../Tv/QueryParameter/AppendToResponse.php | 28 + sources/modules/Tmdb/Model/Tv/Season.php | 351 + .../QueryParameter/AppendToResponse.php | 27 + .../Tmdb/Repository/AbstractRepository.php | 88 + .../Tmdb/Repository/AccountRepository.php | 154 + .../Repository/AuthenticationRepository.php | 148 + .../Repository/CertificationRepository.php | 58 + .../Tmdb/Repository/ChangesRepository.php | 87 + .../Tmdb/Repository/CollectionRepository.php | 115 + .../Tmdb/Repository/CompanyRepository.php | 105 + .../Repository/ConfigurationRepository.php | 55 + .../Tmdb/Repository/CreditsRepository.php | 58 + .../Tmdb/Repository/DiscoverRepository.php | 107 + .../Tmdb/Repository/FindRepository.php | 57 + .../Tmdb/Repository/GenreRepository.php | 98 + .../Repository/GuestSessionRepository.php | 70 + .../Tmdb/Repository/JobsRepository.php | 78 + .../Tmdb/Repository/KeywordRepository.php | 75 + .../Tmdb/Repository/ListRepository.php | 151 + .../Tmdb/Repository/MovieRepository.php | 468 + .../Tmdb/Repository/NetworkRepository.php | 60 + .../Tmdb/Repository/PeopleRepository.php | 210 + .../Tmdb/Repository/ReviewRepository.php | 57 + .../Tmdb/Repository/SearchRepository.php | 360 + .../Tmdb/Repository/TimezoneRepository.php | 56 + .../Tmdb/Repository/TvEpisodeRepository.php | 251 + .../modules/Tmdb/Repository/TvRepository.php | 219 + .../Tmdb/Repository/TvSeasonRepository.php | 191 + sources/modules/Tmdb/RequestToken.php | 110 + sources/modules/Tmdb/SessionToken.php | 106 + sources/modules/UberViz/AudioHandler.js | 12 +- sources/modules/UberViz/style.css | 14 +- sources/modules/ZipStream/Exception.php | 11 + .../Exception/FileNotFoundException.php | 20 + .../Exception/FileNotReadableException.php | 20 + .../Exception/InvalidOptionException.php | 22 + sources/modules/ZipStream/ZipStream.php | 860 ++ sources/modules/ampacheapi/AmpacheApi.lib.php | 5 +- sources/modules/archive/archive.lib.php | 727 -- sources/modules/aurora.js/aac.js | 4651 ++++++++++ sources/modules/aurora.js/aac.js.map | 37 + sources/modules/aurora.js/alac.js | 725 ++ sources/modules/aurora.js/alac.js.map | 21 + sources/modules/aurora.js/aurora.js | 3937 ++++++++ sources/modules/aurora.js/aurora.js.map | 77 + sources/modules/aurora.js/flac.js | 793 ++ sources/modules/aurora.js/flac.js.map | 21 + sources/modules/aurora.js/mp3.js | 7706 ++++++++++++++++ sources/modules/aurora.js/mp3.js.map | 43 + sources/modules/aurora.js/ogg.js | 100 + sources/modules/aurora.js/opus.js | 139 + sources/modules/aurora.js/vorbis.js | 161 + sources/modules/captcha/FreeMono.ttf | Bin 0 -> 343980 bytes sources/modules/captcha/MyUnderwood.ttf | Bin 50096 -> 0 bytes sources/modules/captcha/MyUnderwood.txt | 22 - sources/modules/captcha/captcha.php | 16 +- sources/modules/catalog/beets.catalog.php | 153 + .../modules/catalog/beetsremote.catalog.php | 141 + sources/modules/catalog/dropbox.catalog.php | 97 +- sources/modules/catalog/local.catalog.php | 370 +- sources/modules/catalog/remote.catalog.php | 22 +- .../modules/catalog/soundcloud.catalog.php | 52 +- sources/modules/catalog/subsonic.catalog.php | 32 +- sources/modules/getid3/docs/changelog.txt | 1446 +-- sources/modules/getid3/docs/readme.txt | 15 +- sources/modules/getid3/getid3.lib.php | 36 +- sources/modules/getid3/getid3.php | 16 +- .../modules/getid3/module.archive.gzip.php | 4 +- .../getid3/module.audio-video.quicktime.php | 249 +- sources/modules/getid3/module.audio.flac.php | 26 +- sources/modules/getid3/module.audio.mp3.php | 18 +- sources/modules/getid3/module.audio.ogg.php | 91 +- sources/modules/getid3/module.tag.apetag.php | 109 +- sources/modules/getid3/module.tag.id3v2.php | 311 +- sources/modules/getid3/module.tag.lyrics3.php | 30 +- sources/modules/getid3/write.id3v2.php | 6 + sources/modules/httpq/httpqplayer.class.php | 2 +- .../infotools/AmazonSearchEngine.class.php | 3 +- sources/modules/infotools/lastfm.class.php | 206 - .../jquery.cookie.js | 0 .../jquery.datetimepicker.css | 417 + .../jquery.datetimepicker.js | 1538 +++ .../jquery-file-upload/jquery.fileupload.js | 1426 +++ .../jquery.iframe-transport.js | 185 + .../jplayer.midnight.black.interface.png | Bin .../jplayer.midnight.black.playlist.png | Bin .../jplayer.midnight.black.png | Bin .../jplayer.midnight.black.seeking.gif | Bin .../jplayer.midnight.black.video.play.png | Bin sources/modules/jquery-jplayer/Jplayer.swf | Bin 14174 -> 0 bytes .../add-on/jplayer.playlist.min.js | 2 + .../jquery-jplayer/jplayer.playlist.min.js | 34 - .../jquery-jplayer/jquery.jplayer.min.js | 117 +- .../modules/jquery-jplayer/jquery.jplayer.swf | Bin 0 -> 13714 bytes sources/modules/jquery-knob/jquery.knob.js | 661 ++ .../jquery-mediaTable/jquery.mediaTable.css | 1 + .../jquery-mediaTable/jquery.mediaTable.js | 2 +- .../jquery.qrcode.min.js | 0 .../animated-overlay.gif | Bin .../images/animated-overlay.gif | Bin .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin .../images/ui-bg_flat_75_ffffff_40x100.png | Bin .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin .../images/ui-bg_glass_65_ffffff_1x400.png | Bin .../images/ui-bg_glass_75_dadada_1x400.png | Bin .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin .../images/ui-icons_222222_256x240.png | Bin .../images/ui-icons_2e83ff_256x240.png | Bin .../images/ui-icons_454545_256x240.png | Bin .../images/ui-icons_888888_256x240.png | Bin .../images/ui-icons_cd0a0a_256x240.png | Bin .../jquery-ui.min.css | 0 sources/modules/jstree/jstree.min.js | 5 + .../modules/jstree/themes/default/32px.png | Bin 0 -> 3121 bytes .../modules/jstree/themes/default/40px.png | Bin 0 -> 1413 bytes .../modules/jstree/themes/default/style.css | 952 ++ .../jstree/themes/default/style.min.css | 1 + .../jstree/themes/default/throbber.gif | Bin 0 -> 1720 bytes .../modules/localplay/httpq.controller.php | 20 +- sources/modules/localplay/mpd.controller.php | 44 +- sources/modules/localplay/upnp.controller.php | 518 ++ sources/modules/localplay/vlc.controller.php | 16 +- sources/modules/localplay/xbmc.controller.php | 10 +- .../musicbrainz/Clients/RequestsMbClient.php | 2 +- .../{twitter/twitteroauth => oauth}/OAuth.php | 146 +- sources/modules/pChart/fonts/Bedizen.ttf | Bin 0 -> 42912 bytes sources/modules/pChart/fonts/Forgotte.ttf | Bin 0 -> 42148 bytes sources/modules/pChart/fonts/GeosansLight.ttf | Bin 0 -> 60072 bytes sources/modules/pChart/fonts/MankSans.ttf | Bin 0 -> 58492 bytes sources/modules/pChart/fonts/Silkscreen.ttf | Bin 0 -> 16172 bytes sources/modules/pChart/fonts/advent_light.ttf | Bin 0 -> 45768 bytes sources/modules/pChart/fonts/calibri.ttf | Bin 0 -> 811052 bytes sources/modules/pChart/fonts/pf_arma_five.ttf | Bin 0 -> 21936 bytes sources/modules/pChart/fonts/verdana.ttf | Bin 0 -> 189144 bytes sources/modules/pChart/pBarcode128.class.php | 184 + sources/modules/pChart/pBarcode39.class.php | 200 + sources/modules/pChart/pBubble.class.php | 326 + sources/modules/pChart/pCache.class.php | 280 + sources/modules/pChart/pData.class.php | 788 ++ sources/modules/pChart/pDraw.class.php | 6216 +++++++++++++ sources/modules/pChart/pImage.class.php | 472 + sources/modules/pChart/pIndicator.class.php | 241 + sources/modules/pChart/pPie.class.php | 1500 +++ sources/modules/pChart/pRadar.class.php | 681 ++ sources/modules/pChart/pScatter.class.php | 1158 +++ sources/modules/pChart/pSplit.class.php | 131 + sources/modules/pChart/pSpring.class.php | 868 ++ sources/modules/pChart/pStock.class.php | 216 + sources/modules/pChart/pSurface.class.php | 315 + .../modules/phpmailer/PHPMailerAutoload.php | 26 +- sources/modules/phpmailer/README.md | 87 +- sources/modules/phpmailer/changelog.md | 153 +- sources/modules/phpmailer/class.phpmailer.php | 1535 +-- sources/modules/phpmailer/class.pop3.php | 154 +- sources/modules/phpmailer/class.smtp.php | 777 +- sources/modules/phpmailer/composer.json | 18 +- sources/modules/phpmailer/docs.ini | 92 - sources/modules/phpmailer/docs/extending.html | 122 +- sources/modules/phpmailer/docs/faq.html | 77 +- .../modules/phpmailer/docs/generatedocs.sh | 7 +- sources/modules/phpmailer/docs/use_gmail.txt | 44 - .../modules/phpmailer/extras/EasyPeasyICS.php | 214 +- sources/modules/phpmailer/extras/README.md | 17 + .../phpmailer/extras/class.html2text.php | 696 -- .../modules/phpmailer/extras/htmlfilter.php | 1765 ++-- .../phpmailer/extras/ntlm_sasl_client.php | 332 +- .../phpmailer/language/phpmailer.lang-am.php | 26 + .../phpmailer/language/phpmailer.lang-ar.php | 41 +- .../phpmailer/language/phpmailer.lang-az.php | 26 + .../phpmailer/language/phpmailer.lang-be.php | 26 + .../phpmailer/language/phpmailer.lang-bg.php | 26 + .../phpmailer/language/phpmailer.lang-br.php | 11 +- .../phpmailer/language/phpmailer.lang-ca.php | 31 +- .../phpmailer/language/phpmailer.lang-ch.php | 33 +- .../phpmailer/language/phpmailer.lang-cz.php | 7 +- .../phpmailer/language/phpmailer.lang-de.php | 9 +- .../phpmailer/language/phpmailer.lang-dk.php | 9 +- .../phpmailer/language/phpmailer.lang-el.php | 25 + .../phpmailer/language/phpmailer.lang-eo.php | 7 +- .../phpmailer/language/phpmailer.lang-es.php | 32 +- .../phpmailer/language/phpmailer.lang-et.php | 13 +- .../phpmailer/language/phpmailer.lang-fa.php | 26 +- .../phpmailer/language/phpmailer.lang-fi.php | 11 +- .../phpmailer/language/phpmailer.lang-fo.php | 12 +- .../phpmailer/language/phpmailer.lang-fr.php | 14 +- .../phpmailer/language/phpmailer.lang-gl.php | 27 + .../phpmailer/language/phpmailer.lang-he.php | 8 +- .../phpmailer/language/phpmailer.lang-hr.php | 26 + .../phpmailer/language/phpmailer.lang-hu.php | 8 +- .../phpmailer/language/phpmailer.lang-id.php | 26 + .../phpmailer/language/phpmailer.lang-it.php | 13 +- .../phpmailer/language/phpmailer.lang-ja.php | 35 +- .../phpmailer/language/phpmailer.lang-ka.php | 26 + .../phpmailer/language/phpmailer.lang-ko.php | 26 + .../phpmailer/language/phpmailer.lang-lt.php | 8 +- .../phpmailer/language/phpmailer.lang-lv.php | 26 + .../phpmailer/language/phpmailer.lang-ms.php | 26 + .../phpmailer/language/phpmailer.lang-nl.php | 42 +- .../phpmailer/language/phpmailer.lang-no.php | 13 +- .../phpmailer/language/phpmailer.lang-pl.php | 14 +- .../phpmailer/language/phpmailer.lang-pt.php | 26 + .../phpmailer/language/phpmailer.lang-ro.php | 22 +- .../phpmailer/language/phpmailer.lang-ru.php | 10 +- .../phpmailer/language/phpmailer.lang-se.php | 11 +- .../phpmailer/language/phpmailer.lang-sk.php | 11 +- .../phpmailer/language/phpmailer.lang-sl.php | 26 + .../phpmailer/language/phpmailer.lang-sr.php | 26 + .../phpmailer/language/phpmailer.lang-tr.php | 43 +- .../phpmailer/language/phpmailer.lang-uk.php | 8 +- .../phpmailer/language/phpmailer.lang-vi.php | 26 + .../phpmailer/language/phpmailer.lang-zh.php | 47 +- .../language/phpmailer.lang-zh_cn.php | 44 +- sources/modules/plugins/7Digital.plugin.php | 167 + sources/modules/plugins/Amazon.plugin.php | 238 + sources/modules/plugins/Bitly.plugin.php | 25 +- .../plugins/CatalogFavorites.plugin.php | 144 + .../modules/plugins/ChartLyrics.plugin.php | 9 +- sources/modules/plugins/Facebook.plugin.php | 88 + sources/modules/plugins/Flattr.plugin.php | 107 + sources/modules/plugins/Flickr.plugin.php | 41 +- .../plugins/GoogleAnalytics.plugin.php | 111 + sources/modules/plugins/GoogleMaps.plugin.php | 156 + sources/modules/plugins/GooglePlus.plugin.php | 88 + sources/modules/plugins/Gravatar.plugin.php | 3 +- sources/modules/plugins/Growl.plugin.php | 10 +- sources/modules/plugins/Headphones.plugin.php | 4 +- sources/modules/plugins/Lastfm.plugin.php | 21 +- sources/modules/plugins/Libravatar.plugin.php | 3 +- sources/modules/plugins/Librefm.plugin.php | 22 +- sources/modules/plugins/LyricWiki.plugin.php | 9 +- .../modules/plugins/MusicBrainz.plugin.php | 28 +- sources/modules/plugins/Omdb.plugin.php | 163 + sources/modules/plugins/Paypal.plugin.php | 126 + sources/modules/plugins/Piwik.plugin.php | 131 + sources/modules/plugins/RSSView.plugin.php | 135 + sources/modules/plugins/ShoutHome.plugin.php | 108 + .../plugins/StreamBandwidth.plugin.php | 132 + sources/modules/plugins/StreamHits.plugin.php | 126 + sources/modules/plugins/StreamTime.plugin.php | 132 + sources/modules/plugins/TheAudioDb.plugin.php | 209 + sources/modules/plugins/Tmdb.plugin.php | 210 + sources/modules/plugins/Tvdb.plugin.php | 179 + sources/modules/plugins/Twitter.plugin.php | 91 + sources/modules/plugins/Yourls.plugin.php | 27 +- sources/modules/subsonic/subsonic.client.php | 2 +- sources/modules/twitter/LICENSE | 23 - sources/modules/twitter/README | 13 - sources/modules/twitter/Twitter-Icon.png | Bin 26897 -> 0 bytes sources/modules/twitter/twitter.sql | 25 - sources/modules/twitter/twitter_login.php | 56 - sources/modules/twitter/twitter_update.php | 60 - sources/modules/twitter/twitter_works.php | 129 - .../twitter/twitteroauth/twitteroauth.php | 268 - sources/modules/upnp/upnpdevice.php | 259 + sources/modules/upnp/upnpplayer.class.php | 384 + sources/modules/upnp/upnpplaylist.php | 141 + sources/modules/vlc/vlcplayer.class.php | 2 +- sources/nbproject/licenseheader.txt | 20 + sources/nbproject/project.properties | 31 + sources/nbproject/project.xml | 9 + sources/phpinfo.php | 2 +- sources/play/.htaccess.dist | 1 + sources/play/index.php | 399 +- sources/playlist.php | 50 +- sources/plex/crossdomain.xml | 5 + sources/plex/index.php | 59 +- sources/plex/php.ini | 2 + sources/plex/web/index.html | 1 + sources/plex/web/index.php | 10 +- sources/plex/web/init.php | 6 +- sources/plex/web/login.php | 2 +- sources/preferences.php | 36 +- sources/pvmsg.php | 136 + sources/radio.php | 9 +- sources/random.php | 2 +- sources/register.php | 42 +- sources/rest/.htaccess.dist | 2 +- sources/rest/index.php | 20 +- sources/rss.php | 13 +- sources/scripts/hooks/pre-commit | 48 + sources/search.php | 34 +- sources/server/ajax.server.php | 288 +- sources/server/browse.ajax.php | 44 +- sources/server/catalog.ajax.php | 4 +- sources/server/democratic.ajax.php | 6 +- sources/server/edit.server.php | 128 + sources/server/fs.ajax.php | 267 + sources/server/index.ajax.php | 82 +- sources/server/localplay.ajax.php | 13 +- sources/server/player.ajax.php | 2 +- sources/server/playlist.ajax.php | 35 +- sources/server/random.ajax.php | 2 +- sources/server/refresh_reordered.server.php | 6 +- sources/server/refresh_updated.server.php | 76 - sources/server/search.ajax.php | 104 +- sources/server/show_edit.server.php | 102 - sources/server/song.ajax.php | 6 +- sources/server/sse.server.php | 172 + sources/server/stats.ajax.php | 34 +- sources/server/stream.ajax.php | 68 +- sources/server/tag.ajax.php | 8 +- sources/server/user.ajax.php | 47 + sources/server/xml.server.php | 5 +- sources/share.php | 38 +- sources/shout.php | 18 +- sources/show_get.php | 12 +- sources/smartplaylist.php | 21 +- sources/song.php | 30 +- sources/sql/ampache.sql | 1269 ++- sources/stats.php | 16 +- sources/stream.php | 135 +- sources/templates/.htaccess | 21 +- sources/templates/base.css | 111 +- sources/templates/browse_content.inc.php | 2 +- sources/templates/browse_filters.inc.php | 6 +- sources/templates/cookie_disclaimer.inc.php | 75 + sources/templates/create_web_player.inc.php | 2 +- .../create_web_player_embedded.inc.php | 35 +- sources/templates/error_page.inc.php | 5 +- sources/templates/footer.inc.php | 57 +- sources/templates/header.inc.php | 237 +- sources/templates/install-doped.css | 23 +- sources/templates/install.css | 2 +- sources/templates/install_footer.inc.php | 2 +- sources/templates/install_header.inc.php | 11 +- sources/templates/javascript_refresh.inc.php | 2 +- .../jplayer.midnight.black-iframed.css | 78 +- sources/templates/jplayer.midnight.black.css | 20 +- sources/templates/jquery-editdialog.css | 64 +- sources/templates/jquery-file-upload.css | 120 + sources/templates/jquery-ui.custom.css | 2 +- sources/templates/list_header.inc.php | 106 +- sources/templates/mainframes.inc.php | 67 - sources/templates/print.css | 2 +- sources/templates/rightbar.inc.php | 59 +- sources/templates/show_access_list.inc.php | 15 +- sources/templates/show_account.inc.php | 93 +- sources/templates/show_add_access.inc.php | 33 +- sources/templates/show_add_catalog.inc.php | 31 +- sources/templates/show_add_channel.inc.php | 4 +- sources/templates/show_add_label.inc.php | 77 + .../templates/show_add_live_stream.inc.php | 2 +- sources/templates/show_add_playlist.inc.php | 2 +- sources/templates/show_add_pvmsg.inc.php | 85 + sources/templates/show_add_share.inc.php | 6 +- sources/templates/show_add_shout.inc.php | 4 +- sources/templates/show_add_upload.inc.php | 329 + sources/templates/show_add_user.inc.php | 15 +- sources/templates/show_adds_catalog.inc.php | 6 +- sources/templates/show_album.inc.php | 166 +- .../templates/show_album_group_disks.inc.php | 107 +- sources/templates/show_album_row.inc.php | 83 +- sources/templates/show_albums.inc.php | 72 +- sources/templates/show_alphabet_form.inc.php | 2 +- sources/templates/show_artist.inc.php | 152 +- sources/templates/show_artist_info.inc.php | 34 +- sources/templates/show_artist_row.inc.php | 80 +- sources/templates/show_artists.inc.php | 62 +- ...ow_album_art.inc.php => show_arts.inc.php} | 14 +- sources/templates/show_big_art.inc.php | 7 +- sources/templates/show_box_bottom.inc.php | 2 +- sources/templates/show_box_top.inc.php | 2 +- sources/templates/show_broadcast_row.inc.php | 14 +- sources/templates/show_broadcasts.inc.php | 8 +- .../templates/show_broadcasts_dialog.inc.php | 2 +- sources/templates/show_catalog_row.inc.php | 46 +- sources/templates/show_catalog_types.inc.php | 2 +- sources/templates/show_catalogs.inc.php | 8 +- sources/templates/show_channel_row.inc.php | 36 +- sources/templates/show_channels.inc.php | 10 +- sources/templates/show_clean_catalog.inc.php | 2 +- sources/templates/show_concert_row.inc.php | 8 +- sources/templates/show_concerts.inc.php | 10 +- sources/templates/show_confirmation.inc.php | 2 +- .../templates/show_create_democratic.inc.php | 2 +- sources/templates/show_debug.inc.php | 229 +- sources/templates/show_democratic.inc.php | 23 +- .../show_democratic_playlist.inc.php | 4 +- sources/templates/show_denied.inc.php | 16 +- sources/templates/show_disabled_songs.inc.php | 2 +- sources/templates/show_duplicate.inc.php | 2 +- sources/templates/show_duplicates.inc.php | 2 +- sources/templates/show_dynamic.inc.php | 2 +- sources/templates/show_edit_access.inc.php | 4 +- sources/templates/show_edit_album_row.inc.php | 70 +- .../templates/show_edit_artist_row.inc.php | 42 +- .../templates/show_edit_broadcast_row.inc.php | 14 +- sources/templates/show_edit_catalog.inc.php | 4 +- .../templates/show_edit_channel_row.inc.php | 36 +- sources/templates/show_edit_label_row.inc.php | 60 + sources/templates/show_edit_license.inc.php | 48 + .../show_edit_live_stream_row.inc.php | 14 +- .../templates/show_edit_playlist_row.inc.php | 10 +- ...w.inc.php => show_edit_search_row.inc.php} | 20 +- sources/templates/show_edit_share_row.inc.php | 52 + sources/templates/show_edit_shout.inc.php | 4 +- sources/templates/show_edit_song_row.inc.php | 58 +- sources/templates/show_edit_tag_row.inc.php | 23 +- .../templates/show_edit_tvshow_row.inc.php | 54 + .../show_edit_tvshow_season_row.inc.php | 40 + sources/templates/show_edit_user.inc.php | 29 +- sources/templates/show_edit_video_row.inc.php | 53 + sources/templates/show_export.inc.php | 2 +- sources/templates/show_gather_art.inc.php | 6 +- ..._albumart.inc.php => show_get_art.inc.php} | 42 +- sources/templates/show_graphs.inc.php | 101 + sources/templates/show_highest.inc.php | 2 +- sources/templates/show_html5_player.inc.php | 481 +- .../show_html5_player_headers.inc.php | 510 + .../templates/show_import_playlist.inc.php | 2 +- sources/templates/show_index.inc.php | 36 +- sources/templates/show_install.inc.php | 6 +- .../templates/show_install_account.inc.php | 4 +- sources/templates/show_install_check.inc.php | 2 +- sources/templates/show_install_config.inc.php | 199 +- sources/templates/show_install_lang.inc.php | 2 +- sources/templates/show_ip_history.inc.php | 2 +- sources/templates/show_label.inc.php | 109 + sources/templates/show_label_row.inc.php | 54 + sources/templates/show_labels.inc.php | 76 + sources/templates/show_license_row.inc.php | 34 + sources/templates/show_live_stream.inc.php | 6 +- .../templates/show_live_stream_row.inc.php | 19 +- sources/templates/show_live_streams.inc.php | 10 +- .../show_localplay_add_instance.inc.php | 2 +- .../templates/show_localplay_control.inc.php | 2 +- .../show_localplay_controllers.inc.php | 2 +- .../show_localplay_edit_instance.inc.php | 2 +- .../show_localplay_instances.inc.php | 2 +- .../templates/show_localplay_playlist.inc.php | 2 +- .../templates/show_localplay_status.inc.php | 2 +- sources/templates/show_login_form.inc.php | 144 +- .../templates/show_lostpassword_form.inc.php | 92 +- sources/templates/show_lyrics.inc.php | 19 +- sources/templates/show_mail_users.inc.php | 2 +- .../templates/show_manage_catalogs.inc.php | 35 +- .../templates/show_manage_democratic.inc.php | 4 +- sources/templates/show_manage_license.inc.php | 61 + .../templates/show_manage_shoutbox.inc.php | 67 +- sources/templates/show_missing_album.inc.php | 22 +- sources/templates/show_missing_albums.inc.php | 14 +- sources/templates/show_missing_artist.inc.php | 6 +- .../templates/show_missing_artists.inc.php | 51 + sources/templates/show_newest.inc.php | 2 +- sources/templates/show_now_playing.inc.php | 10 +- .../templates/show_now_playing_row.inc.php | 46 +- .../show_now_playing_similar.inc.php | 4 +- .../show_now_playing_video_row.inc.php | 72 + sources/templates/show_object_rating.inc.php | 2 +- sources/templates/show_object_row.inc.php | 2 +- .../templates/show_object_userflag.inc.php | 2 +- sources/templates/show_objects.inc.php | 2 +- sources/templates/show_partial_clip.inc.php | 24 + .../templates/show_partial_clip_row.inc.php | 23 + sources/templates/show_partial_clips.inc.php | 23 + .../show_partial_edit_clip_row.inc.php | 28 + .../show_partial_edit_movie_row.inc.php | 34 + ...ow_partial_edit_personal_video_row.inc.php | 30 + ...ow_partial_edit_tvshow_episode_row.inc.php | 40 + sources/templates/show_partial_movie.inc.php | 22 + .../templates/show_partial_movie_row.inc.php | 23 + sources/templates/show_partial_movies.inc.php | 24 + .../show_partial_personal_video.inc.php | 24 + .../show_partial_personal_video_row.inc.php | 23 + .../show_partial_personal_videos.inc.php | 23 + .../show_partial_tvshow_episode.inc.php | 26 + .../show_partial_tvshow_episode_row.inc.php | 25 + .../show_partial_tvshow_episodes.inc.php | 25 + sources/templates/show_playlist.inc.php | 44 +- sources/templates/show_playlist_row.inc.php | 53 +- .../templates/show_playlist_song_row.inc.php | 50 +- sources/templates/show_playlist_songs.inc.php | 39 +- sources/templates/show_playlist_title.inc.php | 2 +- sources/templates/show_playlists.inc.php | 25 +- .../templates/show_playlists_dialog.inc.php | 6 +- .../templates/show_playtype_switch.inc.php | 2 +- sources/templates/show_plugins.inc.php | 4 +- sources/templates/show_popular.inc.php | 24 +- .../templates/show_preference_admin.inc.php | 2 +- sources/templates/show_preference_box.inc.php | 4 +- sources/templates/show_preferences.inc.php | 2 +- sources/templates/show_pvmsg.inc.php | 45 + sources/templates/show_pvmsg_row.inc.php | 36 + sources/templates/show_pvmsgs.inc.php | 85 + sources/templates/show_random.inc.php | 4 +- sources/templates/show_random_albums.inc.php | 10 +- sources/templates/show_random_videos.inc.php | 61 + sources/templates/show_recent.inc.php | 2 +- .../templates/show_recently_played.inc.php | 50 +- .../show_recommended_artists.inc.php | 8 +- .../show_registration_confirmation.inc.php | 67 +- sources/templates/show_rules.inc.php | 4 +- .../templates/show_run_add_catalog.inc.php | 2 +- sources/templates/show_search.inc.php | 83 +- sources/templates/show_search_bar.inc.php | 10 +- .../templates/show_search_descriptor.inc.php | 2 +- sources/templates/show_search_form.inc.php | 60 + sources/templates/show_search_options.inc.php | 6 +- ...st_row.inc.php => show_search_row.inc.php} | 28 +- ...itle.inc.php => show_search_title.inc.php} | 2 +- ...laylists.inc.php => show_searches.inc.php} | 23 +- sources/templates/show_share.inc.php | 23 +- sources/templates/show_share_row.inc.php | 41 + .../templates/show_shared_object_row.inc.php | 41 - sources/templates/show_shared_objects.inc.php | 12 +- sources/templates/show_shares.inc.php | 10 +- sources/templates/show_shout_row.inc.php | 14 +- sources/templates/show_shoutbox.inc.php | 5 +- sources/templates/show_smartplaylist.inc.php | 60 - sources/templates/show_song.inc.php | 124 +- .../templates/show_song_preview_row.inc.php | 30 +- sources/templates/show_song_previews.inc.php | 6 +- sources/templates/show_song_row.inc.php | 84 +- sources/templates/show_songs.inc.php | 91 +- sources/templates/show_stats.inc.php | 121 +- sources/templates/show_stats_highest.inc.php | 11 +- sources/templates/show_stats_newest.inc.php | 11 +- sources/templates/show_stats_popular.inc.php | 12 +- sources/templates/show_stats_recent.inc.php | 11 +- sources/templates/show_stats_share.inc.php | 2 +- sources/templates/show_stats_upload.inc.php | 44 + sources/templates/show_stats_userflag.inc.php | 18 +- sources/templates/show_stats_wanted.inc.php | 2 +- sources/templates/show_tag_row.inc.php | 23 + sources/templates/show_tagcloud.inc.php | 39 +- sources/templates/show_test.inc.php | 3 +- sources/templates/show_test_config.inc.php | 3 +- sources/templates/show_test_table.inc.php | 52 +- sources/templates/show_tvshow.inc.php | 106 + sources/templates/show_tvshow_row.inc.php | 64 + sources/templates/show_tvshow_season.inc.php | 101 + .../templates/show_tvshow_season_row.inc.php | 63 + sources/templates/show_tvshow_seasons.inc.php | 89 + sources/templates/show_tvshows.inc.php | 95 + sources/templates/show_update_items.inc.php | 2 +- sources/templates/show_uploads.inc.php | 25 + sources/templates/show_user.inc.php | 170 +- sources/templates/show_user_activate.inc.php | 50 +- .../templates/show_user_preferences.inc.php | 2 +- .../templates/show_user_registration.inc.php | 198 +- sources/templates/show_user_row.inc.php | 57 +- sources/templates/show_userflag.inc.php | 2 +- sources/templates/show_users.inc.php | 42 +- sources/templates/show_verify_catalog.inc.php | 4 +- sources/templates/show_video.inc.php | 142 + sources/templates/show_video_row.inc.php | 80 +- sources/templates/show_videos.inc.php | 58 +- sources/templates/show_wanted.inc.php | 2 +- .../templates/show_wanted_album_row.inc.php | 14 +- sources/templates/show_wanted_albums.inc.php | 8 +- sources/templates/show_web_player.inc.php | 47 +- sources/templates/sidebar.inc.php | 142 +- sources/templates/sidebar.light.inc.php | 33 + sources/templates/sidebar_admin.inc.php | 17 +- sources/templates/sidebar_home.inc.php | 119 +- sources/templates/sidebar_localplay.inc.php | 8 +- sources/templates/sidebar_modules.inc.php | 6 +- sources/templates/sidebar_preferences.inc.php | 4 +- sources/templates/stylesheets.inc.php | 3 +- sources/templates/subnavbar.inc.php | 2 +- sources/test.php | 2 +- sources/themes/classic/images/ajax-loader.gif | Bin 404 -> 0 bytes sources/themes/classic/images/ampache.png | Bin 7595 -> 0 bytes sources/themes/classic/images/bg_login.jpg | Bin 9435 -> 0 bytes sources/themes/classic/images/blank-pixel.gif | Bin 43 -> 0 bytes sources/themes/classic/images/blankalbum.gif | Bin 11378 -> 0 bytes sources/themes/classic/images/blankalbum.jpg | Bin 6943 -> 0 bytes sources/themes/classic/images/bottom.gif | Bin 99 -> 0 bytes sources/themes/classic/images/bottomleft.gif | Bin 609 -> 0 bytes sources/themes/classic/images/bottomright.gif | Bin 673 -> 0 bytes sources/themes/classic/images/left.gif | Bin 68 -> 0 bytes sources/themes/classic/images/right.gif | Bin 98 -> 0 bytes .../themes/classic/images/rightbar_top.jpg | Bin 21471 -> 0 bytes sources/themes/classic/images/sidebar_top.jpg | Bin 1564 -> 0 bytes sources/themes/classic/images/top.gif | Bin 96 -> 0 bytes sources/themes/classic/images/topleft.gif | Bin 343 -> 0 bytes sources/themes/classic/images/topright.gif | Bin 391 -> 0 bytes sources/themes/classic/preview.png | Bin 168326 -> 0 bytes sources/themes/classic/templates/default.css | 959 -- sources/themes/classic/theme.cfg.php | 52 - sources/themes/fresh/ampache.psd | Bin 170232 -> 0 bytes sources/themes/fresh/images/ajax-loader.gif | Bin 723 -> 0 bytes sources/themes/fresh/images/ajax-loader2.gif | Bin 673 -> 0 bytes sources/themes/fresh/images/ampache.png | Bin 19663 -> 0 bytes sources/themes/fresh/images/blank-pixel.gif | Bin 43 -> 0 bytes sources/themes/fresh/images/blankalbum.jpg | Bin 6943 -> 0 bytes .../themes/fresh/images/icons/icon_add.png | Bin 667 -> 0 bytes .../themes/fresh/images/icons/icon_add12.png | Bin 330 -> 0 bytes .../themes/fresh/images/icons/icon_add2.png | Bin 711 -> 0 bytes .../fresh/images/icons/icon_add_user.png | Bin 785 -> 0 bytes .../themes/fresh/images/icons/icon_admin.png | Bin 423 -> 0 bytes .../themes/fresh/images/icons/icon_all.png | Bin 470 -> 0 bytes .../images/icons/icon_batch_download.png | Bin 636 -> 0 bytes .../themes/fresh/images/icons/icon_delete.png | Bin 544 -> 0 bytes .../fresh/images/icons/icon_disable.png | Bin 752 -> 0 bytes .../themes/fresh/images/icons/icon_edit.png | Bin 810 -> 0 bytes .../themes/fresh/images/icons/icon_edit2.png | Bin 891 -> 0 bytes .../themes/fresh/images/icons/icon_enable.png | Bin 724 -> 0 bytes .../themes/fresh/images/icons/icon_feed.png | Bin 688 -> 0 bytes .../themes/fresh/images/icons/icon_home.png | Bin 752 -> 0 bytes .../themes/fresh/images/icons/icon_logout.png | Bin 660 -> 0 bytes .../themes/fresh/images/icons/icon_next.png | Bin 564 -> 0 bytes .../themes/fresh/images/icons/icon_pause.png | Bin 427 -> 0 bytes .../themes/fresh/images/icons/icon_play.png | Bin 470 -> 0 bytes .../fresh/images/icons/icon_playlist_add.png | Bin 607 -> 0 bytes .../themes/fresh/images/icons/icon_plugin.png | Bin 678 -> 0 bytes .../themes/fresh/images/icons/icon_prev.png | Bin 551 -> 0 bytes .../themes/fresh/images/icons/icon_random.png | Bin 877 -> 0 bytes .../themes/fresh/images/icons/icon_stop.png | Bin 411 -> 0 bytes .../themes/fresh/images/icons/icon_view.png | Bin 736 -> 0 bytes .../fresh/images/icons/icon_volumeup.png | Bin 566 -> 0 bytes .../fresh/images/ratings/star_rating.gif | Bin 695 -> 0 bytes .../fresh/images/ratings/star_rating.png | Bin 4055 -> 0 bytes sources/themes/fresh/preview.png | Bin 164941 -> 0 bytes sources/themes/fresh/templates/default.css | 1255 --- sources/themes/fresh/theme.cfg.php | 52 - sources/themes/greysme/images/ajax-loader.gif | Bin 5144 -> 0 bytes sources/themes/greysme/images/ampache.png | Bin 16333 -> 0 bytes .../themes/greysme/images/ampache_back.gif | Bin 111 -> 0 bytes .../themes/greysme/images/ampache_menu.gif | Bin 3349 -> 0 bytes sources/themes/greysme/images/back-box.gif | Bin 1548 -> 0 bytes sources/themes/greysme/images/background.jpg | Bin 18277 -> 0 bytes sources/themes/greysme/images/blankalbum.gif | Bin 6988 -> 0 bytes sources/themes/greysme/images/blankalbum.jpg | Bin 6943 -> 0 bytes sources/themes/greysme/images/box_bottom.png | Bin 984 -> 0 bytes sources/themes/greysme/images/box_top.png | Bin 1098 -> 0 bytes sources/themes/greysme/images/button_back.png | Bin 149 -> 0 bytes .../themes/greysme/images/button_back2.png | Bin 160 -> 0 bytes sources/themes/greysme/images/curl.gif | Bin 607 -> 0 bytes .../themes/greysme/images/icons/icon_add.png | Bin 3619 -> 0 bytes .../greysme/images/icons/icon_admin.png | Bin 959 -> 0 bytes .../themes/greysme/images/icons/icon_all.png | Bin 962 -> 0 bytes .../images/icons/icon_batch_download.png | Bin 3635 -> 0 bytes .../greysme/images/icons/icon_browse.png | Bin 201 -> 0 bytes .../greysme/images/icons/icon_delete.png | Bin 3642 -> 0 bytes .../greysme/images/icons/icon_download.png | Bin 3635 -> 0 bytes .../themes/greysme/images/icons/icon_edit.png | Bin 179 -> 0 bytes .../themes/greysme/images/icons/icon_home.png | Bin 199 -> 0 bytes .../greysme/images/icons/icon_logout.png | Bin 207 -> 0 bytes .../images/icons/icon_playlist_add.png | Bin 960 -> 0 bytes .../greysme/images/icons/icon_random.png | Bin 207 -> 0 bytes .../greysme/images/icons/icon_volumeup.png | Bin 205 -> 0 bytes .../themes/greysme/images/list_back-old.png | Bin 162 -> 0 bytes sources/themes/greysme/images/list_back.png | Bin 377 -> 0 bytes sources/themes/greysme/images/overlay.png | Bin 9519 -> 0 bytes sources/themes/greysme/images/puce.gif | Bin 577 -> 0 bytes sources/themes/greysme/images/punaise-bl.gif | Bin 551 -> 0 bytes sources/themes/greysme/images/punaise-br.gif | Bin 555 -> 0 bytes sources/themes/greysme/images/punaise-tl.gif | Bin 554 -> 0 bytes .../greysme/images/ratings/star_rating.gif | Bin 1317 -> 0 bytes sources/themes/greysme/images/ratings/x.gif | Bin 577 -> 0 bytes .../themes/greysme/images/ratings/x_off.gif | Bin 577 -> 0 bytes sources/themes/greysme/images/sort_off.gif | Bin 58 -> 0 bytes sources/themes/greysme/images/sort_on.gif | Bin 58 -> 0 bytes sources/themes/greysme/preview.png | Bin 141879 -> 0 bytes sources/themes/greysme/templates/default.css | 879 -- sources/themes/greysme/theme.cfg.php | 53 - sources/themes/penguin/images/ajax-loader.gif | Bin 322 -> 0 bytes sources/themes/penguin/images/ampache.png | Bin 3810 -> 0 bytes sources/themes/penguin/images/background.gif | Bin 41 -> 0 bytes sources/themes/penguin/images/bg_login.jpg | Bin 20327 -> 0 bytes sources/themes/penguin/images/blank-pixel.gif | Bin 43 -> 0 bytes sources/themes/penguin/images/blankalbum.gif | Bin 402 -> 0 bytes sources/themes/penguin/images/blankalbum.jpg | Bin 13983 -> 0 bytes sources/themes/penguin/images/bottom.gif | Bin 96 -> 0 bytes sources/themes/penguin/images/bottomright.gif | Bin 94 -> 0 bytes .../themes/penguin/images/icons/icon_add.png | Bin 862 -> 0 bytes .../penguin/images/icons/icon_add_key.png | Bin 228 -> 0 bytes .../penguin/images/icons/icon_add_user.png | Bin 232 -> 0 bytes .../penguin/images/icons/icon_admin.png | Bin 860 -> 0 bytes .../themes/penguin/images/icons/icon_all.png | Bin 856 -> 0 bytes .../images/icons/icon_batch_download.png | Bin 194 -> 0 bytes .../penguin/images/icons/icon_browse.png | Bin 198 -> 0 bytes .../themes/penguin/images/icons/icon_cog.png | Bin 243 -> 0 bytes .../penguin/images/icons/icon_delete.png | Bin 860 -> 0 bytes .../penguin/images/icons/icon_disable.png | Bin 858 -> 0 bytes .../penguin/images/icons/icon_download.png | Bin 852 -> 0 bytes .../themes/penguin/images/icons/icon_edit.png | Bin 858 -> 0 bytes .../penguin/images/icons/icon_enable.png | Bin 1169 -> 0 bytes .../themes/penguin/images/icons/icon_feed.png | Bin 242 -> 0 bytes .../themes/penguin/images/icons/icon_home.png | Bin 866 -> 0 bytes .../themes/penguin/images/icons/icon_link.png | Bin 255 -> 0 bytes .../penguin/images/icons/icon_logout.png | Bin 859 -> 0 bytes .../themes/penguin/images/icons/icon_next.png | Bin 315 -> 0 bytes .../penguin/images/icons/icon_pause.png | Bin 261 -> 0 bytes .../themes/penguin/images/icons/icon_play.png | Bin 255 -> 0 bytes .../images/icons/icon_playlist_add.png | Bin 861 -> 0 bytes .../penguin/images/icons/icon_plugin.png | Bin 859 -> 0 bytes .../penguin/images/icons/icon_preferences.png | Bin 847 -> 0 bytes .../themes/penguin/images/icons/icon_prev.png | Bin 325 -> 0 bytes .../penguin/images/icons/icon_random.png | Bin 238 -> 0 bytes .../images/icons/icon_server_lightning.png | Bin 230 -> 0 bytes .../themes/penguin/images/icons/icon_stop.png | Bin 254 -> 0 bytes .../themes/penguin/images/icons/icon_view.png | Bin 232 -> 0 bytes .../penguin/images/icons/icon_volumedn.png | Bin 231 -> 0 bytes .../penguin/images/icons/icon_volumemute.png | Bin 1163 -> 0 bytes .../penguin/images/icons/icon_volumeup.png | Bin 866 -> 0 bytes .../penguin/images/ratings/star_rating.gif | Bin 111 -> 0 bytes sources/themes/penguin/images/ratings/x.gif | Bin 65 -> 0 bytes .../themes/penguin/images/ratings/x_off.gif | Bin 65 -> 0 bytes .../themes/penguin/images/rightbar_top.jpg | Bin 16440 -> 0 bytes sources/themes/penguin/preview.png | Bin 133414 -> 0 bytes sources/themes/penguin/templates/default.css | 1107 --- sources/themes/penguin/theme.cfg.php | 53 - sources/themes/reborn/images/ampache-blue.png | Bin 0 -> 45726 bytes .../reborn/images/ampache-reborn-blue.png | Bin 0 -> 49118 bytes sources/themes/reborn/images/blankmovie.png | Bin 0 -> 27450 bytes sources/themes/reborn/images/icons.sprite.png | Bin 56821 -> 0 bytes .../themes/reborn/images/icons/icon_mail.png | Bin 0 -> 436 bytes .../reborn/images/icons/icon_play_add.png | Bin 635 -> 593 bytes .../reborn/images/icons/icon_upload.png | Bin 0 -> 623 bytes sources/themes/reborn/images/videoplay.png | Bin 0 -> 6998 bytes sources/themes/reborn/templates/dark.css | 544 ++ .../themes/reborn/templates/dark_preview.png | Bin 0 -> 344545 bytes sources/themes/reborn/templates/default.css | 760 +- .../themes/reborn/templates/icons.sprite.css | 71 - sources/themes/reborn/templates/light.css | 526 ++ sources/themes/reborn/theme.cfg.php | 44 +- sources/tvshow_seasons.php | 68 + sources/tvshows.php | 91 + sources/update.php | 39 +- sources/upload.php | 58 + sources/upnp/MediaServerConnectionManager.xml | 134 + sources/upnp/MediaServerContentDirectory.xml | 208 + sources/upnp/MediaServerServiceDesc.php | 91 + sources/upnp/cm-control-reply.php | 32 + sources/upnp/cm-event-reply.php | 3 + sources/upnp/control-reply.php | 104 + sources/upnp/event-reply.php | 19 + sources/upnp/find.php | 140 + sources/upnp/images/icon120.jpg | Bin 0 -> 9325 bytes sources/upnp/images/icon120.png | Bin 0 -> 17224 bytes sources/upnp/images/icon32.jpg | Bin 0 -> 4827 bytes sources/upnp/images/icon32.png | Bin 0 -> 4867 bytes sources/upnp/images/icon48.jpg | Bin 0 -> 5490 bytes sources/upnp/images/icon48.png | Bin 0 -> 6742 bytes sources/upnp/images/upnp.jpg | Bin 0 -> 20423 bytes sources/upnp/index.php | 55 + sources/upnp/playstatus.php | 49 + sources/upnp/readme.txt | 4 + sources/util.php | 2 +- sources/video.php | 64 + sources/waveform.php | 7 +- sources/web_player.php | 2 +- sources/web_player_embedded.php | 2 +- .../index.php} | 40 +- 1887 files changed, 304088 insertions(+), 59659 deletions(-) create mode 100644 conf/ampache.cfg.php.old create mode 100644 sources/.gitattributes create mode 100644 sources/.gitignore create mode 100644 sources/.maintenance.example create mode 100644 sources/.php_cs create mode 100644 sources/.scrutinizer.yml create mode 100644 sources/.tgitconfig create mode 100644 sources/.travis.yml create mode 100644 sources/.tx/config create mode 100644 sources/CNAME create mode 100644 sources/CONTRIBUTING.md create mode 100644 sources/admin/license.php create mode 100644 sources/arts.php create mode 100644 sources/bin/broadcast.inc create mode 100644 sources/bin/calculate_art_size.inc delete mode 100644 sources/bin/install/.htaccess rename sources/channel/{.htaccess => .htaccess.dist} (57%) delete mode 100644 sources/config/.gitignore create mode 100644 sources/cookie_disclaimer.php create mode 100644 sources/daap/.htaccess create mode 100644 sources/daap/index.php create mode 100644 sources/graph.php rename sources/{themes/reborn => }/images/background.png (100%) create mode 100644 sources/images/fileupload-border.png create mode 100644 sources/images/fileupload-icons.png create mode 100644 sources/images/icon_clean.png create mode 100644 sources/images/icon_file_refresh.png create mode 100644 sources/images/icon_play_next.png create mode 100644 sources/images/icon_replaygain.png create mode 100644 sources/images/icon_share_facebook.png create mode 100644 sources/images/icon_share_googleplus.png create mode 100644 sources/images/icon_share_twitter.png create mode 100644 sources/images/icon_sort.png create mode 100644 sources/labels.php create mode 100644 sources/lib/class/clip.class.php create mode 100644 sources/lib/class/daap_api.class.php create mode 100644 sources/lib/class/graph.class.php create mode 100644 sources/lib/class/label.class.php create mode 100644 sources/lib/class/library_item.interface.php create mode 100644 sources/lib/class/license.class.php rename sources/lib/class/{radio.class.php => live_stream.class.php} (65%) create mode 100644 sources/lib/class/movie.class.php create mode 100644 sources/lib/class/personal_video.class.php create mode 100644 sources/lib/class/playable_item.interface.php create mode 100644 sources/lib/class/privatemsg.class.php create mode 100644 sources/lib/class/tvshow.class.php create mode 100644 sources/lib/class/tvshow_episode.class.php create mode 100644 sources/lib/class/tvshow_season.class.php create mode 100644 sources/lib/class/upload.class.php create mode 100644 sources/lib/class/upnp_api.class.php create mode 100644 sources/lib/class/webdav_auth.class.php create mode 100644 sources/lib/class/webdav_catalog.class.php create mode 100644 sources/lib/class/webdav_directory.class.php create mode 100644 sources/lib/class/webdav_file.class.php create mode 100644 sources/lib/javascript/dynamicpage.js rename sources/{modules/jquery-jplayer => lib/javascript}/jplayer.playlist.ext.js (85%) create mode 100644 sources/locale/pt_BR/messages.mo create mode 100644 sources/locale/pt_BR/messages.po rename sources/modules/{php-openid => }/Auth/OpenID.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/AX.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Association.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/BigMath.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Consumer.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/CryptUtil.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/DatabaseConnection.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/DiffieHellman.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Discover.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/DumbStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Extension.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/FileStore.php (99%) rename sources/modules/{php-openid => }/Auth/OpenID/HMAC.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Interface.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/KVForm.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/MDB2Store.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/MemcachedStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Message.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/MySQLStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Nonce.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/PAPE.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Parse.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/PostgreSQLStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/PredisStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/SQLStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/SQLiteStore.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/SReg.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/Server.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/ServerRequest.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/TrustRoot.php (100%) rename sources/modules/{php-openid => }/Auth/OpenID/URINorm.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/HTTPFetcher.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/Manager.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/Misc.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/ParanoidHTTPFetcher.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/ParseHTML.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/PlainHTTPFetcher.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/XML.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/XRDS.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/XRI.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/XRIRes.php (100%) rename sources/modules/{php-openid => }/Auth/Yadis/Yadis.php (100%) create mode 100644 sources/modules/Beets/Catalog.php create mode 100644 sources/modules/Beets/CliHandler.php create mode 100644 sources/modules/Beets/Handler.php create mode 100644 sources/modules/Beets/JsonHandler.php create mode 100644 sources/modules/Dropbox/AuthBase.php create mode 100644 sources/modules/Dropbox/OAuth1AccessToken.php create mode 100644 sources/modules/Dropbox/OAuth1Upgrader.php create mode 100644 sources/modules/Dropbox/SSLTester.php create mode 100644 sources/modules/Dropbox/certs/trusted-certs.crt create mode 100644 sources/modules/Dropbox/strict.php delete mode 100644 sources/modules/Dropbox/trusted-certs.crt create mode 100644 sources/modules/Moinax/TvDb/Actor.php create mode 100644 sources/modules/Moinax/TvDb/Banner.php create mode 100644 sources/modules/Moinax/TvDb/Client.php create mode 100644 sources/modules/Moinax/TvDb/CurlException.php create mode 100644 sources/modules/Moinax/TvDb/Episode.php create mode 100644 sources/modules/Moinax/TvDb/Exception.php create mode 100644 sources/modules/Moinax/TvDb/Serie.php create mode 100644 sources/modules/Moinax/TvDb/XmlException.php create mode 100644 sources/modules/SabreDAV/autoload.php create mode 100644 sources/modules/SabreDAV/composer/ClassLoader.php create mode 100644 sources/modules/SabreDAV/composer/autoload_classmap.php create mode 100644 sources/modules/SabreDAV/composer/autoload_namespaces.php create mode 100644 sources/modules/SabreDAV/composer/autoload_psr4.php create mode 100644 sources/modules/SabreDAV/composer/autoload_real.php create mode 100644 sources/modules/SabreDAV/composer/installed.json create mode 100644 sources/modules/SabreDAV/sabre/dav/.gitignore create mode 100644 sources/modules/SabreDAV/sabre/dav/.travis.yml create mode 100644 sources/modules/SabreDAV/sabre/dav/CONTRIBUTING.md create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/build.php create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/googlecode_upload.py create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/migrateto17.php create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/migrateto20.php create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/migrateto21.php create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/naturalselection create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/sabredav create mode 100644 sources/modules/SabreDAV/sabre/dav/bin/sabredav.php create mode 100644 sources/modules/SabreDAV/sabre/dav/composer.json create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/SharingSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Backend/SyncSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Calendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarHome.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarObject.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarQueryParser.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarQueryValidator.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarRoot.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/CalendarRootNode.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ICSExportPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ICalendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ICalendarObject.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/IShareableCalendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ISharedCalendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Collection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/ICollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/INode.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/INotificationType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Node.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Notification/Invite.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Notification/InviteReply.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Notification/SystemStatus.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Notifications/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/Collection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/IProxyRead.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/ProxyRead.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Principal/User.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/AllowedSharingModes.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/EmailAddressSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/Invite.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/ScheduleCalendarTransp.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/SupportedCalendarComponentSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/SupportedCalendarData.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Property/SupportedCollationSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/IInbox.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/IOutbox.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/Inbox.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/Outbox.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/ShareableCalendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/SharedCalendar.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/SharingPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CalDAV/UserCalendars.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/AddressBook.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/AddressBookQueryParser.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/AddressBookRoot.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Backend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Backend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Backend/SyncSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Card.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/IAddressBook.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/ICard.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/IDirectory.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Property/SupportedAddressData.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/Property/SupportedCollationSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/UserAddressBooks.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/CardDAV/VCFExportPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/Apache.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Backend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Auth/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/GuessContentType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/PropFindAll.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/favicon.ico create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/sabredav.css create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Browser/assets/sabredav.png create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Client.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Collection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/CorePlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/BadRequest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/Conflict.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/ConflictingLock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/FileNotFound.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/Forbidden.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/InsufficientStorage.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/InvalidResourceType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/LengthRequired.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/Locked.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/NotAuthenticated.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/NotFound.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/NotImplemented.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/PaymentRequired.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/PreconditionFailed.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/ReportNotSupported.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/TooManyMatches.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FS/Directory.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FS/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FS/Node.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FSExt/Directory.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FSExt/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/FSExt/Node.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/ICollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IExtendedCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IFile.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IMoveTarget.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IMultiGet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/INode.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IProperties.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/IQuota.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Backend/FS.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Backend/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Backend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/LockInfo.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Locks/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Mount/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Node.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PartialUpdate/IFile.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PartialUpdate/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropFind.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropPatch.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/GetLastModified.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/Href.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/HrefList.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/IHref.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/LockDiscovery.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/ResourceType.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/Response.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/ResponseList.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/SupportedLock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/SupportedMethodSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Property/SupportedReportSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropertyInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/PropertyStorage/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Server.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/ServerPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/SimpleCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/SimpleFile.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/StringUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Sync/ISyncCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Sync/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Tree.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/URLUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/UUIDUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/Version.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAV/XMLUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Exception/AceConflict.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Exception/NoAbstract.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/IACL.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/IPrincipal.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/IPrincipalCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Plugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Principal.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/PrincipalCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Property/Acl.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Property/AclRestrictions.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Property/CurrentUserPrivilegeSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Property/Principal.php create mode 100644 sources/modules/SabreDAV/sabre/dav/lib/DAVACL/Property/SupportedPrivilegeSet.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarQueryParserTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/Notification/InviteReplyTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/Notification/InviteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/AllowedSharingModesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/EmailAddressSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/InviteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/ScheduleCalendarTranspTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/SupportedCalendarComponentSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/SupportedCalendarDataTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Property/SupportedCollationSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ShareableCalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/TestUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryParserTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/CardTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Property/SupportedAddressDataTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/Property/SupportedCollationSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/TestUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/UserAddressBooksTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/AbstractServer.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ClientMock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ClientTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/CopyTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ExceptionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/FSExt/NodeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/HttpPutTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Issue33Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/FSTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Mock/Collection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Mock/File.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropFindTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropPatchTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/GetLastModifiedTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/HrefListTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/HrefTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/ResourceTypeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/ResponseListTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/ResponseTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/SupportedMethodSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Property/SupportedReportSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerCopyMoveTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/StringUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/TestPlugin.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/TreeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAV/XMLUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Property/ACLRestrictionsTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Property/ACLTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Property/CurrentUserPrivilegeSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Property/PrincipalTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/Property/SupportedPrivilegeSetTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/DAVServerTest.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/HTTP/ResponseMock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/HTTP/SapiMock.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/Sabre/TestUtil.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/bootstrap.php create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/phpcs/ruleset.xml create mode 100644 sources/modules/SabreDAV/sabre/dav/tests/phpunit.xml create mode 100644 sources/modules/SabreDAV/sabre/event/.gitignore create mode 100644 sources/modules/SabreDAV/sabre/event/.travis.yml create mode 100644 sources/modules/SabreDAV/sabre/event/ChangeLog.md create mode 100644 sources/modules/SabreDAV/sabre/event/LICENSE create mode 100644 sources/modules/SabreDAV/sabre/event/README.md create mode 100644 sources/modules/SabreDAV/sabre/event/composer.json create mode 100644 sources/modules/SabreDAV/sabre/event/lib/EventEmitter.php create mode 100644 sources/modules/SabreDAV/sabre/event/lib/EventEmitterInterface.php create mode 100644 sources/modules/SabreDAV/sabre/event/lib/EventEmitterTrait.php create mode 100644 sources/modules/SabreDAV/sabre/event/lib/Promise.php create mode 100644 sources/modules/SabreDAV/sabre/event/lib/PromiseAlreadyResolvedException.php create mode 100644 sources/modules/SabreDAV/sabre/event/lib/Version.php create mode 100644 sources/modules/SabreDAV/sabre/event/phpunit.xml.dist create mode 100644 sources/modules/SabreDAV/sabre/event/tests/ContinueCallbackTest.php create mode 100644 sources/modules/SabreDAV/sabre/event/tests/EventEmitterTest.php create mode 100644 sources/modules/SabreDAV/sabre/event/tests/PromiseTest.php create mode 100644 sources/modules/SabreDAV/sabre/event/tests/benchmark/bench.php create mode 100644 sources/modules/SabreDAV/sabre/http/.gitignore create mode 100644 sources/modules/SabreDAV/sabre/http/.travis.yml create mode 100644 sources/modules/SabreDAV/sabre/http/ChangeLog.md create mode 100644 sources/modules/SabreDAV/sabre/http/LICENSE create mode 100644 sources/modules/SabreDAV/sabre/http/README.md create mode 100644 sources/modules/SabreDAV/sabre/http/bin/.empty create mode 100644 sources/modules/SabreDAV/sabre/http/composer.json create mode 100644 sources/modules/SabreDAV/sabre/http/examples/asyncclient.php create mode 100644 sources/modules/SabreDAV/sabre/http/examples/basicauth.php create mode 100644 sources/modules/SabreDAV/sabre/http/examples/client.php create mode 100644 sources/modules/SabreDAV/sabre/http/examples/reverseproxy.php create mode 100644 sources/modules/SabreDAV/sabre/http/examples/stringify.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Auth/AWS.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Auth/AbstractAuth.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Auth/Basic.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Auth/Digest.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Client.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/ClientException.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/ClientHttpException.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/HttpException.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Message.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/MessageDecoratorTrait.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/MessageInterface.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Request.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/RequestDecorator.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/RequestInterface.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Response.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/ResponseDecorator.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/ResponseInterface.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Sapi.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/URLUtil.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Util.php create mode 100644 sources/modules/SabreDAV/sabre/http/lib/Version.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/Auth/AWSTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/Auth/BasicTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/Auth/DigestTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/ClientTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/MessageDecoratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/MessageTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/RequestDecoratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/RequestTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/ResponseDecoratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/ResponseTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/SapiTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/URLUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/HTTP/UtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/bootstrap.php create mode 100644 sources/modules/SabreDAV/sabre/http/tests/phpcs/ruleset.xml create mode 100644 sources/modules/SabreDAV/sabre/http/tests/phpunit.xml create mode 100644 sources/modules/SabreDAV/sabre/vobject/.gitignore create mode 100644 sources/modules/SabreDAV/sabre/vobject/.travis.yml create mode 100644 sources/modules/SabreDAV/sabre/vobject/ChangeLog.md create mode 100644 sources/modules/SabreDAV/sabre/vobject/LICENSE create mode 100644 sources/modules/SabreDAV/sabre/vobject/README.md create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/bench.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/fetch_windows_zones.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/generate_vcards create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/generateicalendardata.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/rrulebench.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/bin/vobject create mode 100644 sources/modules/SabreDAV/sabre/vobject/composer.json create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Cli.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/Available.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VAlarm.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VAvailability.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VCalendar.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VCard.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VEvent.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VFreeBusy.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VJournal.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VTimeZone.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Component/VTodo.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/DateTimeParser.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Document.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ElementList.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/EofException.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/FreeBusyGenerator.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ITip/Broker.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ITip/ITipException.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ITip/Message.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Node.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Parameter.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/ParseException.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Parser/Json.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Parser/MimeDir.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Parser/Parser.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Binary.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Boolean.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/FlatText.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Float.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/CalAddress.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/Date.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/DateTime.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/Duration.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/Period.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/ICalendar/Recur.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Integer.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Text.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Time.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Unknown.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/Uri.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/UtcOffset.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/VCard/Date.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/VCard/DateAndOrTime.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/VCard/DateTime.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/VCard/LanguageTag.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Property/VCard/TimeStamp.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Reader.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Recur/EventIterator.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Recur/NoInstancesException.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Recur/RDateIterator.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Recur/RRuleIterator.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/RecurrenceIterator.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Splitter/ICalendar.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Splitter/SplitterInterface.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Splitter/VCard.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/StringUtil.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/TimeZoneUtil.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/UUIDUtil.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/VCardConverter.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/Version.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/timezonedata/exchangezones.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/timezonedata/lotuszones.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/timezonedata/php-bc.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/timezonedata/php-workaround.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/lib/timezonedata/windowszones.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/AttachIssueTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/CliTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VAlarmTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VCalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VCardTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VJournalTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Component/VTodoTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ComponentTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/DateTimeParserTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/DocumentTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ElementListTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/EmClientTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/EmptyParameterTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/EmptyValueIssueTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerTester.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/EvolutionTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ITip/MessageTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue153Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue26Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue40Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue64Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Issue96Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/JCalTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/JCardTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/LineFoldingIssueTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ParameterTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Parser/JsonTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Parser/MimeDirTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/BinaryTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/BooleanTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/CompoundTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/FloatTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/TextTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/PropertyTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/ReaderTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/SlashRTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/Splitter/VCardTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/StringUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/TestCase.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/TimeZoneUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/UUIDUtilTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/VCard21Test.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/VCardConverterTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/VersionTest.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/issue153.vcf create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/VObject/issue64.vcf create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/bootstrap.php create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/phpcs/ruleset.xml create mode 100644 sources/modules/SabreDAV/sabre/vobject/tests/phpunit.xml create mode 100644 sources/modules/Tmdb/Api/AbstractApi.php create mode 100644 sources/modules/Tmdb/Api/Account.php create mode 100644 sources/modules/Tmdb/Api/ApiInterface.php create mode 100644 sources/modules/Tmdb/Api/Authentication.php create mode 100644 sources/modules/Tmdb/Api/Certifications.php create mode 100644 sources/modules/Tmdb/Api/Changes.php create mode 100644 sources/modules/Tmdb/Api/Collections.php create mode 100644 sources/modules/Tmdb/Api/Companies.php create mode 100644 sources/modules/Tmdb/Api/Configuration.php create mode 100644 sources/modules/Tmdb/Api/Credits.php create mode 100644 sources/modules/Tmdb/Api/Discover.php create mode 100644 sources/modules/Tmdb/Api/Find.php create mode 100644 sources/modules/Tmdb/Api/Genres.php create mode 100644 sources/modules/Tmdb/Api/GuestSession.php create mode 100644 sources/modules/Tmdb/Api/Jobs.php create mode 100644 sources/modules/Tmdb/Api/Keywords.php create mode 100644 sources/modules/Tmdb/Api/Lists.php create mode 100644 sources/modules/Tmdb/Api/Movies.php create mode 100644 sources/modules/Tmdb/Api/Networks.php create mode 100644 sources/modules/Tmdb/Api/People.php create mode 100644 sources/modules/Tmdb/Api/Reviews.php create mode 100644 sources/modules/Tmdb/Api/Search.php create mode 100644 sources/modules/Tmdb/Api/Timezones.php create mode 100644 sources/modules/Tmdb/Api/Tv.php create mode 100644 sources/modules/Tmdb/Api/TvEpisode.php create mode 100644 sources/modules/Tmdb/Api/TvSeason.php create mode 100644 sources/modules/Tmdb/ApiToken.php create mode 100644 sources/modules/Tmdb/Client.php create mode 100644 sources/modules/Tmdb/Common/ObjectHydrator.php create mode 100644 sources/modules/Tmdb/Exception/InvalidArgumentException.php create mode 100644 sources/modules/Tmdb/Exception/MissingArgumentException.php create mode 100644 sources/modules/Tmdb/Exception/MissingSessionTokenException.php create mode 100644 sources/modules/Tmdb/Exception/NotImplementedException.php create mode 100644 sources/modules/Tmdb/Exception/RuntimeException.php create mode 100644 sources/modules/Tmdb/Exception/TmdbApiException.php create mode 100644 sources/modules/Tmdb/Exception/UnauthorizedRequestTokenException.php create mode 100644 sources/modules/Tmdb/Factory/AbstractFactory.php create mode 100644 sources/modules/Tmdb/Factory/AccountFactory.php create mode 100644 sources/modules/Tmdb/Factory/AuthenticationFactory.php create mode 100644 sources/modules/Tmdb/Factory/CertificationFactory.php create mode 100644 sources/modules/Tmdb/Factory/ChangesFactory.php create mode 100644 sources/modules/Tmdb/Factory/CollectionFactory.php create mode 100644 sources/modules/Tmdb/Factory/Common/ChangeFactory.php create mode 100644 sources/modules/Tmdb/Factory/Common/GenericCollectionFactory.php create mode 100644 sources/modules/Tmdb/Factory/Common/VideoFactory.php create mode 100644 sources/modules/Tmdb/Factory/CompanyFactory.php create mode 100644 sources/modules/Tmdb/Factory/ConfigurationFactory.php create mode 100644 sources/modules/Tmdb/Factory/CreditsFactory.php create mode 100644 sources/modules/Tmdb/Factory/FindFactory.php create mode 100644 sources/modules/Tmdb/Factory/GenreFactory.php create mode 100644 sources/modules/Tmdb/Factory/GuestSessionFactory.php create mode 100644 sources/modules/Tmdb/Factory/ImageFactory.php create mode 100644 sources/modules/Tmdb/Factory/JobsFactory.php create mode 100644 sources/modules/Tmdb/Factory/KeywordFactory.php create mode 100644 sources/modules/Tmdb/Factory/ListFactory.php create mode 100644 sources/modules/Tmdb/Factory/Lists/ListItemFactory.php create mode 100644 sources/modules/Tmdb/Factory/Movie/AlternativeTitleFactory.php create mode 100644 sources/modules/Tmdb/Factory/Movie/ListItemFactory.php create mode 100644 sources/modules/Tmdb/Factory/MovieFactory.php create mode 100644 sources/modules/Tmdb/Factory/NetworkFactory.php create mode 100644 sources/modules/Tmdb/Factory/People/CastFactory.php create mode 100644 sources/modules/Tmdb/Factory/People/CrewFactory.php create mode 100644 sources/modules/Tmdb/Factory/PeopleFactory.php create mode 100644 sources/modules/Tmdb/Factory/ReviewFactory.php create mode 100644 sources/modules/Tmdb/Factory/TimezoneFactory.php create mode 100644 sources/modules/Tmdb/Factory/TvEpisodeFactory.php create mode 100644 sources/modules/Tmdb/Factory/TvFactory.php create mode 100644 sources/modules/Tmdb/Factory/TvSeasonFactory.php create mode 100644 sources/modules/Tmdb/GuestSessionToken.php create mode 100644 sources/modules/Tmdb/Helper/ImageHelper.php create mode 100644 sources/modules/Tmdb/HttpClient/HttpClient.php create mode 100644 sources/modules/Tmdb/HttpClient/HttpClientInterface.php create mode 100644 sources/modules/Tmdb/HttpClient/Plugin/AcceptJsonHeaderPlugin.php create mode 100644 sources/modules/Tmdb/HttpClient/Plugin/AdultFilterPlugin.php create mode 100644 sources/modules/Tmdb/HttpClient/Plugin/ApiTokenPlugin.php create mode 100644 sources/modules/Tmdb/HttpClient/Plugin/LanguageFilterPlugin.php create mode 100644 sources/modules/Tmdb/HttpClient/Plugin/SessionTokenPlugin.php create mode 100644 sources/modules/Tmdb/Model/AbstractModel.php create mode 100644 sources/modules/Tmdb/Model/Account.php create mode 100644 sources/modules/Tmdb/Model/Account/ListItem.php create mode 100644 sources/modules/Tmdb/Model/Certification.php create mode 100644 sources/modules/Tmdb/Model/Certification/CountryCertification.php create mode 100644 sources/modules/Tmdb/Model/Change.php create mode 100644 sources/modules/Tmdb/Model/Collection.php create mode 100644 sources/modules/Tmdb/Model/Collection/Changes.php create mode 100644 sources/modules/Tmdb/Model/Collection/CreditsCollection.php create mode 100644 sources/modules/Tmdb/Model/Collection/CreditsCollection/CombinedCredits.php create mode 100644 sources/modules/Tmdb/Model/Collection/CreditsCollection/MovieCredits.php create mode 100644 sources/modules/Tmdb/Model/Collection/CreditsCollection/TvCredits.php create mode 100644 sources/modules/Tmdb/Model/Collection/Genres.php create mode 100644 sources/modules/Tmdb/Model/Collection/Images.php create mode 100644 sources/modules/Tmdb/Model/Collection/Jobs.php create mode 100644 sources/modules/Tmdb/Model/Collection/Keywords.php create mode 100644 sources/modules/Tmdb/Model/Collection/People.php create mode 100644 sources/modules/Tmdb/Model/Collection/People/Cast.php create mode 100644 sources/modules/Tmdb/Model/Collection/People/Crew.php create mode 100644 sources/modules/Tmdb/Model/Collection/People/PersonInterface.php create mode 100644 sources/modules/Tmdb/Model/Collection/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Collection/QueryParametersCollection.php create mode 100644 sources/modules/Tmdb/Model/Collection/ResultCollection.php create mode 100644 sources/modules/Tmdb/Model/Collection/Timezones.php create mode 100644 sources/modules/Tmdb/Model/Collection/Videos.php create mode 100644 sources/modules/Tmdb/Model/Common/AbstractTrailer.php create mode 100644 sources/modules/Tmdb/Model/Common/Change.php create mode 100644 sources/modules/Tmdb/Model/Common/Change/Item.php create mode 100644 sources/modules/Tmdb/Model/Common/Country.php create mode 100644 sources/modules/Tmdb/Model/Common/ExternalIds.php create mode 100644 sources/modules/Tmdb/Model/Common/GenericCollection.php create mode 100644 sources/modules/Tmdb/Model/Common/QueryParameter/Adult.php create mode 100644 sources/modules/Tmdb/Model/Common/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Common/QueryParameter/Language.php create mode 100644 sources/modules/Tmdb/Model/Common/QueryParameter/QueryParameterInterface.php create mode 100644 sources/modules/Tmdb/Model/Common/QueryParameter/Type/CollectionToCommaSeperatedString.php create mode 100644 sources/modules/Tmdb/Model/Common/SpokenLanguage.php create mode 100644 sources/modules/Tmdb/Model/Common/Trailer/Youtube.php create mode 100644 sources/modules/Tmdb/Model/Common/Translation.php create mode 100644 sources/modules/Tmdb/Model/Common/Video.php create mode 100644 sources/modules/Tmdb/Model/Common/Video/Youtube.php create mode 100644 sources/modules/Tmdb/Model/Company.php create mode 100644 sources/modules/Tmdb/Model/Configuration.php create mode 100644 sources/modules/Tmdb/Model/Credits.php create mode 100644 sources/modules/Tmdb/Model/Credits/Media.php create mode 100644 sources/modules/Tmdb/Model/Filter/AdultFilter.php create mode 100644 sources/modules/Tmdb/Model/Filter/CountryFilter.php create mode 100644 sources/modules/Tmdb/Model/Filter/ImageFilter.php create mode 100644 sources/modules/Tmdb/Model/Filter/LanguageFilter.php create mode 100644 sources/modules/Tmdb/Model/Find.php create mode 100644 sources/modules/Tmdb/Model/Genre.php create mode 100644 sources/modules/Tmdb/Model/Image.php create mode 100644 sources/modules/Tmdb/Model/Image/BackdropImage.php create mode 100644 sources/modules/Tmdb/Model/Image/LogoImage.php create mode 100644 sources/modules/Tmdb/Model/Image/PosterImage.php create mode 100644 sources/modules/Tmdb/Model/Image/ProfileImage.php create mode 100644 sources/modules/Tmdb/Model/Image/StillImage.php create mode 100644 sources/modules/Tmdb/Model/Job.php create mode 100644 sources/modules/Tmdb/Model/Keyword.php create mode 100644 sources/modules/Tmdb/Model/Lists.php create mode 100644 sources/modules/Tmdb/Model/Lists/ItemStatus.php create mode 100644 sources/modules/Tmdb/Model/Lists/ListItem.php create mode 100644 sources/modules/Tmdb/Model/Lists/Result.php create mode 100644 sources/modules/Tmdb/Model/Lists/ResultWithListId.php create mode 100644 sources/modules/Tmdb/Model/Movie.php create mode 100644 sources/modules/Tmdb/Model/Movie/AccountStates.php create mode 100644 sources/modules/Tmdb/Model/Movie/AlternativeTitle.php create mode 100644 sources/modules/Tmdb/Model/Movie/ListItem.php create mode 100644 sources/modules/Tmdb/Model/Movie/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Movie/Rating.php create mode 100644 sources/modules/Tmdb/Model/Movie/Release.php create mode 100644 sources/modules/Tmdb/Model/Network.php create mode 100644 sources/modules/Tmdb/Model/Person.php create mode 100644 sources/modules/Tmdb/Model/Person/AbstractMember.php create mode 100644 sources/modules/Tmdb/Model/Person/CastMember.php create mode 100644 sources/modules/Tmdb/Model/Person/CrewMember.php create mode 100644 sources/modules/Tmdb/Model/Person/MovieCredit.php create mode 100644 sources/modules/Tmdb/Model/Person/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Query/ChangesQuery.php create mode 100644 sources/modules/Tmdb/Model/Query/Discover/DiscoverMoviesQuery.php create mode 100644 sources/modules/Tmdb/Model/Query/Discover/DiscoverTvQuery.php create mode 100644 sources/modules/Tmdb/Model/Query/FindQuery.php create mode 100644 sources/modules/Tmdb/Model/Review.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/CollectionSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/CompanySearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/KeywordSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/ListSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/MovieSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/PersonSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Search/SearchQuery/TvSearchQuery.php create mode 100644 sources/modules/Tmdb/Model/Timezone/CountryTimezone.php create mode 100644 sources/modules/Tmdb/Model/Tv.php create mode 100644 sources/modules/Tmdb/Model/Tv/Episode.php create mode 100644 sources/modules/Tmdb/Model/Tv/Episode/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Tv/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Model/Tv/Season.php create mode 100644 sources/modules/Tmdb/Model/Tv/Season/QueryParameter/AppendToResponse.php create mode 100644 sources/modules/Tmdb/Repository/AbstractRepository.php create mode 100644 sources/modules/Tmdb/Repository/AccountRepository.php create mode 100644 sources/modules/Tmdb/Repository/AuthenticationRepository.php create mode 100644 sources/modules/Tmdb/Repository/CertificationRepository.php create mode 100644 sources/modules/Tmdb/Repository/ChangesRepository.php create mode 100644 sources/modules/Tmdb/Repository/CollectionRepository.php create mode 100644 sources/modules/Tmdb/Repository/CompanyRepository.php create mode 100644 sources/modules/Tmdb/Repository/ConfigurationRepository.php create mode 100644 sources/modules/Tmdb/Repository/CreditsRepository.php create mode 100644 sources/modules/Tmdb/Repository/DiscoverRepository.php create mode 100644 sources/modules/Tmdb/Repository/FindRepository.php create mode 100644 sources/modules/Tmdb/Repository/GenreRepository.php create mode 100644 sources/modules/Tmdb/Repository/GuestSessionRepository.php create mode 100644 sources/modules/Tmdb/Repository/JobsRepository.php create mode 100644 sources/modules/Tmdb/Repository/KeywordRepository.php create mode 100644 sources/modules/Tmdb/Repository/ListRepository.php create mode 100644 sources/modules/Tmdb/Repository/MovieRepository.php create mode 100644 sources/modules/Tmdb/Repository/NetworkRepository.php create mode 100644 sources/modules/Tmdb/Repository/PeopleRepository.php create mode 100644 sources/modules/Tmdb/Repository/ReviewRepository.php create mode 100644 sources/modules/Tmdb/Repository/SearchRepository.php create mode 100644 sources/modules/Tmdb/Repository/TimezoneRepository.php create mode 100644 sources/modules/Tmdb/Repository/TvEpisodeRepository.php create mode 100644 sources/modules/Tmdb/Repository/TvRepository.php create mode 100644 sources/modules/Tmdb/Repository/TvSeasonRepository.php create mode 100644 sources/modules/Tmdb/RequestToken.php create mode 100644 sources/modules/Tmdb/SessionToken.php create mode 100644 sources/modules/ZipStream/Exception.php create mode 100644 sources/modules/ZipStream/Exception/FileNotFoundException.php create mode 100644 sources/modules/ZipStream/Exception/FileNotReadableException.php create mode 100644 sources/modules/ZipStream/Exception/InvalidOptionException.php create mode 100644 sources/modules/ZipStream/ZipStream.php delete mode 100644 sources/modules/archive/archive.lib.php create mode 100644 sources/modules/aurora.js/aac.js create mode 100644 sources/modules/aurora.js/aac.js.map create mode 100644 sources/modules/aurora.js/alac.js create mode 100644 sources/modules/aurora.js/alac.js.map create mode 100644 sources/modules/aurora.js/aurora.js create mode 100644 sources/modules/aurora.js/aurora.js.map create mode 100644 sources/modules/aurora.js/flac.js create mode 100644 sources/modules/aurora.js/flac.js.map create mode 100644 sources/modules/aurora.js/mp3.js create mode 100644 sources/modules/aurora.js/mp3.js.map create mode 100644 sources/modules/aurora.js/ogg.js create mode 100644 sources/modules/aurora.js/opus.js create mode 100644 sources/modules/aurora.js/vorbis.js create mode 100644 sources/modules/captcha/FreeMono.ttf delete mode 100644 sources/modules/captcha/MyUnderwood.ttf delete mode 100644 sources/modules/captcha/MyUnderwood.txt create mode 100644 sources/modules/catalog/beets.catalog.php create mode 100644 sources/modules/catalog/beetsremote.catalog.php delete mode 100644 sources/modules/infotools/lastfm.class.php rename sources/modules/{jquery => jquery-cookie}/jquery.cookie.js (100%) create mode 100644 sources/modules/jquery-datetimepicker/jquery.datetimepicker.css create mode 100644 sources/modules/jquery-datetimepicker/jquery.datetimepicker.js create mode 100644 sources/modules/jquery-file-upload/jquery.fileupload.js create mode 100644 sources/modules/jquery-file-upload/jquery.iframe-transport.js rename sources/modules/{jquery-jplayer => jquery-jplayer-midnight-black}/jplayer.midnight.black.interface.png (100%) rename sources/modules/{jquery-jplayer => jquery-jplayer-midnight-black}/jplayer.midnight.black.playlist.png (100%) rename sources/modules/{jquery-jplayer => jquery-jplayer-midnight-black}/jplayer.midnight.black.png (100%) rename sources/modules/{jquery-jplayer => jquery-jplayer-midnight-black}/jplayer.midnight.black.seeking.gif (100%) rename sources/modules/{jquery-jplayer => jquery-jplayer-midnight-black}/jplayer.midnight.black.video.play.png (100%) delete mode 100644 sources/modules/jquery-jplayer/Jplayer.swf create mode 100644 sources/modules/jquery-jplayer/add-on/jplayer.playlist.min.js delete mode 100644 sources/modules/jquery-jplayer/jplayer.playlist.min.js create mode 100644 sources/modules/jquery-jplayer/jquery.jplayer.swf create mode 100644 sources/modules/jquery-knob/jquery.knob.js rename sources/modules/{jquery => jquery-qrcode}/jquery.qrcode.min.js (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/animated-overlay.gif (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/animated-overlay.gif (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_flat_0_aaaaaa_40x100.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_flat_75_ffffff_40x100.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_glass_55_fbf9ee_1x400.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_glass_65_ffffff_1x400.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_glass_75_dadada_1x400.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_glass_75_e6e6e6_1x400.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_glass_95_fef1ec_1x400.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-bg_highlight-soft_75_cccccc_1x100.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-icons_222222_256x240.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-icons_2e83ff_256x240.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-icons_454545_256x240.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-icons_888888_256x240.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/images/ui-icons_cd0a0a_256x240.png (100%) rename sources/modules/{jquery-ui => jquery-ui-ampache}/jquery-ui.min.css (100%) create mode 100644 sources/modules/jstree/jstree.min.js create mode 100644 sources/modules/jstree/themes/default/32px.png create mode 100644 sources/modules/jstree/themes/default/40px.png create mode 100644 sources/modules/jstree/themes/default/style.css create mode 100644 sources/modules/jstree/themes/default/style.min.css create mode 100644 sources/modules/jstree/themes/default/throbber.gif create mode 100644 sources/modules/localplay/upnp.controller.php rename sources/modules/{twitter/twitteroauth => oauth}/OAuth.php (88%) create mode 100644 sources/modules/pChart/fonts/Bedizen.ttf create mode 100644 sources/modules/pChart/fonts/Forgotte.ttf create mode 100644 sources/modules/pChart/fonts/GeosansLight.ttf create mode 100644 sources/modules/pChart/fonts/MankSans.ttf create mode 100644 sources/modules/pChart/fonts/Silkscreen.ttf create mode 100644 sources/modules/pChart/fonts/advent_light.ttf create mode 100644 sources/modules/pChart/fonts/calibri.ttf create mode 100644 sources/modules/pChart/fonts/pf_arma_five.ttf create mode 100644 sources/modules/pChart/fonts/verdana.ttf create mode 100644 sources/modules/pChart/pBarcode128.class.php create mode 100644 sources/modules/pChart/pBarcode39.class.php create mode 100644 sources/modules/pChart/pBubble.class.php create mode 100644 sources/modules/pChart/pCache.class.php create mode 100644 sources/modules/pChart/pData.class.php create mode 100644 sources/modules/pChart/pDraw.class.php create mode 100644 sources/modules/pChart/pImage.class.php create mode 100644 sources/modules/pChart/pIndicator.class.php create mode 100644 sources/modules/pChart/pPie.class.php create mode 100644 sources/modules/pChart/pRadar.class.php create mode 100644 sources/modules/pChart/pScatter.class.php create mode 100644 sources/modules/pChart/pSplit.class.php create mode 100644 sources/modules/pChart/pSpring.class.php create mode 100644 sources/modules/pChart/pStock.class.php create mode 100644 sources/modules/pChart/pSurface.class.php delete mode 100644 sources/modules/phpmailer/docs.ini delete mode 100644 sources/modules/phpmailer/docs/use_gmail.txt create mode 100644 sources/modules/phpmailer/extras/README.md delete mode 100644 sources/modules/phpmailer/extras/class.html2text.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-am.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-az.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-be.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-bg.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-el.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-gl.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-hr.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-id.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-ka.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-ko.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-lv.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-ms.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-pt.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-sl.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-sr.php create mode 100644 sources/modules/phpmailer/language/phpmailer.lang-vi.php create mode 100644 sources/modules/plugins/7Digital.plugin.php create mode 100644 sources/modules/plugins/Amazon.plugin.php create mode 100644 sources/modules/plugins/CatalogFavorites.plugin.php create mode 100644 sources/modules/plugins/Facebook.plugin.php create mode 100644 sources/modules/plugins/Flattr.plugin.php create mode 100644 sources/modules/plugins/GoogleAnalytics.plugin.php create mode 100644 sources/modules/plugins/GoogleMaps.plugin.php create mode 100644 sources/modules/plugins/GooglePlus.plugin.php create mode 100644 sources/modules/plugins/Omdb.plugin.php create mode 100644 sources/modules/plugins/Paypal.plugin.php create mode 100644 sources/modules/plugins/Piwik.plugin.php create mode 100644 sources/modules/plugins/RSSView.plugin.php create mode 100644 sources/modules/plugins/ShoutHome.plugin.php create mode 100644 sources/modules/plugins/StreamBandwidth.plugin.php create mode 100644 sources/modules/plugins/StreamHits.plugin.php create mode 100644 sources/modules/plugins/StreamTime.plugin.php create mode 100644 sources/modules/plugins/TheAudioDb.plugin.php create mode 100644 sources/modules/plugins/Tmdb.plugin.php create mode 100644 sources/modules/plugins/Tvdb.plugin.php create mode 100644 sources/modules/plugins/Twitter.plugin.php delete mode 100644 sources/modules/twitter/LICENSE delete mode 100644 sources/modules/twitter/README delete mode 100644 sources/modules/twitter/Twitter-Icon.png delete mode 100644 sources/modules/twitter/twitter.sql delete mode 100644 sources/modules/twitter/twitter_login.php delete mode 100644 sources/modules/twitter/twitter_update.php delete mode 100644 sources/modules/twitter/twitter_works.php delete mode 100644 sources/modules/twitter/twitteroauth/twitteroauth.php create mode 100644 sources/modules/upnp/upnpdevice.php create mode 100644 sources/modules/upnp/upnpplayer.class.php create mode 100644 sources/modules/upnp/upnpplaylist.php create mode 100644 sources/nbproject/licenseheader.txt create mode 100644 sources/nbproject/project.properties create mode 100644 sources/nbproject/project.xml create mode 100644 sources/plex/crossdomain.xml create mode 100644 sources/plex/php.ini create mode 100644 sources/plex/web/index.html create mode 100644 sources/pvmsg.php create mode 100755 sources/scripts/hooks/pre-commit create mode 100644 sources/server/edit.server.php create mode 100644 sources/server/fs.ajax.php delete mode 100644 sources/server/refresh_updated.server.php delete mode 100644 sources/server/show_edit.server.php create mode 100644 sources/server/sse.server.php create mode 100644 sources/server/user.ajax.php create mode 100644 sources/templates/cookie_disclaimer.inc.php create mode 100644 sources/templates/jquery-file-upload.css delete mode 100644 sources/templates/mainframes.inc.php create mode 100644 sources/templates/show_add_label.inc.php create mode 100644 sources/templates/show_add_pvmsg.inc.php create mode 100644 sources/templates/show_add_upload.inc.php rename sources/templates/{show_album_art.inc.php => show_arts.inc.php} (68%) create mode 100644 sources/templates/show_edit_label_row.inc.php create mode 100644 sources/templates/show_edit_license.inc.php rename sources/templates/{show_edit_smartplaylist_row.inc.php => show_edit_search_row.inc.php} (64%) create mode 100644 sources/templates/show_edit_share_row.inc.php create mode 100644 sources/templates/show_edit_tvshow_row.inc.php create mode 100644 sources/templates/show_edit_tvshow_season_row.inc.php create mode 100644 sources/templates/show_edit_video_row.inc.php rename sources/templates/{show_get_albumart.inc.php => show_get_art.inc.php} (57%) create mode 100644 sources/templates/show_graphs.inc.php create mode 100644 sources/templates/show_html5_player_headers.inc.php create mode 100644 sources/templates/show_label.inc.php create mode 100644 sources/templates/show_label_row.inc.php create mode 100644 sources/templates/show_labels.inc.php create mode 100644 sources/templates/show_license_row.inc.php create mode 100644 sources/templates/show_manage_license.inc.php create mode 100644 sources/templates/show_missing_artists.inc.php create mode 100644 sources/templates/show_now_playing_video_row.inc.php create mode 100644 sources/templates/show_partial_clip.inc.php create mode 100644 sources/templates/show_partial_clip_row.inc.php create mode 100644 sources/templates/show_partial_clips.inc.php create mode 100644 sources/templates/show_partial_edit_clip_row.inc.php create mode 100644 sources/templates/show_partial_edit_movie_row.inc.php create mode 100644 sources/templates/show_partial_edit_personal_video_row.inc.php create mode 100644 sources/templates/show_partial_edit_tvshow_episode_row.inc.php create mode 100644 sources/templates/show_partial_movie.inc.php create mode 100644 sources/templates/show_partial_movie_row.inc.php create mode 100644 sources/templates/show_partial_movies.inc.php create mode 100644 sources/templates/show_partial_personal_video.inc.php create mode 100644 sources/templates/show_partial_personal_video_row.inc.php create mode 100644 sources/templates/show_partial_personal_videos.inc.php create mode 100644 sources/templates/show_partial_tvshow_episode.inc.php create mode 100644 sources/templates/show_partial_tvshow_episode_row.inc.php create mode 100644 sources/templates/show_partial_tvshow_episodes.inc.php create mode 100644 sources/templates/show_pvmsg.inc.php create mode 100644 sources/templates/show_pvmsg_row.inc.php create mode 100644 sources/templates/show_pvmsgs.inc.php create mode 100644 sources/templates/show_random_videos.inc.php create mode 100644 sources/templates/show_search_form.inc.php rename sources/templates/{show_smartplaylist_row.inc.php => show_search_row.inc.php} (51%) rename sources/templates/{show_smartplaylist_title.inc.php => show_search_title.inc.php} (95%) rename sources/templates/{show_smartplaylists.inc.php => show_searches.inc.php} (79%) create mode 100644 sources/templates/show_share_row.inc.php delete mode 100644 sources/templates/show_shared_object_row.inc.php delete mode 100644 sources/templates/show_smartplaylist.inc.php create mode 100644 sources/templates/show_stats_upload.inc.php create mode 100644 sources/templates/show_tag_row.inc.php create mode 100644 sources/templates/show_tvshow.inc.php create mode 100644 sources/templates/show_tvshow_row.inc.php create mode 100644 sources/templates/show_tvshow_season.inc.php create mode 100644 sources/templates/show_tvshow_season_row.inc.php create mode 100644 sources/templates/show_tvshow_seasons.inc.php create mode 100644 sources/templates/show_tvshows.inc.php create mode 100644 sources/templates/show_uploads.inc.php create mode 100644 sources/templates/show_video.inc.php create mode 100644 sources/templates/sidebar.light.inc.php delete mode 100644 sources/themes/classic/images/ajax-loader.gif delete mode 100644 sources/themes/classic/images/ampache.png delete mode 100644 sources/themes/classic/images/bg_login.jpg delete mode 100644 sources/themes/classic/images/blank-pixel.gif delete mode 100644 sources/themes/classic/images/blankalbum.gif delete mode 100644 sources/themes/classic/images/blankalbum.jpg delete mode 100644 sources/themes/classic/images/bottom.gif delete mode 100644 sources/themes/classic/images/bottomleft.gif delete mode 100644 sources/themes/classic/images/bottomright.gif delete mode 100644 sources/themes/classic/images/left.gif delete mode 100644 sources/themes/classic/images/right.gif delete mode 100644 sources/themes/classic/images/rightbar_top.jpg delete mode 100644 sources/themes/classic/images/sidebar_top.jpg delete mode 100644 sources/themes/classic/images/top.gif delete mode 100644 sources/themes/classic/images/topleft.gif delete mode 100644 sources/themes/classic/images/topright.gif delete mode 100644 sources/themes/classic/preview.png delete mode 100644 sources/themes/classic/templates/default.css delete mode 100644 sources/themes/classic/theme.cfg.php delete mode 100644 sources/themes/fresh/ampache.psd delete mode 100644 sources/themes/fresh/images/ajax-loader.gif delete mode 100644 sources/themes/fresh/images/ajax-loader2.gif delete mode 100644 sources/themes/fresh/images/ampache.png delete mode 100644 sources/themes/fresh/images/blank-pixel.gif delete mode 100644 sources/themes/fresh/images/blankalbum.jpg delete mode 100755 sources/themes/fresh/images/icons/icon_add.png delete mode 100755 sources/themes/fresh/images/icons/icon_add12.png delete mode 100755 sources/themes/fresh/images/icons/icon_add2.png delete mode 100755 sources/themes/fresh/images/icons/icon_add_user.png delete mode 100755 sources/themes/fresh/images/icons/icon_admin.png delete mode 100755 sources/themes/fresh/images/icons/icon_all.png delete mode 100755 sources/themes/fresh/images/icons/icon_batch_download.png delete mode 100755 sources/themes/fresh/images/icons/icon_delete.png delete mode 100755 sources/themes/fresh/images/icons/icon_disable.png delete mode 100755 sources/themes/fresh/images/icons/icon_edit.png delete mode 100755 sources/themes/fresh/images/icons/icon_edit2.png delete mode 100755 sources/themes/fresh/images/icons/icon_enable.png delete mode 100755 sources/themes/fresh/images/icons/icon_feed.png delete mode 100755 sources/themes/fresh/images/icons/icon_home.png delete mode 100755 sources/themes/fresh/images/icons/icon_logout.png delete mode 100755 sources/themes/fresh/images/icons/icon_next.png delete mode 100755 sources/themes/fresh/images/icons/icon_pause.png delete mode 100755 sources/themes/fresh/images/icons/icon_play.png delete mode 100755 sources/themes/fresh/images/icons/icon_playlist_add.png delete mode 100755 sources/themes/fresh/images/icons/icon_plugin.png delete mode 100755 sources/themes/fresh/images/icons/icon_prev.png delete mode 100755 sources/themes/fresh/images/icons/icon_random.png delete mode 100755 sources/themes/fresh/images/icons/icon_stop.png delete mode 100755 sources/themes/fresh/images/icons/icon_view.png delete mode 100755 sources/themes/fresh/images/icons/icon_volumeup.png delete mode 100644 sources/themes/fresh/images/ratings/star_rating.gif delete mode 100644 sources/themes/fresh/images/ratings/star_rating.png delete mode 100644 sources/themes/fresh/preview.png delete mode 100644 sources/themes/fresh/templates/default.css delete mode 100644 sources/themes/fresh/theme.cfg.php delete mode 100644 sources/themes/greysme/images/ajax-loader.gif delete mode 100644 sources/themes/greysme/images/ampache.png delete mode 100644 sources/themes/greysme/images/ampache_back.gif delete mode 100644 sources/themes/greysme/images/ampache_menu.gif delete mode 100644 sources/themes/greysme/images/back-box.gif delete mode 100644 sources/themes/greysme/images/background.jpg delete mode 100644 sources/themes/greysme/images/blankalbum.gif delete mode 100644 sources/themes/greysme/images/blankalbum.jpg delete mode 100644 sources/themes/greysme/images/box_bottom.png delete mode 100644 sources/themes/greysme/images/box_top.png delete mode 100644 sources/themes/greysme/images/button_back.png delete mode 100644 sources/themes/greysme/images/button_back2.png delete mode 100644 sources/themes/greysme/images/curl.gif delete mode 100644 sources/themes/greysme/images/icons/icon_add.png delete mode 100644 sources/themes/greysme/images/icons/icon_admin.png delete mode 100644 sources/themes/greysme/images/icons/icon_all.png delete mode 100644 sources/themes/greysme/images/icons/icon_batch_download.png delete mode 100644 sources/themes/greysme/images/icons/icon_browse.png delete mode 100644 sources/themes/greysme/images/icons/icon_delete.png delete mode 100644 sources/themes/greysme/images/icons/icon_download.png delete mode 100644 sources/themes/greysme/images/icons/icon_edit.png delete mode 100644 sources/themes/greysme/images/icons/icon_home.png delete mode 100644 sources/themes/greysme/images/icons/icon_logout.png delete mode 100644 sources/themes/greysme/images/icons/icon_playlist_add.png delete mode 100644 sources/themes/greysme/images/icons/icon_random.png delete mode 100644 sources/themes/greysme/images/icons/icon_volumeup.png delete mode 100644 sources/themes/greysme/images/list_back-old.png delete mode 100644 sources/themes/greysme/images/list_back.png delete mode 100644 sources/themes/greysme/images/overlay.png delete mode 100644 sources/themes/greysme/images/puce.gif delete mode 100644 sources/themes/greysme/images/punaise-bl.gif delete mode 100644 sources/themes/greysme/images/punaise-br.gif delete mode 100644 sources/themes/greysme/images/punaise-tl.gif delete mode 100644 sources/themes/greysme/images/ratings/star_rating.gif delete mode 100644 sources/themes/greysme/images/ratings/x.gif delete mode 100644 sources/themes/greysme/images/ratings/x_off.gif delete mode 100644 sources/themes/greysme/images/sort_off.gif delete mode 100644 sources/themes/greysme/images/sort_on.gif delete mode 100644 sources/themes/greysme/preview.png delete mode 100644 sources/themes/greysme/templates/default.css delete mode 100644 sources/themes/greysme/theme.cfg.php delete mode 100644 sources/themes/penguin/images/ajax-loader.gif delete mode 100644 sources/themes/penguin/images/ampache.png delete mode 100644 sources/themes/penguin/images/background.gif delete mode 100644 sources/themes/penguin/images/bg_login.jpg delete mode 100644 sources/themes/penguin/images/blank-pixel.gif delete mode 100644 sources/themes/penguin/images/blankalbum.gif delete mode 100644 sources/themes/penguin/images/blankalbum.jpg delete mode 100644 sources/themes/penguin/images/bottom.gif delete mode 100644 sources/themes/penguin/images/bottomright.gif delete mode 100644 sources/themes/penguin/images/icons/icon_add.png delete mode 100644 sources/themes/penguin/images/icons/icon_add_key.png delete mode 100644 sources/themes/penguin/images/icons/icon_add_user.png delete mode 100644 sources/themes/penguin/images/icons/icon_admin.png delete mode 100644 sources/themes/penguin/images/icons/icon_all.png delete mode 100644 sources/themes/penguin/images/icons/icon_batch_download.png delete mode 100644 sources/themes/penguin/images/icons/icon_browse.png delete mode 100644 sources/themes/penguin/images/icons/icon_cog.png delete mode 100644 sources/themes/penguin/images/icons/icon_delete.png delete mode 100644 sources/themes/penguin/images/icons/icon_disable.png delete mode 100644 sources/themes/penguin/images/icons/icon_download.png delete mode 100644 sources/themes/penguin/images/icons/icon_edit.png delete mode 100644 sources/themes/penguin/images/icons/icon_enable.png delete mode 100644 sources/themes/penguin/images/icons/icon_feed.png delete mode 100644 sources/themes/penguin/images/icons/icon_home.png delete mode 100644 sources/themes/penguin/images/icons/icon_link.png delete mode 100644 sources/themes/penguin/images/icons/icon_logout.png delete mode 100644 sources/themes/penguin/images/icons/icon_next.png delete mode 100644 sources/themes/penguin/images/icons/icon_pause.png delete mode 100644 sources/themes/penguin/images/icons/icon_play.png delete mode 100644 sources/themes/penguin/images/icons/icon_playlist_add.png delete mode 100644 sources/themes/penguin/images/icons/icon_plugin.png delete mode 100644 sources/themes/penguin/images/icons/icon_preferences.png delete mode 100644 sources/themes/penguin/images/icons/icon_prev.png delete mode 100644 sources/themes/penguin/images/icons/icon_random.png delete mode 100644 sources/themes/penguin/images/icons/icon_server_lightning.png delete mode 100644 sources/themes/penguin/images/icons/icon_stop.png delete mode 100644 sources/themes/penguin/images/icons/icon_view.png delete mode 100644 sources/themes/penguin/images/icons/icon_volumedn.png delete mode 100644 sources/themes/penguin/images/icons/icon_volumemute.png delete mode 100644 sources/themes/penguin/images/icons/icon_volumeup.png delete mode 100644 sources/themes/penguin/images/ratings/star_rating.gif delete mode 100644 sources/themes/penguin/images/ratings/x.gif delete mode 100644 sources/themes/penguin/images/ratings/x_off.gif delete mode 100644 sources/themes/penguin/images/rightbar_top.jpg delete mode 100644 sources/themes/penguin/preview.png delete mode 100644 sources/themes/penguin/templates/default.css delete mode 100644 sources/themes/penguin/theme.cfg.php create mode 100644 sources/themes/reborn/images/ampache-blue.png create mode 100644 sources/themes/reborn/images/ampache-reborn-blue.png create mode 100644 sources/themes/reborn/images/blankmovie.png delete mode 100644 sources/themes/reborn/images/icons.sprite.png create mode 100644 sources/themes/reborn/images/icons/icon_mail.png create mode 100644 sources/themes/reborn/images/icons/icon_upload.png create mode 100644 sources/themes/reborn/images/videoplay.png create mode 100644 sources/themes/reborn/templates/dark.css create mode 100644 sources/themes/reborn/templates/dark_preview.png delete mode 100644 sources/themes/reborn/templates/icons.sprite.css create mode 100644 sources/themes/reborn/templates/light.css create mode 100644 sources/tvshow_seasons.php create mode 100644 sources/tvshows.php create mode 100644 sources/upload.php create mode 100644 sources/upnp/MediaServerConnectionManager.xml create mode 100644 sources/upnp/MediaServerContentDirectory.xml create mode 100644 sources/upnp/MediaServerServiceDesc.php create mode 100644 sources/upnp/cm-control-reply.php create mode 100644 sources/upnp/cm-event-reply.php create mode 100644 sources/upnp/control-reply.php create mode 100644 sources/upnp/event-reply.php create mode 100644 sources/upnp/find.php create mode 100644 sources/upnp/images/icon120.jpg create mode 100644 sources/upnp/images/icon120.png create mode 100644 sources/upnp/images/icon32.jpg create mode 100644 sources/upnp/images/icon32.png create mode 100644 sources/upnp/images/icon48.jpg create mode 100644 sources/upnp/images/icon48.png create mode 100644 sources/upnp/images/upnp.jpg create mode 100644 sources/upnp/index.php create mode 100644 sources/upnp/playstatus.php create mode 100644 sources/upnp/readme.txt create mode 100644 sources/video.php rename sources/{server/show_edit_playlist.server.php => webdav/index.php} (59%) diff --git a/conf/admin.sql b/conf/admin.sql index 322f09e..0e2a2a9 100644 --- a/conf/admin.sql +++ b/conf/admin.sql @@ -1 +1,107 @@ INSERT INTO user(id,username,fullname,access) VALUES("", "yunoadmin", "yunoadmin", "100"); + +UPDATE `user_preference` SET `value` = 'fr_FR' WHERE `preference` = 31 ; + +SET @lastid = LAST_INSERT_ID(); + +INSERT INTO `user_preference` (`user`, `preference`, `value`) VALUES +(@lastid, 1, '1'), +(@lastid, 4, '10'), +(@lastid, 19, '32'), +(@lastid, 22, 'Ampache :: For the love of Music'), +(@lastid, 23, '0'), +(@lastid, 24, '0'), +(@lastid, 25, '80'), +(@lastid, 41, 'mpd'), +(@lastid, 29, 'web_player'), +(@lastid, 31, 'fr_FR'), +(@lastid, 32, 'm3u'), +(@lastid, 33, 'reborn'), +(@lastid, 34, '27'), +(@lastid, 35, '27'), +(@lastid, 36, '27'), +(@lastid, 51, '50'), +(@lastid, 40, '100'), +(@lastid, 44, '1'), +(@lastid, 45, '1'), +(@lastid, 46, '1'), +(@lastid, 47, '7'), +(@lastid, 49, '1'), +(@lastid, 52, '8192'), +(@lastid, 53, 'default'), +(@lastid, 55, 'default'), +(@lastid, 57, ''), +(@lastid, 69, '0'), +(@lastid, 70, '0'), +(@lastid, 71, '0'), +(@lastid, 72, '0'), +(@lastid, 73, ''), +(@lastid, 74, ''), +(@lastid, 75, ''), +(@lastid, 76, ''), +(@lastid, 77, ''), +(@lastid, 78, ''), +(@lastid, 114, '1'), +(@lastid, 113, '0'), +(@lastid, 112, '-1'), +(@lastid, 111, '1'), +(@lastid, 110, '0'), +(@lastid, 109, '0'), +(@lastid, 108, '0'), +(@lastid, 107, '0'), +(@lastid, 106, '0'), +(@lastid, 105, '0'), +(@lastid, 104, '0'), +(@lastid, 103, '7'), +(@lastid, 102, '0'), +(@lastid, 101, '0'), +(@lastid, 100, '1'), +(@lastid, 99, '0'), +(@lastid, 95, '1'), +(@lastid, 94, '0'), +(@lastid, 93, '1'), +(@lastid, 92, '1'), +(@lastid, 91, '1'), +(@lastid, 90, '1'), +(@lastid, 89, '1'), +(@lastid, 88, '1'), +(@lastid, 87, '0'), +(@lastid, 86, '1'), +(@lastid, 85, '1'), +(@lastid, 84, '0'), +(@lastid, 83, '0'), +(@lastid, 79, '50'), +(@lastid, 80, '50'), +(@lastid, 82, '1'), +(@lastid, 81, '1'), +(@lastid, 115, '0'), +(@lastid, 116, ''), +(@lastid, 117, '1'), +(@lastid, 118, '0'), +(@lastid, 119, ''), +(@lastid, 120, '0'), +(@lastid, 121, '1'), +(@lastid, 122, '1'), +(@lastid, 123, '1'), +(@lastid, 124, '0'), +(@lastid, 125, '1'), +(@lastid, 126, '1'), +(@lastid, 127, '1'), +(@lastid, 128, '1'), +(@lastid, 129, ''), +(@lastid, 130, 'album,ep,live,single'), +(@lastid, 131, '1'), +(@lastid, 132, '10'), +(@lastid, 133, '0'), +(@lastid, 134, '1'), +(@lastid, 135, '1'), +(@lastid, 136, ''), +(@lastid, 137, ''), +(@lastid, 138, ''), +(@lastid, 139, '0'), +(@lastid, 140, '0'), +(@lastid, 96, ''), +(@lastid, 97, ''), +(@lastid, 98, ''); + + diff --git a/conf/ampache.cfg.php b/conf/ampache.cfg.php index ee30470..a851dad 100644 --- a/conf/ampache.cfg.php +++ b/conf/ampache.cfg.php @@ -4,41 +4,47 @@ ;################### ; This value is used to detect quickly -; if this config file is up to date +; if this config file is up to date ; this is compared against a value hard-coded ; into the init script -config_version = 16 +config_version = 29 ;################### ; Path Vars # ;################### -; The http host of your server. +; The public http host of your server. ; If not set, retrieved automatically from client request. ; This setting is required for WebSocket server ; DEFAULT: "" http_host = "DOMAINTOCHANGE" -; The path to your ampache install -; Do not put a trailing / on this path +; The public path to your ampache install +; Do not put a trailing / on this path ; For example if your site is located at http://localhost ; than you do not need to enter anything for the web_path -; if it is located at http://localhost/music you need to +; if it is located at http://localhost/music you need to ; set web_path to /music ; DEFAULT: "" web_path = "PATHTOCHANGE" +; The local http url of your server. +; If not set, retrieved automatically from server information. +; DEFAULT: "" +;local_web_path = "http://localhost/ampache" + ;############################## ; Session and Login Variables # ;############################## ; Hostname of your database +; For socket authentication, set the path to socket file (e.g. /var/run/mysqld/mysqld.sock) ; DEFAULT: localhost database_hostname = "localhost" ; Port to use when connecting to your database ; DEFAULT: none -;database_port = 3306 +database_port = "" ; Name of your ampache database ; DEFAULT: ampache @@ -50,44 +56,48 @@ database_username = "yunouser" ; Password for your ampache database, this can not be blank ; this is a 'forced' security precaution, the default value -; will not work +; will not work (except if using socket authentication) ; DEFAULT: "" database_password = "yunopass" +; Cryptographic secret +; This MUST BE changed with your own secret key. Ampache-specific, just pick any random string you want. +secret_key = "abcdefghijklmnoprqstuvwyz0123456" + ; Length that a session will last expressed in seconds. Default is -; one hour. +; one hour. ; DEFAULT: 3600 -session_length = "3600" +session_length = 3600 ; Length that the session for a single streaming instance will last -; the default is two hours. With some clients, and long songs this can +; the default is two hours. With some clients, and long songs this can ; cause playback to stop, increase this value if you experience that ; DEFAULT: 7200 -stream_length = "7200" +stream_length = 7200 -; This length defines how long a 'remember me' session and cookie will -; last, the default is 7200, same as length. It is up to the administrator -; of the box to increase this, for reference 86400 = 1 day -; 604800 = 1 week and 2419200 = 1 month -; DEFAULT: 86400 -remember_length = "86400" +; This length defines how long a 'remember me' session and cookie will +; last, the default is 86400, same as length. It is up to the administrator +; of the box to increase this, for reference 86400 = 1 day, +; 604800 = 1 week, and 2419200 = 1 month +; DEFAULT: 604800 +remember_length = 604800 ; Name of the Session/Cookie that will sent to the browser ; default should be fine ; DEFAULT: ampache -session_name = "ampache" +session_name = ampache ; Lifetime of the Cookie, 0 == Forever (until browser close) , otherwise in terms of seconds -; If you want cookies to last past a browser close set this to a value in seconds. +; If you want cookies to last past a browser close set this to a value in seconds. ; DEFAULT: 0 -session_cookielife = "0" +session_cookielife = 0 ; Is the cookie a "secure" cookie? This should only be set to 1 (true) if you are -; running a secure site (HTTPS). +; running a secure site (HTTPS). ; DEFAULT: 0 -session_cookiesecure = "1" +session_cookiesecure = 0 -; Auth Methods +; Auth Methods ; This defines which auth methods Auth will attempt to use and in which order. ; If auto_create isn't enabled the user must exist locally. ; DEFAULT: mysql @@ -108,7 +118,7 @@ auth_methods = "http,mysql" ;auth_password_save = "false" ; Logout redirection target -; Defaults to our own login.php, but we can override it here if, for instance, +; Defaults to our own login.php, but we can override it here if, for instance, ; we want to redirect to an SSO provider instead. logout_redirect = "https://DOMAINTOCHANGE/yunohost/sso/?action=logout" @@ -120,15 +130,15 @@ logout_redirect = "https://DOMAINTOCHANGE/yunohost/sso/?action=logout" ; This defines which file types Ampache will attempt to catalog ; You can specify any file extension you want in here separating them ; with a | -; DEFAULT: mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx|ra|ape|shn|wv -catalog_file_pattern = "mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx|ra|ape|shn|wv" +; DEFAULT: mp3|mpc|m4p|m4a|aac|ogg|oga|wav|aif|aiff|rm|wma|asf|flac|opus|spx|ra|ape|shn|wv +catalog_file_pattern = "mp3|mpc|m4p|m4a|aac|ogg|oga|wav|aif|aiff|rm|wma|asf|flac|opus|spx|ra|ape|shn|wv" ; Video Pattern ; This defines which video file types Ampache will attempt to catalog ; You can specify any file extension you want in here seperating them with ; a | but ampache may not be able to parse them -; DEAFULT: avi|mpg|flv|m4v|webm -catalog_video_pattern = "avi|mpg|flv|m4v|webm" +; DEAFULT: avi|mpg|mpeg|flv|m4v|mp4|webm|mkv|wmv|ogv|mov|divx|m2ts +catalog_video_pattern = "avi|mpg|mpeg|flv|m4v|mp4|webm|mkv|wmv|ogv|mov|divx|m2ts" ; Playlist Pattern ; This defines which playlist types Ampache will attempt to catalog @@ -149,19 +159,19 @@ catalog_prefix_pattern = "The|An|A|Die|Das|Ein|Eine|Les|Le|La" ; DEFAULT: false ;catalog_disable = "false" -; Use Access List +; Use Access List ; Toggle this on if you want ampache to pay attention to the access list -; and only allow streaming/downloading/api-rpc from known hosts api-rpc +; and only allow streaming/downloading/api-rpc from known hosts api-rpc ; will not work without this on. -; NOTE: Default Behavior is DENY FROM ALL +; NOTE: Default Behavior is DENY FROM ALL ; DEFAULT: true -access_control = "true" +access_control = "true" ; Require Session ; If this is set to true ampache will make sure that the URL passed when ; attempting to retrieve a song contains a valid Session ID This prevents ; others from guessing URL's. This setting is ignored if you have use_auth -; disabled. +; disabled. ; DEFAULT: true require_session = "true" @@ -188,62 +198,63 @@ require_localnet_session = "true" ; Track User IPs ; If this is enabled Ampache will log the IP of every completed login -; it will store user,ip,time at one row per login. The results are +; it will store user,ip,time at one row per login. The results are ; displayed in Admin --> Users ; DEFAULT: false ;track_user_ip = "false" ; User IP Cardinality ; This defines how many days worth of IP history Ampache will track -; As it is one row per login on high volume sites you will want to -; clear it every now and then. +; As it is one row per login on high volume sites you will want to +; clear it every now and then. ; DEFAULT: 42 days ;user_ip_cardinality = "42" ; Allow Zip Download ; This setting allows/disallows using zlib to zip up an entire ; playlist/album for download. Even if this is turned on you will -; still need to enabled downloading for the specific user you +; still need to enabled downloading for the specific user you ; want to be able to use this function ; DEFAULT: false ;allow_zip_download = "false" -; File Zip Download -; This settings tells Ampache to attempt to save the zip file -; to the filesystem instead of creating it in memory, you must -; also set tmp_dir_path in order for this to work -; DEFAULT: false -;file_zip_download = "false" +Allow Zip Types +; This setting allows/disallows zip download of specific object types +; If empty, all supported object types can be zipped. +; Otherwise, only the given object list can be zipped. +; POSSIBLE VALUES: artist, album, playlist, search, tmp_playlist +; DEFAULT: none +;allow_zip_types = "album" ; File Zip Comment ; This is an optional configuration option that adds a comment ; to your zip files, this only applies if you've got allow_zip_downloads ; DEFAULT: Ampache - Zip Batch Download -;file_zip_comment = "Ampache - Zip Batch Download" +;file_zip_comment = "Ampache - Zip Batch Download" ; Waveform ; This settings tells Ampache to attempt to generate a waveform ; for each song. It requires transcode and encode_args_wav settings. ; You must also set tmp_dir_path in order for this to work ; DEFAULT: false -;waveform = "false" +;waveform = "false" ; Waveform color ; The waveform color. ; DEFAULT: #FF0000 -;waveform_color = "#FF0000" +;waveform_color = "#FF0000" ; Temporary Directory Path -; If File Zip Download or Waveform is enabled this must be set to tell +; If Waveform is enabled this must be set to tell ; Ampache which directory to save the temporary file to. Do not put a ; trailing slash or this will not work. ; DEFAULT: false ;tmp_dir_path = "false" ; This setting throttles a persons downloading to the specified -; bytes per second. This is not a 100% guaranteed function, and +; bytes per second. This is not a 100% guaranteed function, and ; you should really use a server based rate limiter if you want -; to do this correctly. +; to do this correctly. ; DEFAULT: off ; VALUES: any whole number (in bytes per second) ;throttle_download = 10 @@ -261,13 +272,52 @@ getid3_tag_order = "id3v2,id3v1,vorbiscomment,quicktime,matroska,ape,asf,avi,mpe ; DEFAULT: false ;getid3_detect_id3v2_encoding = "false" +; This determines if file metadata should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3 = "false" + +; This determines if album art should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3_art = "false" + +; This determines if catalog manager users can delete medias from disk. +; DEFAULT: false +;delete_from_disk = "false" + ; This determines the order in which metadata sources are used (and in the ; case of plugins, checked) ; POSSIBLE VALUES (builtins): filename and getID3 -; POSSIBLE VALUES (plugins): MusicBrainz, plus any others you've installed. +; POSSIBLE VALUES (plugins): MusicBrainz,TheAudioDb, plus any others you've installed. ; DEFAULT: getID3 filename metadata_order = "getID3,filename" +; This determines the order in which metadata sources are used (and in the +; case of plugins, checked) for video files +; POSSIBLE VALUES (builtins): filename and getID3 +; POSSIBLE VALUES (plugins): Tvdb,Tmdb,Omdb, plus any others you've installed. +; DEFAULT: filename getID3 +metadata_order_video = "filename,getID3" + +; This determines if extended metadata grabbed from external services should be deferred. +; If enabled, extended metadata is retrieved when browsing the library item. +; If disabled, extended metadata is retrieved at catalog update. +; Today, only Artist information (summary, place formed, ...) can be deferred. +; DEFAULT: true +deferred_ext_metadata = "true" + +; Some taggers use delimiters other than \0 for fields +; This list specifies possible delimiters additional to \0 +; This setting takes a regex pattern. +; DEFAULT: // / \ | , ; +additional_genre_delimiters = "[/]{2}|[/|\\\\|\|,|;]" + +; This determines if a preview image should be retrieved from video files +; It requires encode_get_image transcode settings. +; DEFAULT: false +;generate_video_preview = "true" + ; Un comment if don't want ampache to follow symlinks ; DEFAULT: false ;no_symlinks = "false" @@ -284,9 +334,9 @@ use_auth = "true" ; If use_auth is set to false then this option is used ; to determine the permission level of the 'default' users ; default is administrator. This setting only takes affect -; if use_auth if false +; if use_auth is false ; POSSIBLE VALUES: user, admin, manager, guest -; DEFAULT: admin +; DEFAULT: guest default_auth_level = "user" ; 5 Star Ratings @@ -295,8 +345,8 @@ default_auth_level = "user" ; DEFAULT: true ratings = "true" -; User flags -; This allows user flags for almost any object in ampache +; User flags/favorites +; This allows user flags for almost any object in ampache as favorite ; POSSIBLE VALUES: false true ; DEFAULT: true userflags = "true" @@ -310,14 +360,14 @@ directplay = "true" ; Sociable ; This turns on / off all of the "social" features of ampache ; default is on, but if you don't care and just want music -; turn this off to disable all social features. +; turn this off to disable all social features. ; DEFAULT: true sociable = "true" -; Notify -; This turns on / off all Ampache notifications -; DEFAULT: true -notify = "true" +; License +; This turns on / off all licensing features on Ampache +; DEFAULT: false +licensing = "false" ; This options will turn on/off Demo Mode ; If Demo mode is on you can not play songs or update your catalog @@ -347,7 +397,43 @@ memory_cache = "true" ; Especially useful if you have a front and a back image in a folder ; comment out if ampache should search for any jpg,gif or png ; DEFAULT: folder.jpg -;album_art_preferred_filename = "folder.jpg" +;album_art_preferred_filename = "folder.jpg" + +; Album Art Store on Disk +; This defines if arts should be stored on disk instead of database. +; DEFAULT: false +;album_art_store_disk = "false" + +; Local Metadata Directory +; This define a local metadata directory with write access where to store +; heavy data if enabled (album arts, ...) +; DEFAULT: none +;local_metadata_dir = "/metadata" + +; Maximal upload size +; Specify the maximal allowed upload size for images, in bytes. +; DEFAULT: 1048576 +;max_upload_size = 1048576 + +; Album Art Minimum Width +; Specify the minimum width for arts (in pixel). +; DEFAULT: none +;album_art_min_width = 100 + +; Album Art Maximum Width +; Specify the maximum width for arts (in pixel). +; DEFAULT: none +;album_art_max_width = 1024 + +; Album Art Minimum Height +; Specify the minimum height for arts (in pixel). +; DEFAULT: none +;album_art_min_height = 100 + +; Album Art Maximum Height +; Specify the maximum height for arts (in pixel). +; DEFAULT: none +;album_art_max_height = 1024 ; Resize Images * Requires PHP-GD * ; Set this to true if you want Ampache to resize the Album @@ -357,24 +443,23 @@ memory_cache = "true" ; DEFAULT: false ;resize_images = "false" +; Statistical Graphs * Requires PHP-GD * +; Set this to true if you want Ampache to generate statistical +; graphs on usages / users. +; DEFAULT: false +;statistical_graphs = "false" + ; Art Gather Order ; Simply arrange the following in the order you would like ; ampache to search. If you want to disable one of the search ; methods simply leave it out. DB should be left as the first ; method unless you want it to overwrite what's already in the ; database -; POSSIBLE VALUES: db tags folder amazon lastfm musicbrainz google +; POSSIBLE VALUES (builtins): db tags folder lastfm musicbrainz google +; POSSIBLE VALUES (plugins): Amazon,TheAudioDb,Tmdb,Omdb,Flickr ; DEFAULT: db,tags,folder,musicbrainz,lastfm,google art_order = "db,tags,folder,musicbrainz,lastfm,google" -; Amazon Developer Key -; These are needed in order to actually use the amazon album art -; Your public key is your 'Access Key ID' -; Your private key is your 'Secret Access Key' -; DEFAULT: false -;amazon_developer_public_key = "" -;amazon_developer_private_key = "" - ; Recommendations ; Set this to true to enable display of similar artists or albums ; while browsing. Requires Last.FM. @@ -389,14 +474,14 @@ art_order = "db,tags,folder,musicbrainz,lastfm,google" ; Last.FM API Key ; Set this to your Last.FM api key to actually use Last.FM for -; recommendations. -;lastfm_api_key = "" +; recommendations and metadata. +lastfm_api_key = "d5df942424c71b754e54ce1832505ae2" ; Wanted ; Set this to true to enable display missing albums and the ; possibility for users to mark it as wanted. ; DEFAULT: false -;wanted = "false" +wanted = "true" ; Wanted types ; Set the allowed types of wanted releases (album,compilation,single,ep,live,remix,promotion,official) @@ -412,59 +497,50 @@ wanted_types = "album,official" ; EchoNest provides several music services. Currently used for missing song 30 seconds preview. ;echonest_api_key = "" +; Labels +; Use labels to browse artists per label membership. +; DEFAULT: false +;label = "false" + ; Broadcasts ; Allow users to broadcast music. ; This feature requires advanced server configuration, please take a look on the wiki for more information. ; DEFAULT: false ;broadcast = "false" +; Channels +; Set this to true to enable channels and the +; possibility for users to create channels from playlists +; DEFAULT: true +channel = "true" + +; Live Streams +; Set this to true to enable live streams (radio) and the +; possibility for users to add new live streams. +; DEFAULT: true +live_stream = "true" + ; Web Socket address ; Declare the web socket server address ; DEFAULT: determined automatically ;websocket_address = "ws://localhost:8100" -; Amazon base urls -; An array of Amazon sites to search. -; NOTE: This will search each of these sites in turn so don't expect it -; to be lightning fast! -; It is strongly recommended that only one of these is selected at any -; one time -; POSSIBLE VALUES: -; http://webservices.amazon.com -; http://webservices.amazon.co.uk -; http://webservices.amazon.de -; http://webservices.amazon.co.jp -; http://webservices.amazon.fr -; http://webservices.amazon.ca -; Default: http://webservices.amazon.com -;amazon_base_urls = "http://webservices.amazon.com" - -; max_amazon_results_pages -; The maximum number of results pages to pull from EACH amazon site -; NOTE: The art search pages through the results returned by your search -; up to this number of pages. As with the base_urls above, this is going -; to take more time, the more pages you ask it to process. -; Of course a good search will return only a few matches anyway. -; It is strongly recommended that you do _not_ change this value -; DEFAULT: 1 page (10 items) -max_amazon_results_pages = "1" - ; Debug ; If this is enabled Ampache will write debugging information to the log file ; DEFAULT: false -debug = "true" +;debug = "false" ; Debug Level ; This should always be set in conjunction with the ; debug option, it defines how prolific you want the -; debugging in ampache to be. values are 1-5. +; debugging in ampache to be. values are 1-5. ; 1 == Errors only ; 2 == Error + Failures (login attempts etc.) ; 3 == ?? ; 4 == ?? (Profit!) ; 5 == Information (cataloging progress etc.) ; DEFAULT: 5 -debug_level = "5" +debug_level = 5 ; Path to Log File ; This defines where you want ampache to log events to @@ -486,22 +562,28 @@ log_filename = "%name.%Y%m%d.log" ; DEFAULT: UTF-8 site_charset = "UTF-8" -; Locale Charset -; In some cases this has to be different -; in order for XHTML and other things to work -; This is disabled by default, enabled only -; if needed. It's specifically needed for Russian -; so that is the default -; DEFAULT: cp1251 -;lc_charset = cp1251 +; Locale Charset +; Local charset (mainly for file operations) if different +; from site_charset. +; This is disabled by default, enable only if needed +; (for Windows please set lc_charset to ISO8859-1) +; DEFAULT: ISO8859-1 +;lc_charset = "ISO8859-1" ; Refresh Limit -; This defines the default refresh limit in seconds for +; This defines the default refresh limit in seconds for ; pages with dynamic content, such as now playing ; DEFAULT: 60 ; Possible Values: Int > 5 refresh_limit = "60" +; Footer Statistics +; This defines whether statistics (Queries, Cache Hits, Load Time) +; are shown in the page footer. +; DEFAULT: true +; Possible values: true, false +show_footer_statistics = "true" + ;######################################################### ; Custom actions (optional) # ;######################################################### @@ -528,10 +610,10 @@ refresh_limit = "60" ;######################################################### ; LDAP filter string to use (required) -; For OpenLDAP use "uid" +; For OpenLDAP use "uid" ; For Microsoft Active Directory (MAD) use "sAMAccountName" ; DEFAULT: null -; ldap_filter = "sAMAccountName" +;ldap_filter = "(sAMAccountName=%v)" ; LDAP objectclass (required) ; OpanLDAP objectclass = "*" @@ -561,7 +643,7 @@ ldap_url = "localhost" ; MAD ldap_name_field = "displayname" ; DEFAULT: null ;ldap_email_field = "mail" -ldap_name_field = "cn" +ldap_name_field = "cn" ;######################################################### ; OpenID login info (optional) # @@ -608,19 +690,41 @@ auto_create = "true" ; DEFAULT: false ;admin_enable_required = "false" -; This setting will allow all registrants/ldap/http users -; to be auto-approved as a user. By default, they will be +; This setting will allow all registrants/ldap/http users +; to be auto-approved as a user. By default, they will be ; added as a guest and must be promoted by the admin. ; POSSIBLE VALUES: guest, user, admin ; DEFAULT: guest auto_user = "user" ; This will display the user agreement when registering -; For agreement text, edit templates/user_agreement.php +; For agreement text, edit config/registration_agreement.php ; User will need to accept the agreement before they can register ; DEFAULT: false ;user_agreement = "false" +; This disable email confirmation when registering. +; DEFAULT: false +;user_no_email_confirm = "false" + +; This will display the cookie disclaimer (EU Cookie Law) +; DEFAULT: false +cookie_disclaimer = "false" + +; The fields that will be shown on Registration page +; If a user wants to register. +; Username and email fields are forced. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: "fullname,website" +registration_display_fields = "fullname,website" + +; The fields that will be mandatory +; This controls which fields are mandatory for registration. +; Username and email fields are forced mandatory. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: fullname +registration_mandatory_fields = "fullname" + ;######################################################## ; These options control the dynamic downsampling based # ; on current usage # @@ -638,11 +742,11 @@ max_bit_rate = 576 ; New dynamically downsampled streams will be denied if they are forced below ; this value. ; DEFAULT: 8 -min_bit_rate = 48 +;min_bit_rate = 48 ;###################################################### ; These are commands used to transcode non-streaming -; formats to the target file type for streaming. +; formats to the target file type for streaming. ; This can be useful in re-encoding file types that don't stream ; very well, or if your player doesn't support some file types. ; @@ -658,23 +762,64 @@ min_bit_rate = 48 ; (e.g. if you store everything in FLAC, but don't want to ever stream that.) ; transcode_TYPE = {allowed|required|false} ; DEFAULT: false +;;; Audio ;transcode_m4a = allowed transcode_flac = required ;transcode_mpc = required +;transcode_ogg = required +;transcode_oga = required +;transcode_wav = required +;transcode_wma = required +;transcode_aif = required +;transcode_aiff = required +;transcode_ape = required +;transcode_shn = required transcode_mp3 = allowed +;;; Video +;transcode_avi = allowed +;transcode_mkv = allowed +;transcode_mpg = allowed +;transcode_mpeg = allowed +;transcode_m4v = allowed +;transcode_mp4 = allowed +;transcode_mov = allowed +;transcode_wmv = allowed +;transcode_ogv = allowed +;transcode_divx = allowed +;transcode_m2ts = allowed +;transcode_webm = allowed -; Default output format +; Default audio output format ; DEFAULT: none encode_target = mp3 +; Default video output format +; DEFAULT: none +;encode_video_target = webm + ; Override the default output format on a per-type basis ; encode_target_TYPE = TYPE ; DEFAULT: none -; encode_target_flac = ogg +;encode_target_flac = ogg + +; Override the default TYPE transcoding behavior on a per-player basis +; transcode_player_PLAYER_TYPE = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;transcode_player_webplayer_m4a = required +;transcode_player_webplayer_flac = required +;transcode_player_webplayer_mpc = required + +; Override the default output format on a per-player basis +; encode_player_PLAYER_target = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;encode_player_webplayer_target = mp3 +;encode_player_api_target = mp3 ; Allow clients to override transcode settings (output type, bitrate, codec ...) ; DEFAULT: true -transcode_player_customize = "1" +transcode_player_customize = "true" ; Command configuration. Substitutions will be made as follows: ; %FILE% => filename @@ -688,30 +833,45 @@ transcode_player_customize = "1" ; equivalent to the old default, but if you find that necessary you should be ; clever enough to figure out how on your own. ; DEFAULT: none -;transcode_cmd = "ffmpeg -i %FILE%" -transcode_cmd = "ffmpeg -i %FILE%" -;transcode_cmd = "/usr/bin/neatokeen %FILE%" +;transcode_cmd = "ffmpeg" +transcode_cmd = "avconv" +;transcode_cmd = "/usr/bin/neatokeen" + +; Transcode input file argument +transcode_input = "-i %FILE%" ; Specific transcode commands ; It shouldn't be necessary in most cases, but you can override the transcode -; command for specific source formats. It still needs to accept the +; command for specific source formats. It still needs to accept the ; encoding arguments, so the easiest approach is to use your normal command as ; a clearing-house. ; transcode_cmd_TYPE = TRANSCODE_CMD -;transcode_cmd_mid = "timidity -Or -o – %FILE% | ffmpeg -f s16le -i pipe:0" +;transcode_cmd_mid = "timidity -Or -o – %FILE% | ffmpeg -f s16le -i pipe:0" ; Encoding arguments ; For each output format, you should provide the necessary arguments for -; your transcode_cmd. +; your transcode_cmd. ; encode_args_TYPE = TRANSCODE_CMD_ARGS -;encode_args_mp3 = "-vn -b:a %SAMPLE%K -c:a libmp3lame -f mp3 pipe:1" -;encode_args_ogg = "-vn -b:a %SAMPLE%K -c:a libvorbis -f ogg pipe:1" -;encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" -;encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" -encode_args_ogg = "-vn -b:a max\(%SAMPLE%K\,49K\) -acodec libvorbis -vcodec libtheora -f ogg pipe:1" -encode_args_mp3 = "-vn -b:a %SAMPLE%K -acodec libmp3lame -f mp3 pipe:1" -encode_args_ogv = "-vcodec libtheora -acodec libvorbis -ar 44100 -f ogv pipe:1" -encode_args_mp4 = "-profile:0 baseline -frag_duration 2 -ar 44100 -f mp4 pipe:1" +encode_args_mp3 = "-vn -b:a %SAMPLE%K -c:a libmp3lame -f mp3 pipe:1" +encode_args_ogg = "-vn -b:a %SAMPLE%K -c:a libvorbis -f ogg pipe:1" +encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" +encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" +encode_args_flv = "-b:a %SAMPLE%K -ar 44100 -ac 2 -v 0 -f flv -c:v libx264 -preset superfast -threads 0 pipe:1" +encode_args_webm = "-q %QUALITY% -f webm -c:v libvpx -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" +encode_args_ts = "-q %QUALITY% -s %RESOLUTION% -f mpegts -c:v libx264 -c:a libmp3lame -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" + +; Encoding arguments to retrieve an image from a single frame +encode_get_image = "-ss %TIME% -f image2 -vframes 1 pipe:1" + +; Encoding argument to encrust subtitle +encode_srt = "-vf \"subtitles='%SRTFILE%'\"" + +; Encode segment frame argument +encode_ss_frame = "-ss %TIME%" + +; Encode segment duration argument +encode_ss_duration = "-t %DURATION%" + ;###################################################### ; these options allow you to configure your rss-feed @@ -719,7 +879,7 @@ encode_args_mp4 = "-profile:0 baseline -frag_duration 2 -ar 44100 -f mp4 pipe:1" ; song is the information in the feed. can be multiple items. ; use_rss = false (values true | false) ;DEFAULT: use_rss = false -;use_rss = false +;use_rss = "false" ;##################################################### ;############################# @@ -735,7 +895,7 @@ encode_args_mp4 = "-profile:0 baseline -frag_duration 2 -ar 44100 -f mp4 pipe:1" ; If Ampache is behind an https reverse proxy, force use HTTPS protocol. ;Default: false -force_ssl = true +force_ssl = "true" ;############################# ; Mail Settings # @@ -747,7 +907,7 @@ force_ssl = true ;mail_type = "php" ;Mail domain. -;DEFAULT: example.com +;DEFAULT: example.com ;mail_domain = "example.com" ;This will be combined with mail_domain and used as the source address for @@ -794,7 +954,7 @@ force_ssl = true ;Enable SMTP authentication ;DEFAULT: false -;mail_auth = true +;mail_auth = "true" ;SMTP Username ;your mail auth username. diff --git a/conf/ampache.cfg.php.old b/conf/ampache.cfg.php.old new file mode 100644 index 0000000..493b760 --- /dev/null +++ b/conf/ampache.cfg.php.old @@ -0,0 +1,977 @@ +;### +;################### +; General Config # +;################### + +; This value is used to detect quickly +; if this config file is up to date +; this is compared against a value hard-coded +; into the init script +config_version = 29 + +;################### +; Path Vars # +;################### + +; The public http host of your server. +; If not set, retrieved automatically from client request. +; This setting is required for WebSocket server +; DEFAULT: "" +http_host = "DOMAINTOCHANGE" + +; The public path to your ampache install +; Do not put a trailing / on this path +; For example if your site is located at http://localhost +; than you do not need to enter anything for the web_path +; if it is located at http://localhost/music you need to +; set web_path to /music +; DEFAULT: "" +web_path = "PATHTOCHANGE" + +; The local http url of your server. +; If not set, retrieved automatically from server information. +; DEFAULT: "" +;local_web_path = "http://localhost/ampache" + +;############################## +; Session and Login Variables # +;############################## + +; Hostname of your database +; For socket authentication, set the path to socket file (e.g. /var/run/mysqld/mysqld.sock) +; DEFAULT: localhost +database_hostname = "localhost" + +; Port to use when connecting to your database +; DEFAULT: none +;database_port = 3306 + +; Name of your ampache database +; DEFAULT: ampache +database_name = "yunobase" + +; Username for your ampache database +; DEFAULT: "" +database_username = "yunouser" + +; Password for your ampache database, this can not be blank +; this is a 'forced' security precaution, the default value +; will not work (except if using socket authentication) +; DEFAULT: "" +database_password = "yunopass" + +; Cryptographic secret +; This MUST BE changed with your own secret key. Ampache-specific, just pick any random string you want. +secret_key = "abcdefghijklmnoprqstuvwyz0123456" + +; Length that a session will last expressed in seconds. Default is +; one hour. +; DEFAULT: 3600 +session_length = "3600" + +; Length that the session for a single streaming instance will last +; the default is two hours. With some clients, and long songs this can +; cause playback to stop, increase this value if you experience that +; DEFAULT: 7200 +stream_length = "7200" + +; This length defines how long a 'remember me' session and cookie will +; last, the default is 86400, same as length. It is up to the administrator +; of the box to increase this, for reference 86400 = 1 day, +; 604800 = 1 week, and 2419200 = 1 month +; DEFAULT: 604800 +remember_length = "86400" + +; Name of the Session/Cookie that will sent to the browser +; default should be fine +; DEFAULT: ampache +session_name = "ampache" + +; Lifetime of the Cookie, 0 == Forever (until browser close) , otherwise in terms of seconds +; If you want cookies to last past a browser close set this to a value in seconds. +; DEFAULT: 0 +session_cookielife = "0" + +; Is the cookie a "secure" cookie? This should only be set to 1 (true) if you are +; running a secure site (HTTPS). +; DEFAULT: 0 +session_cookiesecure = "1" + +; Auth Methods +; This defines which auth methods Auth will attempt to use and in which order. +; If auto_create isn't enabled the user must exist locally. +; DEFAULT: mysql +; VALUES: mysql,ldap,http,pam,external,openid +auth_methods = "http,mysql" + +; External authentication +; This sets the helper used for external authentication. It should conform to +; the interface used by mod_authnz_external +; DEFAULT: none +;external_authenticator = "/usr/sbin/pwauth" + +; Automatic local password updating +; Determines whether successful authentication against an external source +; will result in an update to the password stored in the database. +; A locally stored password is needed for API access. +; DEFAULT: false +;auth_password_save = "false" + +; Logout redirection target +; Defaults to our own login.php, but we can override it here if, for instance, +; we want to redirect to an SSO provider instead. +logout_redirect = "https://DOMAINTOCHANGE/yunohost/sso/?action=logout" + +;##################### +; Program Settings # +;##################### + +; File Pattern +; This defines which file types Ampache will attempt to catalog +; You can specify any file extension you want in here separating them +; with a | +; DEFAULT: mp3|mpc|m4p|m4a|aac|ogg|oga|wav|aif|aiff|rm|wma|asf|flac|opus|spx|ra|ape|shn|wv +catalog_file_pattern = "mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx|ra|ape|shn|wv" + +; Video Pattern +; This defines which video file types Ampache will attempt to catalog +; You can specify any file extension you want in here seperating them with +; a | but ampache may not be able to parse them +; DEAFULT: avi|mpg|mpeg|flv|m4v|mp4|webm|mkv|wmv|ogv|mov|divx|m2ts +catalog_video_pattern = "avi|mpg|flv|m4v|webm" + +; Playlist Pattern +; This defines which playlist types Ampache will attempt to catalog +; You can specify any file extension you want in here seperating them with +; a | but ampache may not be able to parse them +; DEFAULT: m3u|pls|asx|xspf +catalog_playlist_pattern = "m3u|pls|asx|xspf" + +; Prefix Pattern +; This defines which prefix Ampache will ignore when importing tags from +; your music. You may add any prefix you want seperating them with a | +; DEFAULT: The|An|A|Die|Das|Ein|Eine|Les|Le|La +catalog_prefix_pattern = "The|An|A|Die|Das|Ein|Eine|Les|Le|La" + +; Catalog disable +; This defines if catalog can be disabled without removing database entries +; WARNING: this increase sensibly sql requests and slow down Ampache a lot +; DEFAULT: false +;catalog_disable = "false" + +; Use Access List +; Toggle this on if you want ampache to pay attention to the access list +; and only allow streaming/downloading/api-rpc from known hosts api-rpc +; will not work without this on. +; NOTE: Default Behavior is DENY FROM ALL +; DEFAULT: true +access_control = "true" + +; Require Session +; If this is set to true ampache will make sure that the URL passed when +; attempting to retrieve a song contains a valid Session ID This prevents +; others from guessing URL's. This setting is ignored if you have use_auth +; disabled. +; DEFAULT: true +require_session = "true" + +; Require LocalNet Session +; If this is set to true then ampache will require that a valid session +; is passed even on hosts defined in the Local Network ACL. This setting +; has no effect if access_control is not enabled +; DEFAULT: true +require_localnet_session = "true" + +; Multiple Logins +; Added by Vlet 07/25/07 +; When this setting is enabled a user may only be logged in from a single +; IP address at any one time, this is to prevent sharing of accounts +; DEFAULT: false +;prevent_multiple_logins = "false" + +; Downsample Remote +; If this is set to true and access control is on any users who are not +; coming from a defined 'network' ACL will be automatically downsampled +; regardless of their preferences. Requires access_control to be enabled +; DEFAULT: false +;downsample_remote = "false" + +; Track User IPs +; If this is enabled Ampache will log the IP of every completed login +; it will store user,ip,time at one row per login. The results are +; displayed in Admin --> Users +; DEFAULT: false +;track_user_ip = "false" + +; User IP Cardinality +; This defines how many days worth of IP history Ampache will track +; As it is one row per login on high volume sites you will want to +; clear it every now and then. +; DEFAULT: 42 days +;user_ip_cardinality = "42" + +; Allow Zip Download +; This setting allows/disallows using zlib to zip up an entire +; playlist/album for download. Even if this is turned on you will +; still need to enabled downloading for the specific user you +; want to be able to use this function +; DEFAULT: false +;allow_zip_download = "false" + +Allow Zip Types +; This setting allows/disallows zip download of specific object types +; If empty, all supported object types can be zipped. +; Otherwise, only the given object list can be zipped. +; POSSIBLE VALUES: artist, album, playlist, search, tmp_playlist +; DEFAULT: none +;allow_zip_types = "album" + +; File Zip Comment +; This is an optional configuration option that adds a comment +; to your zip files, this only applies if you've got allow_zip_downloads +; DEFAULT: Ampache - Zip Batch Download +;file_zip_comment = "Ampache - Zip Batch Download" + +; Waveform +; This settings tells Ampache to attempt to generate a waveform +; for each song. It requires transcode and encode_args_wav settings. +; You must also set tmp_dir_path in order for this to work +; DEFAULT: false +;waveform = "false" + +; Waveform color +; The waveform color. +; DEFAULT: #FF0000 +;waveform_color = "#FF0000" + +; Temporary Directory Path +; If Waveform is enabled this must be set to tell +; Ampache which directory to save the temporary file to. Do not put a +; trailing slash or this will not work. +; DEFAULT: false +;tmp_dir_path = "false" + +; This setting throttles a persons downloading to the specified +; bytes per second. This is not a 100% guaranteed function, and +; you should really use a server based rate limiter if you want +; to do this correctly. +; DEFAULT: off +; VALUES: any whole number (in bytes per second) +;throttle_download = 10 + +; This determines the tag order for all cataloged +; music. If none of the listed tags are found then +; ampache will randomly use whatever was found. +; POSSIBLE VALUES: ape asf avi id3v1 id3v2 lyrics3 matroska mpeg quicktime riff +; vorbiscomment +; DEFAULT: id3v2 id3v1 vorbiscomment quicktime matroska ape asf avi mpeg riff +getid3_tag_order = "id3v2,id3v1,vorbiscomment,quicktime,matroska,ape,asf,avi,mpeg,riff" + +; Determines whether we try to autodetect the encoding for id3v2 tags. +; May break valid tags. +; DEFAULT: false +;getid3_detect_id3v2_encoding = "false" + +; This determines if file metadata should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3 = "false" + +; This determines if album art should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3_art = "false" + +; This determines if catalog manager users can delete medias from disk. +; DEFAULT: false +;delete_from_disk = "false" + +; This determines the order in which metadata sources are used (and in the +; case of plugins, checked) +; POSSIBLE VALUES (builtins): filename and getID3 +; POSSIBLE VALUES (plugins): MusicBrainz,TheAudioDb, plus any others you've installed. +; DEFAULT: getID3 filename +metadata_order = "getID3,filename" + +; This determines the order in which metadata sources are used (and in the +; case of plugins, checked) for video files +; POSSIBLE VALUES (builtins): filename and getID3 +; POSSIBLE VALUES (plugins): Tvdb,Tmdb,Omdb, plus any others you've installed. +; DEFAULT: filename getID3 +metadata_order_video = "filename,getID3" + +; This determines if extended metadata grabbed from external services should be deferred. +; If enabled, extended metadata is retrieved when browsing the library item. +; If disabled, extended metadata is retrieved at catalog update. +; Today, only Artist information (summary, place formed, ...) can be deferred. +; DEFAULT: true +deferred_ext_metadata = "true" + +; Some taggers use delimiters other than \0 for fields +; This list specifies possible delimiters additional to \0 +; This setting takes a regex pattern. +; DEFAULT: // / \ | , ; +additional_genre_delimiters = "[/]{2}|[/|\\\\|\|,|;]" + +; This determines if a preview image should be retrieved from video files +; It requires encode_get_image transcode settings. +; DEFAULT: false +;generate_video_preview = "true" + +; Un comment if don't want ampache to follow symlinks +; DEFAULT: false +;no_symlinks = "false" + +; Use auth? +; If this is set to "Yes" ampache will require a valid +; Username and password. If this is set to false then ampache +; will not ask you for a username and password. false is only +; recommended for internal only instances +; DEFAULT true +use_auth = "true" + +; Default Auth Level +; If use_auth is set to false then this option is used +; to determine the permission level of the 'default' users +; default is administrator. This setting only takes affect +; if use_auth is false +; POSSIBLE VALUES: user, admin, manager, guest +; DEFAULT: guest +default_auth_level = "user" + +; 5 Star Ratings +; This allows ratings for almost any object in ampache +; POSSIBLE VALUES: false true +; DEFAULT: true +ratings = "true" + +; User flags/favorites +; This allows user flags for almost any object in ampache as favorite +; POSSIBLE VALUES: false true +; DEFAULT: true +userflags = "true" + +; Direct play +; This allows user to play directly a song or album +; POSSIBLE VALUES: false true +; DEFAULT: true +directplay = "true" + +; Sociable +; This turns on / off all of the "social" features of ampache +; default is on, but if you don't care and just want music +; turn this off to disable all social features. +; DEFAULT: true +sociable = "true" + +; License +; This turns on / off all licensing features on Ampache +; DEFAULT: false +;licensing = "false" + +; This options will turn on/off Demo Mode +; If Demo mode is on you can not play songs or update your catalog +; in other words.. leave this commented out +; DEFAULT: false +;demo_mode = "false" + +; Caching +; This turns the caching mechanisms on or off, due to a large number of +; problems with people with very large catalogs and low memory settings +; this is off by default as it does significantly increase the memory +; requirments on larger catalogs. If you have the memory this can create +; a 2-3x speed improvement. +; DEFAULT: false +memory_cache = "true" + +; Memory Limit +; This defines the "Min" memory limit for PHP if your php.ini +; has a lower value set Ampache will set it up to this. If you +; set it below 16MB getid3() will not work! +; DEFAULT: 32 +;memory_limit = 32 + +; Album Art Preferred Filename +; Specify a filename to look for if you always give the same filename +; i.e. "folder.jpg" Ampache currently only supports jpg/gif and png +; Especially useful if you have a front and a back image in a folder +; comment out if ampache should search for any jpg,gif or png +; DEFAULT: folder.jpg +;album_art_preferred_filename = "folder.jpg" + +; Album Art Store on Disk +; This defines if arts should be stored on disk instead of database. +; DEFAULT: false +;album_art_store_disk = "false" + +; Local Metadata Directory +; This define a local metadata directory with write access where to store +; heavy data if enabled (album arts, ...) +; DEFAULT: none +;local_metadata_dir = "/metadata" + +; Maximal upload size +; Specify the maximal allowed upload size for images, in bytes. +; DEFAULT: 1048576 +;max_upload_size = 1048576 + +; Album Art Minimum Width +; Specify the minimum width for arts (in pixel). +; DEFAULT: none +;album_art_min_width = 100 + +; Album Art Maximum Width +; Specify the maximum width for arts (in pixel). +; DEFAULT: none +;album_art_max_width = 1024 + +; Album Art Minimum Height +; Specify the minimum height for arts (in pixel). +; DEFAULT: none +;album_art_min_height = 100 + +; Album Art Maximum Height +; Specify the maximum height for arts (in pixel). +; DEFAULT: none +;album_art_max_height = 1024 + +; Resize Images * Requires PHP-GD * +; Set this to true if you want Ampache to resize the Album +; art on the fly, this increases load time and CPU usage +; and also requires the PHP-GD library. This is very useful +; If you have high-quality album art and a small upload cap +; DEFAULT: false +;resize_images = "false" + +; Statistical Graphs * Requires PHP-GD * +; Set this to true if you want Ampache to generate statistical +; graphs on usages / users. +; DEFAULT: false +;statistical_graphs = "false" + +; Art Gather Order +; Simply arrange the following in the order you would like +; ampache to search. If you want to disable one of the search +; methods simply leave it out. DB should be left as the first +; method unless you want it to overwrite what's already in the +; database +; POSSIBLE VALUES (builtins): db tags folder lastfm musicbrainz google +; POSSIBLE VALUES (plugins): Amazon,TheAudioDb,Tmdb,Omdb,Flickr +; DEFAULT: db,tags,folder,musicbrainz,lastfm,google +art_order = "db,tags,folder,musicbrainz,lastfm,google" + +; Recommendations +; Set this to true to enable display of similar artists or albums +; while browsing. Requires Last.FM. +; DEFAULT: false +;show_similar = "false" + +; Concerts +; Set this to true to enable display of artist concerts +; Requires Last.FM. +; DEFAULT: false +;show_concerts = "false" + +; Last.FM API Key +; Set this to your Last.FM api key to actually use Last.FM for +; recommendations and metadata. +lastfm_api_key = "d5df942424c71b754e54ce1832505ae2" + +; Wanted +; Set this to true to enable display missing albums and the +; possibility for users to mark it as wanted. +; DEFAULT: false +wanted = "true" + +; Wanted types +; Set the allowed types of wanted releases (album,compilation,single,ep,live,remix,promotion,official) +; DEFAULT: album,official +wanted_types = "album,official" + +; Wanted Auto Accept +; Mark wanted requests as accepted by default (no content manager agreement required) +; DEFAULT: false +;wanted_auto_accept = "false" + +; EchoNest API key +; EchoNest provides several music services. Currently used for missing song 30 seconds preview. +;echonest_api_key = "" + +; Labels +; Use labels to browse artists per label membership. +; DEFAULT: false +;label = "false" + +; Broadcasts +; Allow users to broadcast music. +; This feature requires advanced server configuration, please take a look on the wiki for more information. +; DEFAULT: false +;broadcast = "false" + +; Channels +; Set this to true to enable channels and the +; possibility for users to create channels from playlists +; DEFAULT: true +channel = "true" + +; Live Streams +; Set this to true to enable live streams (radio) and the +; possibility for users to add new live streams. +; DEFAULT: true +live_stream = "true" + +; Web Socket address +; Declare the web socket server address +; DEFAULT: determined automatically +;websocket_address = "ws://localhost:8100" + +; Debug +; If this is enabled Ampache will write debugging information to the log file +; DEFAULT: false +debug = "true" + +; Debug Level +; This should always be set in conjunction with the +; debug option, it defines how prolific you want the +; debugging in ampache to be. values are 1-5. +; 1 == Errors only +; 2 == Error + Failures (login attempts etc.) +; 3 == ?? +; 4 == ?? (Profit!) +; 5 == Information (cataloging progress etc.) +; DEFAULT: 5 +debug_level = "5" + +; Path to Log File +; This defines where you want ampache to log events to +; this will only happen if debug is turned on. Do not +; include trailing slash. You will need to make sure that +; the specified directory exists and your HTTP server has +; write access. +; DEFAULT: NULL +log_path = "/var/www/ampache/log" + +; Log filename pattern +; This defines where the log file name pattern. +; %name.%Y%m%d.log will create a different log file every day. +; DEFAULT: %name.%Y%m%d.log +log_filename = "%name.%Y%m%d.log" + +; Charset of generated HTML pages +; Default of UTF-8 should work for most people +; DEFAULT: UTF-8 +site_charset = "UTF-8" + +; Locale Charset +; Local charset (mainly for file operations) if different +; from site_charset. +; This is disabled by default, enable only if needed +; (for Windows please set lc_charset to ISO8859-1) +; DEFAULT: ISO8859-1 +;lc_charset = "ISO8859-1" + +; Refresh Limit +; This defines the default refresh limit in seconds for +; pages with dynamic content, such as now playing +; DEFAULT: 60 +; Possible Values: Int > 5 +refresh_limit = "60" + +; Footer Statistics +; This defines whether statistics (Queries, Cache Hits, Load Time) +; are shown in the page footer. +; DEFAULT: true +; Possible values: true, false +show_footer_statistics = "true" + +;######################################################### +; Custom actions (optional) # +;######################################################### + +; Your custom play action title +;custom_play_action_title_0 = "" +; Your custom play action icon name (stored as /images/icon_[your_image].png) +;custom_play_action_icon_0 = "" +; Your custom action script, where: +; - %f: the media file path +; - %c: the excepted codec target (mp3, ogg, ...) +; - %a: the artist name +; - %A: the album name +; - %t: the song title +;custom_play_action_run_0 = "" + +; Example for Karaoke playing +;custom_play_action_title_0 = "Karaoke" +;custom_play_action_icon_0 = "microphone" +;custom_play_action_run_0 = "sox \"%f\" -p oops | ffmpeg -i pipe:0 -f %c pipe:1" + +;######################################################### +; LDAP login info (optional) # +;######################################################### + +; LDAP filter string to use (required) +; For OpenLDAP use "uid" +; For Microsoft Active Directory (MAD) use "sAMAccountName" +; DEFAULT: null +;ldap_filter = "(sAMAccountName=%v)" + +; LDAP objectclass (required) +; OpanLDAP objectclass = "*" +; MAD objectclass = "organizationalPerson" +; DEFAULT null +ldap_objectclass = "posixAccount" + +; Initial credentials to bind with for searching (optional) +; DEFAULT: null +;ldap_username = "" +;ldap_password = "" + +; Require that the user is in a specific group (optional) +; DEFAULT: null +;ldap_require_group = "cn=yourgroup,ou=yourorg,dc=yoursubdomain,dc=yourdomain,dc=yourtld" + +; This is the search dn used to find users (required) +; DEFAULT: null +ldap_search_dn = "dc=yunohost,dc=org" + +; This is the address of your ldap server (required) +; DEFAULT: null +ldap_url = "localhost" + +; Attributes where additional user information is stored (optional) +; OpenLDAP ldap_name_field = "cn" +; MAD ldap_name_field = "displayname" +; DEFAULT: null +;ldap_email_field = "mail" +ldap_name_field = "cn" + +;######################################################### +; OpenID login info (optional) # +;######################################################### + +; Requires specific OpenID Provider Authentication Policy +; DEFAULT: null +; VALUES: PAPE_AUTH_MULTI_FACTOR_PHYSICAL,PAPE_AUTH_MULTI_FACTOR,PAPE_AUTH_PHISHING_RESISTANT +;openid_required_pape = "" + +;######################################################### +; Public Registration settings, defaults to disabled # +;######################################################### + +; This setting will silently create an ampache account +; for anyone who can login using ldap (or any other login +; extension). The default is to create new users as guests +; see auto_user config option if you would like to change this +; DEFAULT: false +auto_create = "true" + +; This setting turns on/off public registration. It is +; recommended you leave this off, as it will allow anyone to +; sign up for an account on your server. +; REMEMBER: don't forget to set the mail from address further down in the config. +; DEFAULT: false +;allow_public_registration = "false" + +; Require Captcha Text on Image confirmation +; Turning this on requires the user to correctly +; type in the letters in the image created by Captcha +; Default is off because its very hard to detect if it failed +; to draw, or they failed to enter it. +; DEFAULT: false +;captcha_public_reg = "false" + +; This setting turns on/off admin notification of registration. +; DEFAULT: false +;admin_notify_reg = "false" + +; This setting determines whether the user will be created as a disabled user. +; If this is on, an administrator will need to manually enable the account +; before it's usable. +; DEFAULT: false +;admin_enable_required = "false" + +; This setting will allow all registrants/ldap/http users +; to be auto-approved as a user. By default, they will be +; added as a guest and must be promoted by the admin. +; POSSIBLE VALUES: guest, user, admin +; DEFAULT: guest +auto_user = "user" + +; This will display the user agreement when registering +; For agreement text, edit config/registration_agreement.php +; User will need to accept the agreement before they can register +; DEFAULT: false +;user_agreement = "false" + +; This disable email confirmation when registering. +; DEFAULT: false +;user_no_email_confirm = "false" + +; This will display the cookie disclaimer (EU Cookie Law) +; DEFAULT: false +;cookie_disclaimer = "false" + +; The fields that will be shown on Registration page +; If a user wants to register. +; Username and email fields are forced. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: "fullname,website" +registration_display_fields = "fullname,website" + +; The fields that will be mandatory +; This controls which fields are mandatory for registration. +; Username and email fields are forced mandatory. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: fullname +registration_mandatory_fields = "fullname" + +;######################################################## +; These options control the dynamic downsampling based # +; on current usage # +; *Note* Transcoding must be enabled and working # +;######################################################## + +; Attempt to optimize bandwidth by dynamically lowering the bit rate of new +; streams. Since the bit rate is only adjusted at the beginning of a song, the +; actual cumulative bitrate for concurrent streams can be up to around +; double the configured value. It also only applies to streams that are +; transcoded. +; DEFAULT: none +max_bit_rate = "576" + +; New dynamically downsampled streams will be denied if they are forced below +; this value. +; DEFAULT: 8 +min_bit_rate = "48" + +;###################################################### +; These are commands used to transcode non-streaming +; formats to the target file type for streaming. +; This can be useful in re-encoding file types that don't stream +; very well, or if your player doesn't support some file types. +; +; 'Downsampling' will also use these commands. +; +; To state the bleeding obvious, any programs referenced in the transcode +; commands must be installed, in the web server's search path (or referenced +; by their full path), and executable by the web server. + +; Input type selection +; TYPE is the extension. 'allowed' certifies that transcoding works properly for +; this input format. 'required' further forbids the direct streaming of a format +; (e.g. if you store everything in FLAC, but don't want to ever stream that.) +; transcode_TYPE = {allowed|required|false} +; DEFAULT: false +;;; Audio +;transcode_m4a = allowed +transcode_flac = "required" +;transcode_mpc = required +;transcode_ogg = required +;transcode_oga = required +;transcode_wav = required +;transcode_wma = required +;transcode_aif = required +;transcode_aiff = required +;transcode_ape = required +;transcode_shn = required +transcode_mp3 = "allowed" +;;; Video +;transcode_avi = allowed +;transcode_mkv = allowed +;transcode_mpg = allowed +;transcode_mpeg = allowed +;transcode_m4v = allowed +;transcode_mp4 = allowed +;transcode_mov = allowed +;transcode_wmv = allowed +;transcode_ogv = allowed +;transcode_divx = allowed +;transcode_m2ts = allowed +;transcode_webm = allowed + +; Default audio output format +; DEFAULT: none +encode_target = "mp3" + +; Default video output format +; DEFAULT: none +;encode_video_target = webm + +; Override the default output format on a per-type basis +; encode_target_TYPE = TYPE +; DEFAULT: none +;encode_target_flac = ogg + +; Override the default TYPE transcoding behavior on a per-player basis +; transcode_player_PLAYER_TYPE = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;transcode_player_webplayer_m4a = required +;transcode_player_webplayer_flac = required +;transcode_player_webplayer_mpc = required + +; Override the default output format on a per-player basis +; encode_player_PLAYER_target = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;encode_player_webplayer_target = mp3 +;encode_player_api_target = mp3 + +; Allow clients to override transcode settings (output type, bitrate, codec ...) +; DEFAULT: true +transcode_player_customize = "1" + +; Command configuration. Substitutions will be made as follows: +; %FILE% => filename +; %SAMPLE% => target sample rate +; You can do fancy things like VBR, but consider whether the consequences are +; acceptable in your environment. + +; Master transcode command +; transcode_cmd should be a single command that supports multiple file types, +; such as ffmpeg or avconv. It's still possible to make a configuration that's +; equivalent to the old default, but if you find that necessary you should be +; clever enough to figure out how on your own. +; DEFAULT: none +transcode_cmd = "ffmpeg -i %FILE%" +;transcode_cmd = "avconv" +;transcode_cmd = "/usr/bin/neatokeen" + +; Transcode input file argument +transcode_input = "-i %FILE%" + +; Specific transcode commands +; It shouldn't be necessary in most cases, but you can override the transcode +; command for specific source formats. It still needs to accept the +; encoding arguments, so the easiest approach is to use your normal command as +; a clearing-house. +; transcode_cmd_TYPE = TRANSCODE_CMD +;transcode_cmd_mid = "timidity -Or -o – %FILE% | ffmpeg -f s16le -i pipe:0" + +; Encoding arguments +; For each output format, you should provide the necessary arguments for +; your transcode_cmd. +; encode_args_TYPE = TRANSCODE_CMD_ARGS +encode_args_mp3 = "-vn -b:a %SAMPLE%K -acodec libmp3lame -f mp3 pipe:1" +encode_args_ogg = "-vn -b:a max\(%SAMPLE%K\,49K\) -acodec libvorbis -vcodec libtheora -f ogg pipe:1" +encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" +encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" +encode_args_flv = "-b:a %SAMPLE%K -ar 44100 -ac 2 -v 0 -f flv -c:v libx264 -preset superfast -threads 0 pipe:1" +encode_args_webm = "-q %QUALITY% -f webm -c:v libvpx -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" +encode_args_ts = "-q %QUALITY% -s %RESOLUTION% -f mpegts -c:v libx264 -c:a libmp3lame -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" + +; Encoding arguments to retrieve an image from a single frame +encode_get_image = "-ss %TIME% -f image2 -vframes 1 pipe:1" + +; Encoding argument to encrust subtitle +encode_srt = "-vf \"subtitles='%SRTFILE%'\"" + +; Encode segment frame argument +encode_ss_frame = "-ss %TIME%" + +; Encode segment duration argument +encode_ss_duration = "-t %DURATION%" + + +;###################################################### +; these options allow you to configure your rss-feed +; layout. rss exists of two parts, main and song main is the information about the feed +; song is the information in the feed. can be multiple items. +; use_rss = false (values true | false) +;DEFAULT: use_rss = false +;use_rss = "false" +;##################################################### + +;############################# +; Proxy Settings (optional) # +;############################# +; If Ampache is behind an http proxy, specifiy the hostname or IP address +; port, proxyusername, and proxypassword here. +;DEFAULT: not in use +;proxy_host = "192.168.0.1" +;proxy_port = "8080" +;proxy_user = "" +;proxy_pass = "" + +; If Ampache is behind an https reverse proxy, force use HTTPS protocol. +;Default: false +force_ssl = "1" + +;############################# +; Mail Settings # +;############################# + +;Method used to send mail +;POSSIBLE VALUES: smtp sendmail php +;DEFAULT: php +;mail_type = "php" + +;Mail domain. +;DEFAULT: example.com +;mail_domain = "example.com" + +;This will be combined with mail_domain and used as the source address for +;emails generated by Ampache. For example, setting this to 'me' will set the +;sender to 'me@example.com'. +;DEFAULT: info +;mail_user = "info" + +;A name to go with the email address. +;DEFAULT: Ampache +;mail_name = "Ampache" + +;How strictly email addresses should be checked. +;easy does a regex match, strict actually performs some SMTP transactions +;to see if we can send to this address. +;POSSIBLE VALUES: strict easy none +; DEFAULT: strict +;mail_check = "strict" + + +;############################ +; sendmail Settings # +;############################ + +;DEFAULT: /usr/sbin/sendmail +;sendmail_path = "/usr/sbin/sendmail" + +;############################# +; SMTP Settings # +;############################# + +;Mail server (hostname or IP address) +;DEFAULT: localhost +;mail_host = "localhost" + +; SMTP port +;DEFAULT: 25 +;mail_port = 25 + +;Secure SMTP +;POSSIBLE VALUES: ssl tls +;DEFAULT: none +;mail_secure_smtp = tls + +;Enable SMTP authentication +;DEFAULT: false +;mail_auth = "true" + +;SMTP Username +;your mail auth username. +;mail_auth_user = "" + +; SMTP Password +; your mail auth password. +;mail_auth_pass = "" + +;############################# +; Multibyte Settings # +;############################# +; See http://php.net/manual/mbstring.supported-encodings.php +; If you want ID3v1 encoding detection to work, you should uncomment this line +; so that the ordering is sane. +; DEFAULT: auto +;mb_detect_order = "ASCII,UTF-8,EUC-JP,ISO-2022-JP,SJIS,JIS" + +slideshow_time = 0 + diff --git a/conf/nginx.conf b/conf/nginx.conf index 21dbfbc..8417a81 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -14,6 +14,7 @@ location PATHTOCHANGE { include fastcgi_params; fastcgi_param REMOTE_USER $remote_user; fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $request_filename; } rewrite ^PATHTOCHANGE/play/ssid/(\w+)/type/(\w+)/oid/([0-9]+)/uid/([0-9]+)/name/(.*)$ PATHTOCHANGE/play/index.php?ssid=$1&type=$2&oid=$3&uid=$4&name=$5 last; diff --git a/scripts/install b/scripts/install index 2701bc4..5931a79 100644 --- a/scripts/install +++ b/scripts/install @@ -67,6 +67,7 @@ sleep 5 sudo yunohost app setting ampache skipped_uris -d sudo yunohost app setting ampache skipped_uris -v "/rest" sudo yunohost app ssowatconf -sudo sed -i '/yunoampache/d' /etc/hosts +sudo sed '/yunoampache/d' /etc/hosts > /tmp/hosts.tmp +sudo cp /tmp/hosts.tmp /etc/hosts ; sudo rm -f /tmp/hosts.tmp mysql -u $db_user -p$db_pwd $db_user < /tmp/admin.sql sudo rm /tmp/admin.sql diff --git a/sources/.gitattributes b/sources/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/sources/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/sources/.gitignore b/sources/.gitignore new file mode 100644 index 0000000..39e94cc --- /dev/null +++ b/sources/.gitignore @@ -0,0 +1,12 @@ +config/ampache.cfg.php +config/ampache-doped.cfg.php +rest/.htaccess +play/.htaccess +*.phpproj +*.sln +*.v11.suo +*.suo +logs +/nbproject/private/ +.pc +/tmp \ No newline at end of file diff --git a/sources/.maintenance.example b/sources/.maintenance.example new file mode 100644 index 0000000..3cf4310 --- /dev/null +++ b/sources/.maintenance.example @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/sources/.php_cs b/sources/.php_cs new file mode 100644 index 0000000..8d80bfc --- /dev/null +++ b/sources/.php_cs @@ -0,0 +1,16 @@ +exclude('modules') + ->exclude('nbproject') + ->in(__DIR__) + ->in(__DIR__ . '/modules/localplay') + ->in(__DIR__ . '/modules/catalog') + ->in(__DIR__ . '/modules/ampacheapi') +; + +return Symfony\CS\Config\Config::create() + ->finder($finder) +; \ No newline at end of file diff --git a/sources/.scrutinizer.yml b/sources/.scrutinizer.yml new file mode 100644 index 0000000..5de5f40 --- /dev/null +++ b/sources/.scrutinizer.yml @@ -0,0 +1,59 @@ +filter: + excluded_paths: + - 'modules/*' + paths: { } + +tools: + php_mess_detector: + enabled: true + filter: + excluded_paths: + - 'modules/*' + - 'themes/*' + paths: { } + php_code_sniffer: + enabled: false + php_pdepend: + enabled: true + configuration_file: null + suffixes: + - php + excluded_dirs: { } + filter: + excluded_paths: + - 'modules/*' + paths: { } + php_analyzer: + enabled: true + extensions: + - php + dependency_paths: + - 'modules/*' + filter: + excluded_paths: + - 'modules/*' + - 'themes/*' + paths: { } + path_configs: { } + php_changetracking: + enabled: true + bug_patterns: + - '\bfix(?:es|ed)?\b' + feature_patterns: + - '\badd(?:s|ed)?\b' + - '\bimplement(?:s|ed)?\b' + filter: + excluded_paths: + - 'modules/*' + paths: { } + php_loc: + enabled: true + excluded_dirs: + - 'modules/*' + php_cpd: + enabled: true + excluded_dirs: { } + filter: + excluded_paths: + - 'modules/*' + paths: { } \ No newline at end of file diff --git a/sources/.tgitconfig b/sources/.tgitconfig new file mode 100644 index 0000000..8a07fe8 --- /dev/null +++ b/sources/.tgitconfig @@ -0,0 +1,6 @@ +[bugtraq] + url = "https://github.com/ampache/ampache/issues/#BUGID#" + label = GitHub Issue Number + warnifnoissue = false + number = true + logregex = "#\\d+\n\\d+" diff --git a/sources/.travis.yml b/sources/.travis.yml new file mode 100644 index 0000000..6f51e77 --- /dev/null +++ b/sources/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + +before_install: + - wget http://cs.sensiolabs.org/get/php-cs-fixer.phar + +before_script: + - chmod +x scripts/tests/syntax.sh + - chmod +x scripts/tests/codestyle.sh + +script: + - scripts/tests/syntax.sh + - scripts/tests/codestyle.sh \ No newline at end of file diff --git a/sources/.tx/config b/sources/.tx/config new file mode 100644 index 0000000..a74e069 --- /dev/null +++ b/sources/.tx/config @@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com + +[project_slug.resource_slug] +file_filter = locale/_/LC_MESSAGES/messages.po +source_file = locale/base/messages.pot +source_lang = en_US +type = PO diff --git a/sources/CNAME b/sources/CNAME new file mode 100644 index 0000000..d5cfb4c --- /dev/null +++ b/sources/CNAME @@ -0,0 +1 @@ +ampache.org \ No newline at end of file diff --git a/sources/CONTRIBUTING.md b/sources/CONTRIBUTING.md new file mode 100644 index 0000000..249c9a2 --- /dev/null +++ b/sources/CONTRIBUTING.md @@ -0,0 +1,6 @@ +## Contribution +Please read [Development section](https://github.com/ampache/ampache/wiki#development). + +## Bug report +Be sure the bug is not already fixed in `develop` branch or already reported in current open issues. +Please add [some logs](https://github.com/ampache/ampache/wiki/Troubleshooting#enable-logging) with your new issue. diff --git a/sources/README.md b/sources/README.md index 9c68a48..73b9418 100755 --- a/sources/README.md +++ b/sources/README.md @@ -22,11 +22,13 @@ Recommended Version Currently, the recommended version is [git HEAD](https://github.com/ampache/ampache/archive/master.tar.gz). [![Build Status](https://api.travis-ci.org/ampache/ampache.png?branch=master)](https://travis-ci.org/ampache/ampache) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ampache/ampache/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ampache/ampache/?branch=master) Latest changes but unstable is [develop branch](https://github.com/ampache/ampache/archive/develop.tar.gz). [![Build Status](https://api.travis-ci.org/ampache/ampache.png?branch=develop)](https://travis-ci.org/ampache/ampache) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ampache/ampache/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/ampache/ampache/?branch=develop) +[![Codacy Badge](https://www.codacy.com/project/badge/b28cdb9e9ee2431c7cb9c23d5438cb80)](https://www.codacy.com/app/afterster_2222/ampache) +[![Code Climate](https://codeclimate.com/github/ampache/ampache/badges/gpa.svg)](https://codeclimate.com/github/ampache/ampache) +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/ee067d0e-3432-4062-a969-01b4ee037f48/mini.png)](https://insight.sensiolabs.com/projects/ee067d0e-3432-4062-a969-01b4ee037f48) Requirements ------------ @@ -38,7 +40,7 @@ receives the most testing: * nginx * IIS -* PHP 5.3 or greater. +* PHP 5.4 or greater. * PHP modules: * PDO @@ -46,6 +48,8 @@ receives the most testing: * hash * session * json + * simplexml (This is not strictly necessary, but may result in a better experience.) + * curl (This is not strictly necessary, but may result in a better experience.) * MySQL 5.x @@ -59,8 +63,8 @@ Upgrading If you are upgrading from an older version of Ampache we recommend moving the old directory out of the way, extracting the new copy in -its place and then copying the old config file into config/. All -database updates will be handled by Ampache. +its place and then copying the old config/ampache.cfg.php, /rest/.htaccess, +and /play/.htaccess files if any. All database updates will be handled by Ampache. License ------- @@ -99,6 +103,15 @@ Ampache includes some external modules that carry their own licensing. * [MediaTable] (https://github.com/edenspiekermann/MediaTable): MIT * [Responsive Elements] (https://github.com/kumailht/responsive-elements): MIT * [Bootstrap] (http://getbootstrap.com): MIT +* [jQuery Knob] (https://github.com/aterrien/jQuery-Knob): MIT +* [jQuery File Upload] (https://github.com/blueimp/jQuery-File-Upload): MIT +* [jsTree] (http://www.jstree.com): MIT +* [php-tmdb-api] (https://github.com/wtfzdotnet/php-tmdb-api) : MIT +* [TvDb] (https://github.com/Moinax/TvDb) : MIT +* [jQuery DateTimePicker] (https://github.com/xdan/datetimepicker) : MIT +* [pChart] (http://www.pchart.net) : GPL v3 +* [ZipStream-PHP] (https://github.com/maennchen/ZipStream-PHP) : [ZipStream-PHP license] (modules/zipstream/COPYING) +* [SabreDAV] (https://github.com/fruux/sabre-dav) : New BSD Translations @@ -109,19 +122,24 @@ following languages. If you are interested in updating an existing translation or adding a new one please see /locale/base/TRANSLATIONS for more instructions. -* English (en_US) -* German (de_DE) -* Spanish (es_ES) -* Dutch (nl_NL) -* Norwegian (nb_NO) -* UK English (en_GB) -* Italian (it_IT) -* French (fr_FR) -* Swedish (sv_SE) -* Japanese (ja_JP) -* Catalan (ca_ES) -* Russian (ru_RU) -* Czech (cs_CZ) +* English (en_US) +* Arabic (ar_SA) +* Catalan (ca_ES) +* Catalan (ca_ES) +* Czech (cs_CZ) +* Dutch (nl_NL) +* French (fr_FR) +* German (de_DE) +* Greek (el_GR) +* Italian (it_IT) +* Japanese (ja_JP) +* Norwegian (nb_NO) +* Persian (fa_IR) +* Polish (pl_PL) +* Russian (ru_RU) +* Spanish (es_ES) +* Swedish (sv_SE) +* UK English (en_GB) Credits ------- @@ -137,6 +155,7 @@ more features, encounter bugs, etc. * [Public Repository](http://github.com/ampache) * IRC: chat.freenode.net #ampache +* [Forum](https://groups.google.com/forum/#!forum/ampache) * [Issue Tracker](https://github.com/ampache/ampache/issues) * [Documentation](https://github.com/ampache/ampache/wiki) diff --git a/sources/admin/access.php b/sources/admin/access.php index 6146992..5c48f78 100644 --- a/sources/admin/access.php +++ b/sources/admin/access.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/admin/catalog.php b/sources/admin/catalog.php index 0bf5994..61372dd 100644 --- a/sources/admin/catalog.php +++ b/sources/admin/catalog.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,74 +30,57 @@ if (!Access::check('interface','100')) { UI::show_header(); +$catalogs = $_REQUEST['catalogs']; +// If only one catalog, check it is ready. +if (is_array($catalogs) && count($catalogs) == 1 && $_REQUEST['action'] !== 'delete_catalog' && $_REQUEST['action'] !== 'show_delete_catalog') { + // If not ready, display the data to make it ready / stop the action. + $catalog = Catalog::create_from_id($catalogs[0]); + if (!$catalog->isReady()) { + if (!isset($_REQUEST['perform_ready'])) { + $catalog->show_ready_process(); + UI::show_footer(); + exit; + } else { + $catalog->perform_ready(); + } + } +} +$sse_catalogs = urlencode(serialize($catalogs)); + /* Big switch statement to handle various actions */ switch ($_REQUEST['action']) { case 'add_to_all_catalogs': - $_REQUEST['catalogs'] = Catalog::get_catalogs(); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=add_to_all_catalogs"; + sse_worker($sse_url); + show_confirmation(T_('Catalog Update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); + break; case 'add_to_catalog': - toggle_visible('ajax-loading'); - ob_end_flush(); if (AmpConfig::get('demo_mode')) { break; } - if ($_REQUEST['catalogs']) { - foreach ($_REQUEST['catalogs'] as $catalog_id) { - $catalog = Catalog::create_from_id($catalog_id); - $catalog->add_to_catalog($_POST); - } - } - $url = AmpConfig::get('web_path') . '/admin/catalog.php'; - $title = T_('Catalog Updated'); - $body = ''; - show_confirmation($title, $body, $url); - toggle_visible('ajax-loading'); + + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=add_to_catalog&catalogs=" . $sse_catalogs; + sse_worker($sse_url); + show_confirmation(T_('Catalog Update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'update_all_catalogs': - $_REQUEST['catalogs'] = Catalog::get_catalogs(); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=update_all_catalogs"; + sse_worker($sse_url); + show_confirmation(T_('Catalog Update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); + break; case 'update_catalog': - toggle_visible('ajax-loading'); - ob_end_flush(); - /* If they are in demo mode stop here */ - if (AmpConfig::get('demo_mode')) { break; } + if (AmpConfig::get('demo_mode')) { break; } - if (isset($_REQUEST['catalogs'])) { - foreach ($_REQUEST['catalogs'] as $catalog_id) { - $catalog = Catalog::create_from_id($catalog_id); - $catalog->verify_catalog(); - } - } - $url = AmpConfig::get('web_path') . '/admin/catalog.php'; - $title = T_('Catalog Updated'); - $body = ''; - show_confirmation($title,$body,$url); - toggle_visible('ajax-loading'); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=update_catalog&catalogs=" . $sse_catalogs; + sse_worker($sse_url); + show_confirmation(T_('Catalog Update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'full_service': - toggle_visible('ajax-loading'); - ob_end_flush(); - /* Make sure they aren't in demo mode */ if (AmpConfig::get('demo_mode')) { UI::access_denied(); break; } - if (!$_REQUEST['catalogs']) { - $_REQUEST['catalogs'] = Catalog::get_catalogs(); - } - - /* This runs the clean/verify/add in that order */ - foreach ($_REQUEST['catalogs'] as $catalog_id) { - $catalog = Catalog::create_from_id($catalog_id); - $catalog->clean_catalog(); - $catalog->count = 0; - $catalog->verify_catalog(); - $catalog->count = 0; - $catalog->add_to_catalog(); - } - Dba::optimize_tables(); - $url = AmpConfig::get('web_path') . '/admin/catalog.php'; - $title = T_('Catalog Updated'); - $body = ''; - show_confirmation($title,$body,$url); - toggle_visible('ajax-loading'); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=full_service&catalogs=" . $sse_catalogs; + sse_worker($sse_url); + show_confirmation(T_('Catalog Update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'delete_catalog': - /* Make sure they aren't in demo mode */ if (AmpConfig::get('demo_mode')) { break; } if (!Core::form_verify('delete_catalog')) { @@ -105,15 +88,21 @@ switch ($_REQUEST['action']) { exit; } + $deleted = true; /* Delete the sucker, we don't need to check perms as thats done above */ - Catalog::delete($_GET['catalog_id']); + foreach ($catalogs as $catalog_id) { + $deleted = Catalog::delete($catalog_id); + if (!$deleted) break; + } $next_url = AmpConfig::get('web_path') . '/admin/catalog.php'; - show_confirmation(T_('Catalog Deleted'), T_('The Catalog and all associated records have been deleted'),$next_url); + if ($deleted) { + show_confirmation(T_('Catalog Deleted'), T_('The Catalog and all associated records have been deleted'), $next_url); + } else { + show_confirmation(T_('Error'), T_('Cannot delete the catalog'), $next_url); + } break; case 'show_delete_catalog': - $catalog_id = scrub_in($_GET['catalog_id']); - - $next_url = AmpConfig::get('web_path') . '/admin/catalog.php?action=delete_catalog&catalog_id=' . scrub_out($catalog_id); + $next_url = AmpConfig::get('web_path') . '/admin/catalog.php?action=delete_catalog&catalogs[]=' . implode(',', $catalogs); show_confirmation(T_('Catalog Delete'), T_('Confirm Deletion Request'),$next_url,1,'delete_catalog'); break; case 'enable_disabled': @@ -134,27 +123,14 @@ switch ($_REQUEST['action']) { show_confirmation($title,$body,$url); break; case 'clean_all_catalogs': - $_REQUEST['catalogs'] = Catalog::get_catalogs(); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=clean_all_catalogs"; + sse_worker($sse_url); + show_confirmation(T_('Catalog Clean started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); + break; case 'clean_catalog': - toggle_visible('ajax-loading'); - ob_end_flush(); - /* If they are in demo mode stop them here */ - if (AmpConfig::get('demo_mode')) { break; } - - // Make sure they checked something - if (isset($_REQUEST['catalogs'])) { - foreach ($_REQUEST['catalogs'] as $catalog_id) { - $catalog = Catalog::create_from_id($catalog_id); - $catalog->clean_catalog(); - } // end foreach catalogs - Dba::optimize_tables(); - } - - $url = AmpConfig::get('web_path') . '/admin/catalog.php'; - $title = T_('Catalog Cleaned'); - $body = ''; - show_confirmation($title,$body,$url); - toggle_visible('ajax-loading'); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=clean_catalog&catalogs=" . $sse_catalogs; + sse_worker($sse_url); + show_confirmation(T_('Catalog Clean started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'update_catalog_settings': /* No Demo Here! */ @@ -171,23 +147,9 @@ switch ($_REQUEST['action']) { case 'update_from': if (AmpConfig::get('demo_mode')) { break; } - // First see if we need to do an add - if ($_POST['add_path'] != '/' AND strlen($_POST['add_path'])) { - if ($catalog_id = Catalog_local::get_from_path($_POST['add_path'])) { - $catalog = Catalog::create_from_id($catalog_id); - $catalog->add_to_catalog(array('subdirectory'=>$_POST['add_path'])); - } - } // end if add - - // Now check for an update - if ($_POST['update_path'] != '/' AND strlen($_POST['update_path'])) { - if ($catalog_id = Catalog_local::get_from_path($_POST['update_path'])) { - $songs = Song::get_from_path($_POST['update_path']); - foreach ($songs as $song_id) { Catalog::update_single_item('song',$song_id); } - } - } // end if update - - echo T_("Done."); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=update_from&add_path=" . scrub_in($_POST['add_path']) . "&update_path=" . $_POST['update_path']; + sse_worker($sse_url); + show_confirmation(T_('Subdirectory update started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'add_catalog': /* Wah Demo! */ @@ -218,19 +180,10 @@ switch ($_REQUEST['action']) { break; } - $catalog = Catalog::create_from_id($catalog_id); - - // Run our initial add - $catalog->add_to_catalog($_POST); - - UI::show_box_top(T_('Catalog Created'), 'box box_catalog_created'); - echo "

" . T_('Catalog Created') . "

"; - Error::display('general'); - Error::display('catalog_add'); - UI::show_box_bottom(); - - show_confirmation('','', AmpConfig::get('web_path').'/admin/catalog.php'); + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=add_catalog&catalog_id=" . $catalog_id . "&options=" . urlencode(serialize($_POST)); + sse_worker($sse_url); + show_confirmation(T_('Catalog Creation started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); } else { require AmpConfig::get('prefix') . '/templates/show_add_catalog.inc.php'; } @@ -275,23 +228,10 @@ switch ($_REQUEST['action']) { $catalog->format(); require_once AmpConfig::get('prefix') . '/templates/show_edit_catalog.inc.php'; break; - case 'gather_album_art': - toggle_visible('ajax-loading'); - ob_end_flush(); - - $catalogs = $_REQUEST['catalogs'] ? $_REQUEST['catalogs'] : Catalog::get_catalogs(); - - // Iterate throught the catalogs and gather as needed - foreach ($catalogs as $catalog_id) { - $catalog = Catalog::create_from_id($catalog_id); - require AmpConfig::get('prefix') . '/templates/show_gather_art.inc.php'; - flush(); - $catalog->gather_art(); - } - $url = AmpConfig::get('web_path') . '/admin/catalog.php'; - $title = T_('Album Art Search Finished'); - $body = ''; - show_confirmation($title,$body,$url); + case 'gather_media_art': + $sse_url = AmpConfig::get('web_path') . "/server/sse.server.php?worker=catalog&action=gather_media_art&catalogs=" . $sse_catalogs; + sse_worker($sse_url); + show_confirmation(T_('Media Art Search started...'), '', AmpConfig::get('web_path') . '/admin/catalog.php', 0, 'confirmation', false); break; case 'show_catalogs': default: diff --git a/sources/admin/duplicates.php b/sources/admin/duplicates.php index ab03ed6..69c812b 100644 --- a/sources/admin/duplicates.php +++ b/sources/admin/duplicates.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/admin/export.php b/sources/admin/export.php index 59e4f1a..bd9e5d1 100644 --- a/sources/admin/export.php +++ b/sources/admin/export.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/admin/index.php b/sources/admin/index.php index fcd9b56..1d63666 100644 --- a/sources/admin/index.php +++ b/sources/admin/index.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/admin/license.php b/sources/admin/license.php new file mode 100644 index 0000000..aa22a2f --- /dev/null +++ b/sources/admin/license.php @@ -0,0 +1,65 @@ +id) { + $license->update($_POST); + } + $text = T_('License Updated'); + } else { + License::create($_POST); + $text = T_('License Created'); + } + show_confirmation($text,'',AmpConfig::get('web_path').'/admin/license.php'); + break; + case 'show_edit': + $license = new License($_REQUEST['license_id']); + case 'show_create': + require_once AmpConfig::get('prefix') . '/templates/show_edit_license.inc.php'; + break; + case 'delete': + License::delete($_REQUEST['license_id']); + show_confirmation(T_('License Deleted'),'',AmpConfig::get('web_path').'/admin/license.php'); + break; + default: + $browse = new Browse(); + $browse->set_type('license'); + $browse->set_simple_browse(true); + $license_ids = $browse->get_objects(); + $browse->show_objects($license_ids); + $browse->store(); + break; +} + +UI::show_footer(); diff --git a/sources/admin/mail.php b/sources/admin/mail.php index 46df45c..15f5e76 100644 --- a/sources/admin/mail.php +++ b/sources/admin/mail.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -39,7 +39,10 @@ switch ($_REQUEST['action']) { // Multi-byte Character Mail if (function_exists('mb_language')) { - ini_set("mbstring.internal_encoding","UTF-8"); + $ini_default_charset = version_compare(PHP_VERSION, '5.6', '<') ? 'mbstring.internal_encoding' : 'default_charset'; + if (ini_get($ini_default_charset)) { + ini_set($ini_default_charset, "UTF-8"); + } mb_language("uni"); } diff --git a/sources/admin/modules.php b/sources/admin/modules.php index f5657f8..7407410 100644 --- a/sources/admin/modules.php +++ b/sources/admin/modules.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -48,10 +48,14 @@ switch ($_REQUEST['action']) { Preference::update('localplay_level',$GLOBALS['user']->id,'100'); Preference::update('localplay_controller',$GLOBALS['user']->id,$localplay->type); - header("Location:" . AmpConfig::get('web_path') . '/admin/modules.php?action=show_localplay'); + /* Show Confirmation */ + $url = AmpConfig::get('web_path') . '/admin/modules.php?action=show_localplay'; + $title = T_('Localplay Installed'); + $body = ''; + show_confirmation($title ,$body, $url); break; case 'install_catalog_type': - $type = scrub_in($_REQUEST['type']); + $type = (string) scrub_in($_REQUEST['type']); $catalog = Catalog::create_catalog_type($type); if ($catalog == null) { Error::add('general', T_('Install Failed, Catalog Error')); @@ -68,21 +72,21 @@ switch ($_REQUEST['action']) { show_confirmation($title ,$body, $url); break; case 'confirm_uninstall_localplay': - $type = scrub_in($_REQUEST['type']); + $type = (string) scrub_in($_REQUEST['type']); $url = AmpConfig::get('web_path') . '/admin/modules.php?action=uninstall_localplay&type=' . $type; $title = T_('Are you sure you want to remove this plugin?'); $body = ''; show_confirmation($title,$body,$url,1); break; case 'confirm_uninstall_catalog_type': - $type = scrub_in($_REQUEST['type']); + $type = (string) scrub_in($_REQUEST['type']); $url = AmpConfig::get('web_path') . '/admin/modules.php?action=uninstall_catalog_type&type=' . $type; $title = T_('Are you sure you want to remove this plugin?'); $body = ''; show_confirmation($title,$body,$url,1); break; case 'uninstall_localplay': - $type = scrub_in($_REQUEST['type']); + $type = (string) scrub_in($_REQUEST['type']); $localplay = new Localplay($type); $localplay->uninstall(); @@ -94,7 +98,7 @@ switch ($_REQUEST['action']) { show_confirmation($title,$body,$url); break; case 'uninstall_catalog_type': - $type = scrub_in($_REQUEST['type']); + $type = (string) scrub_in($_REQUEST['type']); $catalog = Catalog::create_catalog_type($type); if ($catalog == null) { diff --git a/sources/admin/shout.php b/sources/admin/shout.php index 04a1061..50c0221 100644 --- a/sources/admin/shout.php +++ b/sources/admin/shout.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -32,8 +32,10 @@ UI::show_header(); // Switch on the incomming action switch ($_REQUEST['action']) { case 'edit_shout': - $shout_id = $_POST['shout_id']; - $update = Shoutbox::update($_POST); + $shout = new Shoutbox($_REQUEST['shout_id']); + if ($shout->id) { + $shout->update($_POST); + } show_confirmation(T_('Shoutbox Post Updated'),'',AmpConfig::get('web_path').'/admin/shout.php'); break; case 'show_edit': diff --git a/sources/admin/system.php b/sources/admin/system.php index d55f108..ea009d9 100644 --- a/sources/admin/system.php +++ b/sources/admin/system.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -39,9 +39,13 @@ switch ($_REQUEST['action']) { $current = parse_ini_file(AmpConfig::get('prefix') . '/config/ampache.cfg.php'); $final = generate_config($current); $browser = new Horde_Browser(); - $browser->downloadHeaders('ampache.cfg.php','text/plain',false,filesize(AmpConfig::get('prefix') . '/config/ampache.cfg.php.dist')); + $browser->downloadHeaders('ampache.cfg.php', 'text/plain',false,filesize(AmpConfig::get('prefix') . '/config/ampache.cfg.php.dist')); echo $final; exit; + case 'write_config': + write_config(AmpConfig::get('prefix') . '/config/ampache.cfg.php'); + header('Location: '. AmpConfig::get('web_path') . '/index.php'); + exit; case 'reset_db_charset': Dba::reset_db_charset(); show_confirmation(T_('Database Charset Updated'), T_('Your Database and associated tables have been updated to match your currently configured charset'), AmpConfig::get('web_path').'/admin/system.php?action=show_debug'); @@ -53,6 +57,14 @@ switch ($_REQUEST['action']) { } require_once AmpConfig::get('prefix') . '/templates/show_debug.inc.php'; break; + case 'clear_cache': + switch ($_REQUEST['type']) { + case 'song' : Song::clear_cache(); break; + case 'artist' : Artist::clear_cache(); break; + case 'album' : Album::clear_cache(); break; + } + show_confirmation(T_('Cache cleared'), T_('Your cache has been cleared successfully.'), AmpConfig::get('web_path').'/admin/system.php?action=show_debug'); + break; default: // Rien a faire break; diff --git a/sources/admin/users.php b/sources/admin/users.php index 7068d4b..5bd3e24 100644 --- a/sources/admin/users.php +++ b/sources/admin/users.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -40,14 +40,17 @@ switch ($_REQUEST['action']) { } /* Clean up the variables */ - $user_id = intval($_POST['user_id']); - $username = scrub_in($_POST['username']); - $fullname = scrub_in($_POST['fullname']); - $email = scrub_in($_POST['email']); - $website = scrub_in($_POST['website']); - $access = scrub_in($_POST['access']); - $pass1 = $_POST['password_1']; - $pass2 = $_POST['password_2']; + $user_id = intval($_POST['user_id']); + $username = scrub_in($_POST['username']); + $fullname = scrub_in($_POST['fullname']); + $email = scrub_in($_POST['email']); + $website = scrub_in($_POST['website']); + $access = scrub_in($_POST['access']); + $pass1 = $_POST['password_1']; + $pass2 = $_POST['password_2']; + $state = scrub_in($_POST['state']); + $city = scrub_in($_POST['city']); + $fullname_public = isset($_POST['fullname_public']); /* Setup the temp user */ $client = new User($user_id); @@ -55,16 +58,27 @@ switch ($_REQUEST['action']) { /* Verify Input */ if (empty($username)) { Error::add('username', T_("Error Username Required")); + } else { + if ($username != $client->username) { + if (!User::check_username($username)) { + Error::add('username', T_("Error Username already exists")); + } + } } if ($pass1 !== $pass2 && !empty($pass1)) { Error::add('password', T_("Error Passwords don't match")); } - /* If we've got an error then break! */ + // Check the mail for correct address formation. + if (!Mailer::validate_address($email)) { + Error::add('email', T_('Invalid email address')); + } + + /* If we've got an error then show edit form! */ if (Error::occurred()) { - $_REQUEST['action'] = 'show_edit'; + require_once AmpConfig::get('prefix') . '/templates/show_edit_user.inc.php'; break; - } // if we've had an oops! + } if ($access != $client->access) { $client->update_access($access); @@ -81,9 +95,18 @@ switch ($_REQUEST['action']) { if ($fullname != $client->fullname) { $client->update_fullname($fullname); } + if ($fullname_public != $client->fullname_public) { + $client->update_fullname_public($fullname_public); + } if ($pass1 == $pass2 && strlen($pass1)) { $client->update_password($pass1); } + if ($state != $client->state) { + $client->update_state($state); + } + if ($city != $client->city) { + $client->update_city($city); + } $client->upload_avatar(); show_confirmation(T_('User Updated'), $client->fullname . "(" . $client->username . ")" . T_('updated'), AmpConfig::get('web_path'). '/admin/users.php'); @@ -96,13 +119,15 @@ switch ($_REQUEST['action']) { exit; } - $username = scrub_in($_POST['username']); - $fullname = scrub_in($_POST['fullname']); - $email = scrub_in($_POST['email']); + $username = scrub_in($_POST['username']); + $fullname = scrub_in($_POST['fullname']); + $email = scrub_in($_POST['email']); $website = scrub_in($_POST['website']); - $access = scrub_in($_POST['access']); - $pass1 = $_POST['password_1']; - $pass2 = $_POST['password_2']; + $access = scrub_in($_POST['access']); + $pass1 = $_POST['password_1']; + $pass2 = $_POST['password_2']; + $state = (string) scrub_in($_POST['state']); + $city = (string) scrub_in($_POST['city']); if ($pass1 !== $pass2 || !strlen($pass1)) { Error::add('password', T_("Error Passwords don't match")); @@ -117,20 +142,25 @@ switch ($_REQUEST['action']) { Error::add('username', T_('Error Username already exists')); } - if (!Error::occurred()) { - /* Attempt to create the user */ - $user_id = User::create($username, $fullname, $email, $website, $pass1, $access); - if (!$user_id) { - Error::add('general', T_("Error: Insert Failed")); - } + // Check the mail for correct address formation. + if (!Mailer::validate_address($email)) { + Error::add('email', T_('Invalid email address')); + } - $user = new User($user_id); - $user->upload_avatar(); - } // if no errors - else { - $_REQUEST['action'] = 'show_add_user'; + /* If we've got an error then show add form! */ + if (Error::occurred()) { + require_once AmpConfig::get('prefix') . '/templates/show_add_user.inc.php'; break; } + + /* Attempt to create the user */ + $user_id = User::create($username, $fullname, $email, $website, $pass1, $access, $state, $city); + if (!$user_id) { + Error::add('general', T_("Error: Insert Failed")); + } + $user = new User($user_id); + $user->upload_avatar(); + if ($access == 5) { $access = T_('Guest');} elseif ($access == 25) { $access = T_('User');} elseif ($access == 100) { $access = T_('Admin');} /* HINT: %1 Username, %2 Access num */ @@ -139,6 +169,9 @@ switch ($_REQUEST['action']) { case 'enable': $client = new User($_REQUEST['user_id']); $client->enable(); + if (!AmpConfig::get('user_no_email_confirm')) { + Registration::send_account_enabled($client->username, $client->fullname, $client->email); + } show_confirmation(T_('User Enabled'),$client->fullname . ' (' . $client->username . ')', AmpConfig::get('web_path'). '/admin/users.php'); break; case 'disable': @@ -239,7 +272,7 @@ switch ($_REQUEST['action']) { $browse = new Browse(); $browse->reset_filters(); $browse->set_type('user'); - $browse->set_simple_browse(1); + $browse->set_simple_browse(true); $browse->set_sort('name','ASC'); $user_ids = $browse->get_objects(); $browse->show_objects($user_ids); diff --git a/sources/albums.php b/sources/albums.php index 6949003..5b189c3 100644 --- a/sources/albums.php +++ b/sources/albums.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -26,144 +26,33 @@ require_once AmpConfig::get('prefix') . '/templates/header.inc.php'; /* Switch on Action */ switch ($_REQUEST['action']) { - case 'clear_art': - if (!$GLOBALS['user']->has_access('75')) { UI::access_denied(); } - $art = new Art($_GET['album_id'],'album'); - $art->reset(); - show_confirmation(T_('Album Art Cleared'), T_('Album Art information has been removed from the database'),"/albums.php?action=show&album=" . $art->uid); - break; - // Upload album art - case 'upload_art': + case 'delete': + if (AmpConfig::get('demo_mode')) { break; } - // we didn't find anything - if (empty($_FILES['file']['tmp_name'])) { - show_confirmation(T_('Album Art Not Located'), T_('Album Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'),"/albums.php?action=show&album=" . $_REQUEST['album_id']); - break; - } + $album_id = scrub_in($_REQUEST['album_id']); + show_confirmation( + T_('Album Deletion'), + T_('Are you sure you want to permanently delete this album?'), + AmpConfig::get('web_path')."/albums.php?action=confirm_delete&album_id=" . $album_id, + 1, + 'delete_album' + ); + break; + case 'confirm_delete': + if (AmpConfig::get('demo_mode')) { break; } $album = new Album($_REQUEST['album_id']); - // Pull the image information - $data = array('file'=>$_FILES['file']['tmp_name']); - $image_data = Art::get_from_source($data, 'album'); - - // If we got something back insert it - if ($image_data) { - $art = new Art($album->id,'album'); - $art->insert($image_data,$_FILES['file']['type']); - show_confirmation(T_('Album Art Inserted'),'',"/albums.php?action=show&album=" . $album->id); - } - // Else it failed - else { - show_confirmation(T_('Album Art Not Located'), T_('Album Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'),"/albums.php?action=show&album=" . $album->id); + if (!Catalog::can_remove($album)) { + debug_event('album', 'Unauthorized to remove the album `.' . $album->id . '`.', 1); + UI::access_denied(); + exit; } - break; - case 'find_art': - // If not a user then kick em out - if (!Access::check('interface','25')) { UI::access_denied(); exit; } - - // Prevent the script from timing out - set_time_limit(0); - - // get the Album information - $album = new Album($_GET['album_id']); - $album->format(); - $art = new Art($album->id,'album'); - $images = array(); - $cover_url = array(); - - // If we've got an upload ignore the rest and just insert it - if (!empty($_FILES['file']['tmp_name'])) { - $path_info = pathinfo($_FILES['file']['name']); - $upload['file'] = $_FILES['file']['tmp_name']; - $upload['mime'] = 'image/' . $path_info['extension']; - $image_data = Art::get_from_source($upload, 'album'); - - if ($image_data) { - $art->insert($image_data,$upload['0']['mime']); - show_confirmation(T_('Album Art Inserted'),'',"/albums.php?action=show&album=" . $_REQUEST['album_id']); - break; - - } // if image data - - } // if it's an upload - - // Build the options for our search - if (isset($_REQUEST['artist_name'])) { - $artist = scrub_in($_REQUEST['artist_name']); - } elseif ($album->artist_count == '1') { - $artist = $album->f_artist_name; + if ($album->remove_from_disk()) { + show_confirmation(T_('Album Deletion'), T_('Album has been deleted.'), AmpConfig::get('web_path')); } else { - $artist = ""; + show_confirmation(T_('Album Deletion'), T_('Cannot delete this album.'), AmpConfig::get('web_path')); } - if (isset($_REQUEST['album_name'])) { - $album_name = scrub_in($_REQUEST['album_name']); - } else { - $album_name = $album->full_name; - } - - $options['artist'] = $artist; - $options['album_name'] = $album_name; - $options['keyword'] = trim($artist . " " . $album_name); - - // Attempt to find the art. - $images = $art->gather($options); - - if (!empty($_REQUEST['cover'])) { - $path_info = pathinfo($_REQUEST['cover']); - $cover_url[0]['url'] = scrub_in($_REQUEST['cover']); - $cover_url[0]['mime'] = 'image/' . $path_info['extension']; - } - $images = array_merge($cover_url,$images); - - // If we've found anything then go for it! - if (count($images)) { - // We don't want to store raw's in here so we need to strip them out into a separate array - foreach ($images as $index=>$image) { - if ($image['raw']) { - unset($images[$index]['raw']); - } - } // end foreach - // Store the results for further use - $_SESSION['form']['images'] = $images; - require_once AmpConfig::get('prefix') . '/templates/show_album_art.inc.php'; - } - // Else nothing - else { - show_confirmation(T_('Album Art Not Located'), T_('Album Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'),"/albums.php?action=show&album=" . $album->id); - } - - $albumname = $album->name; - $artistname = $artist; - - // Remember the last typed entry, if there was one - if (!empty($_REQUEST['album_name'])) { $albumname = scrub_in($_REQUEST['album_name']); } - if (!empty($_REQUEST['artist_name'])) { $artistname = scrub_in($_REQUEST['artist_name']); } - - require_once AmpConfig::get('prefix') . '/templates/show_get_albumart.inc.php'; - - break; - case 'select_art': - - /* Check to see if we have the image url still */ - $image_id = $_REQUEST['image']; - $album_id = $_REQUEST['album_id']; - - // Prevent the script from timing out - set_time_limit(0); - - $album = new Album($album_id); - $album_groups = $album->get_group_disks_ids(); - - $image = Art::get_from_source($_SESSION['form']['images'][$image_id], 'album'); - $mime = $_SESSION['form']['images'][$image_id]['mime']; - - foreach ($album_groups as $a_id) { - $art = new Art($a_id, 'album'); - $art->insert($image, $mime); - } - - header("Location:" . AmpConfig::get('web_path') . "/albums.php?action=show&album=" . $album_id); break; case 'update_from_tags': // Make sure they are a 'power' user at least @@ -193,7 +82,7 @@ switch ($_REQUEST['action']) { if (isset($_GET['order'])) { $songs = explode(";", $_GET['order']); - $track = 1; + $track = $_GET['offset'] ? (intval($_GET['offset']) + 1) : 1; foreach ($songs as $song_id) { if ($song_id != '') { Song::update_track($track, $song_id); diff --git a/sources/artists.php b/sources/artists.php index c6d86c8..7fb38ff 100644 --- a/sources/artists.php +++ b/sources/artists.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -28,10 +28,42 @@ UI::show_header(); * Display Switch */ switch ($_REQUEST['action']) { + case 'delete': + if (AmpConfig::get('demo_mode')) { break; } + + $artist_id = scrub_in($_REQUEST['artist_id']); + show_confirmation( + T_('Artist Deletion'), + T_('Are you sure you want to permanently delete this artist?'), + AmpConfig::get('web_path')."/artists.php?action=confirm_delete&artist_id=" . $artist_id, + 1, + 'delete_artist' + ); + break; + case 'confirm_delete': + if (AmpConfig::get('demo_mode')) { break; } + + $artist = new Artist($_REQUEST['artist_id']); + if (!Catalog::can_remove($artist)) { + debug_event('artist', 'Unauthorized to remove the artist `.' . $artist->id . '`.', 1); + UI::access_denied(); + exit; + } + + if ($artist->remove_from_disk()) { + show_confirmation(T_('Artist Deletion'), T_('Artist has been deleted.'), AmpConfig::get('web_path')); + } else { + show_confirmation(T_('Artist Deletion'), T_('Cannot delete this artist.'), AmpConfig::get('web_path')); + } + break; case 'show': $artist = new Artist($_REQUEST['artist']); $artist->format(); - $object_ids = $artist->get_albums($_REQUEST['catalog']); + if (AmpConfig::get('album_release_type')) { + $multi_object_ids = $artist->get_albums($_REQUEST['catalog'], false, true); + } else { + $object_ids = $artist->get_albums($_REQUEST['catalog']); + } $object_type = 'album'; require_once AmpConfig::get('prefix') . '/templates/show_artist.inc.php'; break; diff --git a/sources/arts.php b/sources/arts.php new file mode 100644 index 0000000..65aaffe --- /dev/null +++ b/sources/arts.php @@ -0,0 +1,169 @@ +get_user_owner() != $GLOBALS['user']->id)) { UI::access_denied(); exit; } + +/* Switch on Action */ +switch ($_REQUEST['action']) { + case 'clear_art': + $art = new Art($object_id, $object_type); + $art->reset(); + show_confirmation(T_('Art Cleared'), T_('Art information has been removed from the database'), $burl); + break; + // Upload art + case 'upload_art': + // we didn't find anything + if (empty($_FILES['file']['tmp_name'])) { + show_confirmation(T_('Art Not Located'), T_('Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'), $burl); + break; + } + + // Pull the image information + $data = array('file'=>$_FILES['file']['tmp_name']); + $image_data = Art::get_from_source($data, $object_type); + + // If we got something back insert it + if ($image_data) { + $art = new Art($object_id, $object_type); + $art->insert($image_data,$_FILES['file']['type']); + show_confirmation(T_('Art Inserted'), '', $burl); + } + // Else it failed + else { + show_confirmation(T_('Art Not Located'), T_('Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'), $burl); + } + + break; + case 'find_art': + // Prevent the script from timing out + set_time_limit(0); + + $item->format(); + $art = new Art($object_id, $object_type); + $images = array(); + $cover_url = array(); + + // If we've got an upload ignore the rest and just insert it + if (!empty($_FILES['file']['tmp_name'])) { + $path_info = pathinfo($_FILES['file']['name']); + $upload['file'] = $_FILES['file']['tmp_name']; + $upload['mime'] = 'image/' . $path_info['extension']; + $image_data = Art::get_from_source($upload, $object_type); + + if ($image_data) { + $art->insert($image_data,$upload['0']['mime']); + show_confirmation(T_('Art Inserted'), '', $burl); + break; + + } // if image data + + } // if it's an upload + + $keywords = $item->get_keywords(); + $keyword = ''; + $options = array(); + foreach ($keywords as $key => $word) { + if (isset($_REQUEST['option_' . $key])) { + $word['value'] = $_REQUEST['option_' . $key]; + } + $options[$key] = $word['value']; + if ($word['important']) { + if (!empty($word['value'])) { + $keyword .= ' ' . $word['value']; + } + } + } + $options['keyword'] = trim($keyword); + + // Attempt to find the art. + $images = $art->gather($options); + + if (!empty($_REQUEST['cover'])) { + $path_info = pathinfo($_REQUEST['cover']); + $cover_url[0]['url'] = scrub_in($_REQUEST['cover']); + $cover_url[0]['mime'] = 'image/' . $path_info['extension']; + } + $images = array_merge($cover_url, $images); + + // If we've found anything then go for it! + if (count($images)) { + // We don't want to store raw's in here so we need to strip them out into a separate array + foreach ($images as $index=>$image) { + if ($image['raw']) { + unset($images[$index]['raw']); + } + } // end foreach + // Store the results for further use + $_SESSION['form']['images'] = $images; + require_once AmpConfig::get('prefix') . '/templates/show_arts.inc.php'; + } + // Else nothing + else { + show_confirmation(T_('Art Not Located'), T_('Art could not be located at this time. This may be due to write access error, or the file is not received correctly.'), $burl); + } + + require_once AmpConfig::get('prefix') . '/templates/show_get_art.inc.php'; + + break; + case 'select_art': + + /* Check to see if we have the image url still */ + $image_id = $_REQUEST['image']; + + // Prevent the script from timing out + set_time_limit(0); + + $image = Art::get_from_source($_SESSION['form']['images'][$image_id], 'album'); + $mime = $_SESSION['form']['images'][$image_id]['mime']; + + // Special case for albums, I'm not sure if we should keep it, remove it or find a generic way + if ($object_type == 'album') { + $album = new $object_type($object_id); + $album_groups = $album->get_group_disks_ids(); + foreach ($album_groups as $a_id) { + $art = new Art($a_id, $object_type); + $art->insert($image, $mime); + } + } else { + $art = new Art($object_id, $object_type); + $art->insert($image, $mime); + } + + header("Location:" . $burl); + break; +} + +UI::show_footer(); diff --git a/sources/batch.php b/sources/batch.php index d4a9d95..14e0a9d 100644 --- a/sources/batch.php +++ b/sources/batch.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,7 +21,16 @@ */ if (!defined('NO_SESSION')) { - require_once 'lib/init.php'; + if (isset($_REQUEST['ssid'])) { + define('NO_SESSION', 1); + require_once 'lib/init.php'; + if (!Session::exists('stream', $_REQUEST['ssid'])) { + UI::access_denied(); + exit; + } + } else { + require_once 'lib/init.php'; + } } ob_end_clean(); @@ -35,72 +44,78 @@ if (!defined('NO_SESSION') && !Access::check_function('batch_download')) { set_time_limit(0); $media_ids = array(); -$name = "Unknown.zip"; +$default_name = "Unknown.zip"; +$object_type = scrub_in($_REQUEST['action']); +$name = $default_name; -switch ($_REQUEST['action']) { - case 'tmp_playlist': - $media_ids = $GLOBALS['user']->playlist->get_items(); - $name = $GLOBALS['user']->username . ' - Playlist'; - break; - case 'playlist': - $playlist = new Playlist($_REQUEST['id']); - $media_ids = $playlist->get_songs(); - $name = $playlist->name; - break; - case 'smartplaylist': - $search = new Search('song', $_REQUEST['id']); - $sql = $search->to_sql(); - $sql = $sql['base'] . ' ' . $sql['table_sql'] . ' WHERE ' . - $sql['where_sql']; - $db_results = Dba::read($sql); - while ($row = Dba::fetch_assoc($db_results)) { - $media_ids[] = $row['id']; +if ($object_type == 'browse') { + $object_type = $_REQUEST['type']; +} + +if (!check_can_zip($object_type)) { + debug_event('batch', 'Object type `' . $object_type . '` is not allowed to be zipped.', 1); + UI::access_denied(); + exit; +} + +if (Core::is_playable_item($_REQUEST['action'])) { + $id = $_REQUEST['id']; + if (!is_array($id)) { + $id = array($id); + } + $media_ids = array(); + foreach ($id as $i) { + $libitem = new $object_type($i); + if ($libitem->id) { + $libitem->format(); + $name = $libitem->get_fullname(); + $media_ids = array_merge($media_ids, $libitem->get_medias()); } - $name = $search->name; - break; - case 'album': - foreach ($_REQUEST['id'] as $a) { - $album = new Album($a); - if (empty($name)) { - $name = $album->name; - } - $asongs = $album->get_songs(); - foreach ($asongs as $song_id) { - $media_ids[] = $song_id; - } - } - break; - case 'artist': - $artist = new Artist($_REQUEST['id']); - $media_ids = $artist->get_songs(); - $name = $artist->name; - break; - case 'browse': - $id = scrub_in($_REQUEST['browse_id']); - $browse = new Browse($id); - $browse_media_ids = $browse->get_saved(); - foreach ($browse_media_ids as $media_id) { - switch ($_REQUEST['type']) { - case 'album': - $album = new Album($media_id); - $media_ids = array_merge($media_ids, $album->get_songs()); - break; - case 'song': - $media_ids[] = $media_id; - break; - case 'video': - $media_ids[] = array('Video', $media_id); - break; - } // switch on type - } // foreach media_id - $name = 'Batch-' . date("dmY",time()); - default: - // Rien a faire - break; -} // action switch + } +} else { + switch ($_REQUEST['action']) { + case 'tmp_playlist': + $media_ids = $GLOBALS['user']->playlist->get_items(); + $name = $GLOBALS['user']->username . ' - Playlist'; + break; + case 'browse': + $id = intval(scrub_in($_REQUEST['browse_id'])); + $browse = new Browse($id); + $browse_media_ids = $browse->get_saved(); + foreach ($browse_media_ids as $media_id) { + switch ($object_type) { + case 'album': + $album = new Album($media_id); + $media_ids = array_merge($media_ids, $album->get_songs()); + break; + case 'song': + $media_ids[] = $media_id; + break; + case 'video': + $media_ids[] = array('object_type' => 'Video', 'object_id' => $media_id); + break; + } // switch on type + } // foreach media_id + $name = 'Batch-' . date("dmY",time()); + default: + // Rien a faire + break; + } // action switch +} + +if (!User::stream_control($media_ids)) { + debug_event('UI::access_denied', 'Stream control failed for user ' . $GLOBALS['user']->username, '3'); + UI::access_denied(); + exit; +} + +// Write/close session data to release session lock for this script. +// This to allow other pages from the same session to be processed +// Do NOT change any session variable after this call +session_write_close(); // Take whatever we've got and send the zip -$song_files = get_song_files($media_ids); +$song_files = get_media_files($media_ids); if (is_array($song_files['0'])) { set_memory_limit($song_files['1']+32); send_zip($name,$song_files['0']); diff --git a/sources/bin/.htaccess b/sources/bin/.htaccess index 896fbc5..cfd4051 100644 --- a/sources/bin/.htaccess +++ b/sources/bin/.htaccess @@ -1,2 +1,10 @@ -Order deny,allow -Deny from all \ No newline at end of file +# Apache 2.4 + + Require all denied + + +# Apache 2.2 + + Order deny,allow + Deny from all + \ No newline at end of file diff --git a/sources/bin/broadcast.inc b/sources/bin/broadcast.inc new file mode 100644 index 0000000..0d600d0 --- /dev/null +++ b/sources/bin/broadcast.inc @@ -0,0 +1,45 @@ + diff --git a/sources/bin/calculate_art_size.inc b/sources/bin/calculate_art_size.inc new file mode 100644 index 0000000..a6cc134 --- /dev/null +++ b/sources/bin/calculate_art_size.inc @@ -0,0 +1,48 @@ + diff --git a/sources/bin/catalog_update.inc b/sources/bin/catalog_update.inc index 5807f3f..0e2c044 100644 --- a/sources/bin/catalog_update.inc +++ b/sources/bin/catalog_update.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -29,38 +29,48 @@ require_once $prefix . '/lib/init.php'; ob_end_flush(); -$catclean = 0; //All off by default +$tmpmemlimoff = 0; //All off by default +$catclean = 0; $catverify = 0; $catadd = 0; $artadd = 0; $plimp = 0; +$optimizetables = 0; if (count($_SERVER['argv']) == 1) { - $operations_string = "\n\t". T_('- All Catalog Operations'); + $operations_string = "\n\t- ". T_('Doing all catalog operations'); } if (count($_SERVER['argv']) > 1) { for ($x = 1; $x < count($_SERVER['argv']); $x++) { - if ($_SERVER['argv'][$x] == "-c") { - $operations_string .= "\n\t" . T_('- Catalog Clean'); + if ($_SERVER['argv'][$x] == "-m") { + $operations_string .= "\n\t- " . T_('Temporary deactivate PHP memory limit'); + $tmpmemlimoff = 1; + } + elseif ($_SERVER['argv'][$x] == "-c") { + $operations_string .= "\n\t- " . T_('Cleaning catalog/s'); $catclean = 1; } elseif ($_SERVER['argv'][$x] == "-v") { - $operations_string .= "\n\t" . T_('- Catalog Verify'); + $operations_string .= "\n\t- " . T_('Verifying catalog/s'); $catverify = 1; } elseif ($_SERVER['argv'][$x] == "-a") { - $operations_string .= "\n\t" . T_('- Catalog Add'); + $operations_string .= "\n\t- " . T_('Adding new media to catalog/s'); $catadd = 1; } elseif ($_SERVER['argv'][$x] == "-g") { - $operations_string .= "\n\t" . T_('- Catalog Art Gather'); - $artadd = 1; + $operations_string .= "\n\t- " . T_('Gathering new media art'); + $artadd = 1; } elseif ($_SERVER['argv'][$x] == "-i") { - $operations_string .= "\n\t" . T_('- Playlist Import'); - $plimp = 1; + $operations_string .= "\n\t- " . T_('Importing playlist/s'); + $plimp = 1; + } + elseif ($_SERVER['argv'][$x] == "-o") { + $operations_string .= "\n\t- " . T_('Database table optimization'); + $optimizetables = 1; } else { if ($where) $where .= " OR "; @@ -70,19 +80,43 @@ if (count($_SERVER['argv']) > 1) { } if (count($_SERVER['argv']) != 1 AND $artadd != 1 && $catclean != 1 && $catverify != 1 && $catadd != 1) { - usage(); - exit; + usage(); + exit; } -if ($artadd == 0 && $catclean == 0 && $catverify == 0 && $catadd == 0) { //didn't pass any clean/verify/add arguments - $catclean = 1; //set them all to on +if ($catclean == 0 && $catverify == 0 && $catadd == 0 && $artadd == 0 && $optimizetables == 0) { //didn't pass any clean/verify/add arguments + $catclean = 1; //set them all to on $catverify = 1; $catadd = 1; $artadd = 1; + $optimizetables = 1; +} +echo "\n----------------------------------------------------------\n"; +echo T_("Starting catalog operations...") . $operations_string . "\n"; +echo "----------------------------------------------------------\n\n"; + +// -------- Options before the catalog actions loop +if ($tmpmemlimoff == 1) { + // Temporarily deactivate PHP memory limit + echo "\033[31m- " . T_("Deactivated PHP memory limit") . " -\033[0m\n"; + ini_set('memory_limit','-1'); + echo "------------------\n\n"; } -echo T_("Starting Catalog Operations...") . $operations_string . "\n"; - +$options = array(); // for $catadd +if ($artadd == 1) { + echo "- " . T_("Gathering art") . " - \n"; + $options['gather_art'] = true; + } else { + $options['gather_art'] = false; +} +if ($plimp == 1) { + echo "- " . T_("Parsing playlists") . " - \n"; + $options['parse_playlist'] = true; + } else { + $options['parse_playlist'] = false; +} +// -------- if ($where) $where = "($where) AND catalog_type='local'"; else $where = "catalog_type='local'"; $sql = "SELECT id FROM catalog"; @@ -94,51 +128,49 @@ ob_start("ob_html_strip",'1024',true); while ($row = Dba::fetch_row($db_results)) { $catalog = Catalog::create_from_id($row['0']); - printf(T_('Reading: %s'), $catalog->name); - ob_flush(); - echo "\n"; + printf(T_('Reading catalog: %s'), $catalog->name); + ob_flush(); + echo "\n"; + if ($catclean == 1) { // Clean out dead files - echo T_("- Starting Clean - "); + echo "- " . T_("Start cleaning orphaned media entries") . " - \n"; echo "\n"; $catalog->clean_catalog(); echo "------------------\n\n"; } - if ($catverify == 1) { // Verify Existing - echo T_("- Starting Verify - "); + echo "- " . T_("Start verifying media related to catalog entries") . " - \n"; echo "\n"; $catalog->verify_catalog($row['0']); echo "-------------------\n\n"; } - if ($catadd == 1) { // Look for new files - echo T_("- Starting Add - "); + echo "- " . T_("Start adding new media") . " - \n"; echo "\n"; - $options = array(); - if ($artadd == 1) { - $options['gather_art'] = true; - } - if ($plimp == 1) { - $options['parse_playlist'] = true; - } $catalog->add_to_catalog($options); echo "----------------\n\n"; - } elseif ($artadd == 1) { - // Look for album art - echo T_('Starting Album Art Search'); - echo "\n"; + } + elseif ($artadd == 1) { + // Look for media art + echo "- " . T_('Start searching new media art') . " - \n"; + echo "\n"; $catalog->gather_art(); echo "----------------\n\n"; - } + } } + if ($optimizetables == 1) { + // Optimize Database Tables + echo "- " . T_('Optimizing database tables') . " - \n"; + echo "\n"; + Dba::optimize_tables(); + echo "------------------\n\n"; + } -Dba::optimize_tables(); - -ob_end_flush(); -echo "\n"; +ob_end_flush(); +echo "\n"; function ob_html_strip($string) { @@ -154,22 +186,37 @@ function ob_html_strip($string) { } // ob_html_strip function usage() { - echo T_("- Catalog Update -"); echo "\n"; - echo T_("Usage: catalog_update.inc [CATALOG NAME] [-c|-v|-a|-g|-t|-i]"); - echo "\n\t"; - echo T_("Default behavior is to do all except playlist import"); + echo "----------------------------------------------------------\n\t\t"; + echo T_("- Catalog Update Help -"); + echo "\n\033[32m"; + echo T_("Usage: catalog_update.inc [CATALOG NAME] [-m|-c|-v|-a|-g|-i|-o]") . "\033[0m (\033[31m!\033[0m)"; + echo "\033[0m\n"; + echo "----------------------------------------------------------"; + echo "\n"; + echo T_("Default behavior is to do all except temporarily deactivate the php memory limit"); + echo "\n"; + echo "----------------------------------------------------------"; + echo "\n-m\t"; + echo T_('Temporarily deactivates PHP memory limit.') . " (\033[31m1\033[0m)"; echo "\n-c\t"; - echo T_('Clean Catalogs'); + echo T_('Cleans catalogs from orphaned entries.'); echo "\n-v\t"; - echo T_('Verify Catalogs'); + echo T_('Verifies catalog entries and updates them if related files have new information.'); echo "\n-a\t"; - echo T_('Add to Catalogs'); - echo "\n-i\t"; - echo T_('Import Playlists'); + echo T_('Adds new media to catalogs.'); echo "\n-g\t"; - echo T_('Gather Art'); + echo T_('Gathers media Art.'); + echo "\n-i\t"; + echo T_('Imports playlists.'); + echo "\n-o\t"; + echo T_('Optimizes Database Tables.') . " (\033[31m2\033[0m)"; echo "\n"; + echo "----------------------------------------------------------\033[31m\n"; + echo "1. " . T_('Use this option at your own risk! Your system could crash or become unresponsive due to huge memory consumption!') . "\n"; + echo "2. " . T_('Depending on your systems performance, this option could need a long time to finish and extremely slow down other database processes if you have big catalogs!') . "\n"; + echo "! " . T_('The switches [-m|-i|-o] can only be used, if one of the other switches [-c|-v|-a|-g] is used.'); + echo "\033[0m\n"; echo "----------------------------------------------------------"; echo "\n"; } diff --git a/sources/bin/channel_run.inc b/sources/bin/channel_run.inc index 8c691ea..dca4f14 100644 --- a/sources/bin/channel_run.inc +++ b/sources/bin/channel_run.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,6 +23,8 @@ define('NO_SESSION','1'); define('CLI', 1); +$chunk_buffer = ''; +$nb_chunks_remainder = 0; $path = dirname(__FILE__); $prefix = realpath($path . '/../'); require_once $prefix . '/lib/init.php'; @@ -42,11 +44,11 @@ if ($cargv > 1) { if ($_SERVER['argv'][$x] == "-c" && ($x + 1) < $cargv) { $chanid = intval($_SERVER['argv'][++$x]); $operations_string .= "\n\t" . T_('- Channel ' . $chanid); - } + } elseif ($_SERVER['argv'][$x] == "-v") { $operations_string .= "\n\t" . T_('- Verbose'); $verbose = true; - } + } elseif ($_SERVER['argv'][$x] == "-p" && ($x + 1) < $cargv) { $port = intval($_SERVER['argv'][++$x]); $operations_string .= "\n\t" . T_('- Port ' . $port); @@ -56,7 +58,7 @@ if ($cargv > 1) { if ($chanid <= 0) { usage(); - exit; + exit; } // Transcode is mandatory to have consistent stream codec @@ -66,7 +68,7 @@ if ($transcode_cfg == 'never') { die('Cannot start channel, transcoding is mandatory to work.'); } -echo T_("Starting Channel...") . $operations_string . "\n"; +echo T_("Starting Channel...") . $operations_string . "\n"; $channel = new Channel($chanid); if (!$channel->id) { @@ -106,7 +108,7 @@ echo T_("Listening on ") . $address . ':' . $port . "\n"; $stream_clients = array(); $client_socks = array(); -$last_stream = 0; +$last_stream = microtime(true); while(true) { //prepare readable sockets @@ -114,7 +116,6 @@ while(true) if (count($client_socks) < $channel->max_listeners) { $read_socks[] = $server; } - //echo "b\n";ob_flush(); //start reading and use a large timeout if(stream_select ( $read_socks, $write, $except, 1)) @@ -123,7 +124,7 @@ while(true) if (in_array($server, $read_socks)) { $new_client = stream_socket_accept($server); - + if ($new_client) { debug_event('channel', 'Connection accepted from ' . stream_socket_get_name($new_client, true) . '.', '5'); @@ -133,288 +134,120 @@ while(true) echo "New client connected.\n"; ob_flush(); } - + //delete the server socket from the read sockets unset($read_socks[array_search($server, $read_socks)]); } - + // Get new message from existing client foreach($read_socks as $sock) { - $data = fread($sock, 1024); - if(!$data) - { - client_disconnect($channel, $client_socks, $stream_clients, $sock); - continue; - } - - $headers = explode("\n", $data); - - if (count($headers) > 0) { - $cmd = explode(" ", $headers[0]); - if ($cmd['0'] == 'GET') { - switch ($cmd['1']) { - case '/stream.' . $channel->stream_type: - $options = array( - 'socket' => $sock, - 'length' => 0 - ); - - for ($i = 1; $i < count($headers); $i++) { - $headerpart = explode(":", $headers[$i], 2); - $header = strtolower(trim($headerpart[0])); - $value = trim($headerpart[1]); - switch ($header) { - case 'icy-metadata': - $options['metadata'] = ($value == '1'); - $options['metadata_lastsent'] = 0; - $options['metadata_lastsong'] = 0; - break; - } - } - - // Stream request - if ($options['metadata']) { - //fwrite($sock, "ICY 200 OK\r\n"); - fwrite($sock, "HTTP/1.0 200 OK\r\n"); - } else { - fwrite($sock, "HTTP/1.1 200 OK\r\n"); - fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n"); - } - fwrite($sock, "Content-Type: " . Song::type_to_mime($transcode_to) . "\r\n"); - fwrite($sock, "Accept-Ranges: none\r\n"); - - $genre = $channel->get_genre(); - // Send Shoutcast metadata on demand - if ($options['metadata']) { - fwrite($sock, "icy-notice1: " . AmpConfig::get('title') . "\r\n"); - fwrite($sock, "icy-name: " . $channel->name . "\r\n"); - if (!empty($genre)) { - fwrite($sock, "icy-genre: " . $genre . "\r\n"); - } - fwrite($sock, "icy-url: " . $channel->url . "\r\n"); - fwrite($sock, "icy-pub: " . ($channel->is_private) ? '0' : '1' . "\r\n"); - if ($channel->bitrate) { - fwrite($sock, "icy-br: " . strval($channel->bitrate) . "\r\n"); - } - fwrite($sock, "icy-metaint: " . strval($metadata_interval) . "\r\n"); - } - // Send additional Icecast metadata - fwrite($sock, "x-audiocast-server-url: " . $channel->url . "\r\n"); - fwrite($sock, "x-audiocast-name: " . $channel->name . "\r\n"); - fwrite($sock, "x-audiocast-description: " . $channel->description . "\r\n"); - fwrite($sock, "x-audiocast-url: " . $channel->url . "\r\n"); - if (!empty($genre)) { - fwrite($sock, "x-audiocast-genre: " . $genre . "\r\n"); - } - fwrite($sock, "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n"); - fwrite($sock, "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n"); - - fwrite($sock, "\r\n"); - - // Add to stream clients list - $key = array_search($sock, $read_socks); - $stream_clients[$key] = $options; - break; - - case '/': - case '/status.xsl': - // Stream request - fwrite($sock, "HTTP/1.0 200 OK\r\n"); - fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n"); - fwrite($sock, "Content-Type: text/html\r\n"); - fwrite($sock, "\r\n"); - - // Create xsl structure - - // Header - $xsl = ""; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "Icecast Streaming Media Server - Ampache" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "
" . "\n"; - - // Content - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "\"\"" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "

Mount Point /stream." . $channel->stream_type . "

" . "\n"; - $xsl .= "stream_type .".m3u\">M3U" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $genre = $channel->get_genre(); - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $currentsong = ""; - if ($channel->media) { - $currentsong = $channel->media->f_artist . " - " . $channel->media->f_title; - } - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "
Stream Title:" . $channel->name . "
Stream Description:" . $channel->description . "
Content Type:" . Song::type_to_mime($channel->stream_type) . "
Mount Start:" . date("c", $channel->start_date) . "
Bitrate:" . $channel->bitrate . "
Current Listeners:" . $channel->listeners . "
Peak Listeners:" . $channel->peak_listeners . "
Stream Genre:" . $genre . "
Stream URL:url . "\" target=\"_blank\">" . $channel->url . "
Current Song:" . $currentsong . "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "\"\"" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "

" . "\n"; - - // Footer - $xsl .= "
" . "\n"; - $xsl .= "Support Icecast development at www.icecast.org" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "
" . "\n"; - $xsl .= "" . "\n"; - $xsl .= "" . "\n"; - - fwrite($sock, $xsl); - - fclose($sock); - unset($client_socks[array_search($sock, $client_socks)]); - break; - - case '/style.css': - case '/favicon.ico': - case '/images/corner_bottomleft.jpg': - case '/images/corner_bottomright.jpg': - case '/images/corner_topleft.jpg': - case '/images/corner_topright.jpg': - case '/images/icecast.png': - case '/images/key.png': - case '/images/tunein.png': - // Get read file data - $fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1']; - $pinfo = pathinfo($fpath); - - $content_type = 'text/html'; - switch ($pinfo['extension']) { - case 'css': - $content_type = "text/css"; - break; - case 'jpg': - $content_type = "image/jpeg"; - break; - case 'png': - $content_type = "image/png"; - break; - case 'ico': - $content_type = "image/vnd.microsoft.icon"; - break; - } - fwrite($sock, "HTTP/1.0 200 OK\r\n"); - fwrite($sock, "Content-Type: " . $content_type . "\r\n"); - $fdata = file_get_contents($fpath); - fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n"); - fwrite($sock, "\r\n"); - fwrite($sock, $fdata); - fclose($sock); - unset($client_socks[array_search($sock, $client_socks)]); - break; - case '/stream.' . $channel->stream_type . '.m3u': - fwrite($sock, "HTTP/1.0 200 OK\r\n"); - fwrite($sock, "Cache-control: public\r\n"); - fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n"); - fwrite($sock, "Content-Type: audio/x-mpegurl\r\n"); - fwrite($sock, "\r\n"); - - fwrite($sock, $channel->get_stream_url() . "\n"); - - fclose($sock); - unset($client_socks[array_search($sock, $client_socks)]); - break; - default: - debug_event('channel', 'Unknown request. Closing connection.', '3'); - fclose($sock); - unset($client_socks[array_search($sock, $client_socks)]); - break; - } - } - } // Handle data parse + http_serve($channel, $client_socks, $stream_clients, $read_socks, $sock); } } - + if ($channel->bitrate) { + $time_offset = microtime(true) - $last_stream; - //$mtime = ($last_stream > 0 && $time_offset < 1000000) ? $time_offset : 1; - $mtime = 1; - if ($last_stream > 0 && $time_offset < 1) { - usleep(1000000 - ($time_offset * 1000000)); - } elseif ($last_stream > 0) { - //$mtime = $time_offset; + + //debug_event('channel', 'time_offset : '. $time_offset, '5'); + //debug_event('channel', 'last_stream: '.$last_stream, '5'); + + if ($time_offset < 1) + usleep(1000000 - ($time_offset * 1000000)); // always at least 1 second between cycles + + $last_stream = microtime(true); + $mtime = ($time_offset > 1) ? $time_offset : 1; + $nb_chunks = ceil(($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8) / $channel->chunk_size); // channel->bitrate+1% ... leave some headroom for metadata / headers + + // we only send full blocks, save remainder and apply when appropriate: allows more granular/arbitrary average bitrates + if ($nb_chunks - ($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8 / $channel->chunk_size) > 0) + $nb_chunks_remainder += $nb_chunks - ($mtime * $channel->bitrate * 1000 / 8 / $channel->chunk_size); + if ($nb_chunks >= 1 && $nb_chunks_remainder >= 1){ + $nb_chunks -= 1; + $nb_chunks_remainder -= 1; + //debug_event('channel', 'REMAINDER: '.$nb_chunks_remainder, '5'); } - $nb_chunks = ceil(($mtime * $channel->bitrate * 1000) / 4096); + //debug_event('channel', 'mtime '.$mtime, '5'); + //debug_event('channel', 'nb_chunks: '.$nb_chunks, '5'); + } else { $nb_chunks = 1; } - + // Get multiple chunks according to bitrate to return enough data per second (because sleep with socket select) for ($c = 0; $c < $nb_chunks; $c++) { + $chunk = $channel->get_chunk(); $chunklen = strlen($chunk); + $chunk_buffer .= $chunk; + + //buffer maintenance + while (strlen($chunk_buffer) > (15 * $nb_chunks * $channel->chunk_size) ){ // buffer 15 seconds + + if (strtolower($channel->stream_type) == "ogg" && strtohex(substr($chunk_buffer, 0, 4)) == "4F676753") { //maintain ogg chunk alignment --- "4F676753" == "OggS" + // read OggS segment length + $hex = strtohex(substr($chunk_buffer, 0, 27)); + $ogg_nr_of_segments = hexdec(substr($hex, 26*2, 2)); + $hex .= strtohex(substr($chunk_buffer, 27, $ogg_nr_of_segments)); + $ogg_sum_segm_laces = 0; + for($segm = 0; $segm < $ogg_nr_of_segments; $segm++){ + $ogg_sum_segm_laces += hexdec(substr($hex, 27*2 + $segm*2, 2)); + } + //$naive = strpos(substr($chunk_buffer, 4), 'OggS') + 4; // naive search for next header + //remove 1 whole OggS chunk + $chunk_buffer = substr($chunk_buffer, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces); + //debug_event('channel', '$new chunk buffer : '.substr($chunk_buffer,0,300) . ' $hex: '.strtohex(substr($chunk_buffer,0,600)) . ' $ogg_nr_of_segments: ' .$ogg_nr_of_segments . ' bytes cut off: '.(27 + $ogg_nr_of_segments + $ogg_sum_segm_laces) . ' naive: ' .$naive, '5'); + } elseif (strtolower($channel->stream_type) == "ogg") { + debug_event('channel', 'Ogg alignament broken! Trying repair...', '5'); + $manual_search = strpos($chunk_buffer, 'OggS'); + $chunk_buffer = substr($chunk_buffer, $manual_search); + } else { // no chunk alignment required + $chunk_buffer = substr($chunk_buffer, $chunklen); + } + //debug_event('channel', 'remvd chunk from buffer ', '5'); + } + if ($chunklen > 0) { foreach($stream_clients as $key => $client) { $sock = $client['socket']; + $clchunk = $chunk; + if(!is_resource($sock)) { client_disconnect($channel, $client_socks, $stream_clients, $sock); continue; } - - $clchunk = $chunk; + + if ($client['isnew'] == 1){ + $client['isnew'] = 0; + //fwrite($sock, $channel->header_chunk); + //debug_event('channel', 'IS NEW' . $channel->header_chunk, '5'); + $clchunk_buffer = $channel->header_chunk . $chunk_buffer; + if ($client['metadata']){ //stub + //if (strtolower($channel->stream_type) == "ogg") + while(strlen($clchunk_buffer) > $metadata_interval){ + fwrite($sock, substr($clchunk_buffer, 0, $metadata_interval) . chr(0x00)); + $clchunk_buffer = substr($clchunk_buffer, $metadata_interval); + } + fwrite($sock, $clchunk_buffer); + $client['metadata_lastsent'] = 0; + $client['length'] += strlen($clchunk_buffer); + } else { + //fwrite($sock, $channel->header_chunk); + $buffer_bytes_written = fwrite($sock, $clchunk_buffer); + while ($buffer_bytes_written != strlen($clchunk_buffer)){ + debug_event('channel', 'I HERPED WHEN I SHOULD HAVE DERPED!', '5'); + //debug_event('channel', 'chunk_buffer bytes written:' .$buffer_bytes_written .'strlen $chunk_buffer: '.strlen($chunk_buffer), '5'); + $clchunk_buffer = substr($clchunk_buffer, $buffer_bytes_written); + $buffer_bytes_written = fwrite($sock, $clchunk_buffer); + } + } + $stream_clients[$key] = $client; + continue; + } + // Check if we need to insert metadata information if ($client['metadata']) { $chkmdlen = ($client['length'] + $chunklen) - $client['metadata_lastsent']; @@ -437,24 +270,23 @@ while(true) $clchunk = substr($chunk, $subpos); } } + if (strlen($clchunk) > 0) { fwrite($sock, $clchunk); $client['length'] += strlen($clchunk); } - $stream_clients[$key] = $client; //debug_event('channel', 'Client stream current length: ' . $client['length'], '5'); } } else { $channel->update_listeners(0); + debug_event('channel', 'No more data, stream ended.', 5); die('No more data, stream ended.'); } - - $last_stream = microtime(true); } } -ob_end_flush(); +ob_end_flush(); echo "\n"; function client_disconnect($channel, &$client_socks, &$stream_clients, $sock) @@ -474,7 +306,7 @@ function usage() echo T_("- Channel Listening -"); echo "\n"; echo T_("Usage: channel_run.inc [-c {CHANNEL ID}|-p {PORT}|-v]"); - echo "\n\t"; + echo "\n\t"; echo "\n-c {CHANNEL ID}\t"; echo T_('Channel id to start'); echo "\n-p {PORT}\t"; @@ -486,4 +318,261 @@ function usage() echo "\n"; } +function http_serve($channel, &$client_socks, &$stream_clients, &$read_socks, $sock) +{ + $data = fread($sock, 1024); + if(!$data) + { + client_disconnect($channel, $client_socks, $stream_clients, $sock); + return; + } + + $headers = explode("\n", $data); + + if (count($headers) > 0) { + $cmd = explode(" ", $headers[0]); + if ($cmd['0'] == 'GET') { + switch ($cmd['1']) { + case '/stream.' . $channel->stream_type: + $options = array( + 'socket' => $sock, + 'length' => 0, + 'isnew' => 1 + ); + + //debug_event('channel', 'HTTP HEADERS: '.$data,'5'); + for ($i = 1; $i < count($headers); $i++) { + $headerpart = explode(":", $headers[$i], 2); + $header = strtolower(trim($headerpart[0])); + $value = trim($headerpart[1]); + switch ($header) { + case 'icy-metadata': + $options['metadata'] = ($value == '1'); + $options['metadata_lastsent'] = 0; + $options['metadata_lastsong'] = 0; + break; + } + } + + // Stream request + if ($options['metadata']) { + //$http = "ICY 200 OK\r\n"); + $http = "HTTP/1.0 200 OK\r\n"; + } else { + $http = "HTTP/1.1 200 OK\r\n"; + $http .= "Cache-Control: no-store, no-cache, must-revalidate\r\n"; + } + $http .= "Content-Type: " . Song::type_to_mime($channel->stream_type) . "\r\n"; + $http .= "Accept-Ranges: none\r\n"; + + $genre = $channel->get_genre(); + // Send Shoutcast metadata on demand + //if ($options['metadata']) { + $http .= "icy-notice1: " . AmpConfig::get('site_title') . "\r\n"; + $http .= "icy-name: " . $channel->name . "\r\n"; + if (!empty($genre)) { + $http .= "icy-genre: " . $genre . "\r\n"; + } + $http .= "icy-url: " . $channel->url . "\r\n"; + $http .= "icy-pub: " . (($channel->is_private) ? "0" : "1") . "\r\n"; + if ($channel->bitrate) { + $http .= "icy-br: " . strval($channel->bitrate) . "\r\n"; + } + global $metadata_interval; + $http .= "icy-metaint: " . strval($metadata_interval) . "\r\n"; + //} + // Send additional Icecast metadata + $http .= "x-audiocast-server-url: " . $channel->url . "\r\n"; + $http .= "x-audiocast-name: " . $channel->name . "\r\n"; + $http .= "x-audiocast-description: " . $channel->description . "\r\n"; + $http .= "x-audiocast-url: " . $channel->url . "\r\n"; + if (!empty($genre)) { + $http .= "x-audiocast-genre: " . $genre . "\r\n"; + } + $http .= "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n"; + $http .= "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n"; + + $http .= "\r\n"; + + fwrite($sock, $http); + + // Add to stream clients list + $key = array_search($sock, $read_socks); + $stream_clients[$key] = $options; + break; + + case '/': + case '/status.xsl': + // Stream request + fwrite($sock, "HTTP/1.0 200 OK\r\n"); + fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n"); + fwrite($sock, "Content-Type: text/html\r\n"); + fwrite($sock, "\r\n"); + + // Create xsl structure + + // Header + $xsl = ""; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "Icecast Streaming Media Server - Ampache" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "
" . "\n"; + + // Content + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "\"\"" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "

Mount Point /stream." . $channel->stream_type . "

" . "\n"; + $xsl .= "stream_type .".m3u\">M3U" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $genre = $channel->get_genre(); + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $currentsong = ""; + if ($channel->media) { + $currentsong = $channel->media->f_artist . " - " . $channel->media->f_title; + } + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "
Stream Title:" . $channel->name . "
Stream Description:" . $channel->description . "
Content Type:" . Song::type_to_mime($channel->stream_type) . "
Mount Start:" . date("c", $channel->start_date) . "
Bitrate:" . $channel->bitrate . "
Current Listeners:" . $channel->listeners . "
Peak Listeners:" . $channel->peak_listeners . "
Stream Genre:" . $genre . "
Stream URL:url . "\" target=\"_blank\">" . $channel->url . "
Current Song:" . $currentsong . "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "\"\"" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "

" . "\n"; + + // Footer + $xsl .= "
" . "\n"; + $xsl .= "Support Ampache at www.ampache.org" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "
" . "\n"; + $xsl .= "" . "\n"; + $xsl .= "" . "\n"; + + fwrite($sock, $xsl); + + fclose($sock); + unset($client_socks[array_search($sock, $client_socks)]); + break; + + case '/style.css': + case '/favicon.ico': + case '/images/corner_bottomleft.jpg': + case '/images/corner_bottomright.jpg': + case '/images/corner_topleft.jpg': + case '/images/corner_topright.jpg': + case '/images/icecast.png': + case '/images/key.png': + case '/images/tunein.png': + // Get read file data + $fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1']; + $pinfo = pathinfo($fpath); + + $content_type = 'text/html'; + switch ($pinfo['extension']) { + case 'css': + $content_type = "text/css"; + break; + case 'jpg': + $content_type = "image/jpeg"; + break; + case 'png': + $content_type = "image/png"; + break; + case 'ico': + $content_type = "image/vnd.microsoft.icon"; + break; + } + fwrite($sock, "HTTP/1.0 200 OK\r\n"); + fwrite($sock, "Content-Type: " . $content_type . "\r\n"); + $fdata = file_get_contents($fpath); + fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n"); + fwrite($sock, "\r\n"); + fwrite($sock, $fdata); + fclose($sock); + unset($client_socks[array_search($sock, $client_socks)]); + break; + case '/stream.' . $channel->stream_type . '.m3u': + fwrite($sock, "HTTP/1.0 200 OK\r\n"); + fwrite($sock, "Cache-control: public\r\n"); + fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n"); + fwrite($sock, "Content-Type: audio/x-mpegurl\r\n"); + fwrite($sock, "\r\n"); + + fwrite($sock, $channel->get_stream_url() . "\n"); + + fclose($sock); + unset($client_socks[array_search($sock, $client_socks)]); + break; + default: + debug_event('channel', 'Unknown request. Closing connection.', '3'); + fclose($sock); + unset($client_socks[array_search($sock, $client_socks)]); + break; + } + } + } +} + +function strtohex($x) { + $s=''; + foreach(str_split($x) as $c) $s.=sprintf("%02X",ord($c)); + return($s); +} + ?> diff --git a/sources/bin/delete_disabled.inc b/sources/bin/delete_disabled.inc index 38601c2..f8932bf 100644 --- a/sources/bin/delete_disabled.inc +++ b/sources/bin/delete_disabled.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/dump_album_art.inc b/sources/bin/dump_album_art.inc index 8238975..e385eec 100644 --- a/sources/bin/dump_album_art.inc +++ b/sources/bin/dump_album_art.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/fix_filenames.inc b/sources/bin/fix_filenames.inc index 5aef3b3..8727235 100644 --- a/sources/bin/fix_filenames.inc +++ b/sources/bin/fix_filenames.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -75,7 +75,7 @@ while ($row = Dba::fetch_assoc($db_results)) { } // end of the catalogs -echo T_('Finished checking filenames for valid chacters'); +echo T_('Finished checking file names for valid characters'); echo "\n"; /************************************************** @@ -191,13 +191,13 @@ function charset_rename_file($full_file,$translated_filename) { $results = copy($full_file,$translated_filename); if (!$results) { - echo T_('Error: Copy Failed, not deleteing old file'); + echo T_('Error: Copy Failed, not deleting old file'); echo "\n"; return false; } - $old_sum = filesize($full_file); - $new_sum = filesize($translated_filename); + $old_sum = Core::get_filesize($full_file); + $new_sum = Core::get_filesize($translated_filename); if ($old_sum != $new_sum OR !$new_sum) { printf (T_('Error: Size Inconsistency, not deleting %s'), $full_file); diff --git a/sources/bin/install/.htaccess b/sources/bin/install/.htaccess deleted file mode 100644 index 3a42882..0000000 --- a/sources/bin/install/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Deny from all diff --git a/sources/bin/install/add_user.inc b/sources/bin/install/add_user.inc index 570afe3..dd1bae1 100644 --- a/sources/bin/install/add_user.inc +++ b/sources/bin/install/add_user.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/install/install_db.inc b/sources/bin/install/install_db.inc index bd1fb9f..2ec1816 100644 --- a/sources/bin/install/install_db.inc +++ b/sources/bin/install/install_db.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/install/update_db.inc b/sources/bin/install/update_db.inc index 997558d..e32f22b 100644 --- a/sources/bin/install/update_db.inc +++ b/sources/bin/install/update_db.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/migrate_config.inc b/sources/bin/migrate_config.inc index 55af8b8..4060a8f 100644 --- a/sources/bin/migrate_config.inc +++ b/sources/bin/migrate_config.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/print_tags.inc b/sources/bin/print_tags.inc index 3890975..243fc6e 100644 --- a/sources/bin/print_tags.inc +++ b/sources/bin/print_tags.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -53,7 +53,7 @@ $catalog = Catalog::create_from_id($results['id']); $dir_pattern = $catalog->sort_pattern; $file_pattern = $catalog->rename_pattern; -$info = new vainfo($filename, '', '', '', $dir_pattern, $file_pattern); +$info = new vainfo($filename, array('music'), '', '', '', $dir_pattern, $file_pattern); if(isset($dir_pattern) || isset($file_pattern)) { printf(T_('Using: %s AND %s for file pattern matching'), $dir_pattern, $file_pattern); print "\n"; diff --git a/sources/bin/sort_files.inc b/sources/bin/sort_files.inc index ff449c7..1bf993f 100644 --- a/sources/bin/sort_files.inc +++ b/sources/bin/sort_files.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -65,7 +65,6 @@ while ($r = Dba::fetch_row($db_results)) { foreach ($songs as $song) { /* Find this poor song a home */ $song->format(); - $song->format_pattern(); $directory = sort_find_home($song,$catalog->sort_pattern,$catalog->path); $filename = $song->f_file; $fullpath = $directory . "/" . $filename; @@ -268,12 +267,12 @@ function sort_move_file($song,$fullname) { /* Look for the folder art and copy that as well */ if (!AmpConfig::get('album_art_preferred_filename') OR strstr(AmpConfig::get('album_art_preferred_filename'),"%")) { - $folder_art = $directory . '/folder.jpg'; - $old_art = $old_dir . '/folder.jpg'; + $folder_art = $directory . DIRECTORY_SEPARATOR . 'folder.jpg'; + $old_art = $old_dir . DIRECTORY_SEPARATOR . 'folder.jpg'; } else { - $folder_art = $directory . "/" . sort_clean_name(AmpConfig::get('album_art_preferred_filename')); - $old_art = $old_dir . "/" . sort_clean_name(AmpConfig::get('album_art_preferred_filename')); + $folder_art = $directory . DIRECTORY_SEPARATOR . sort_clean_name(AmpConfig::get('album_art_preferred_filename')); + $old_art = $old_dir . DIRECTORY_SEPARATOR . sort_clean_name(AmpConfig::get('album_art_preferred_filename')); } debug_event('copy_art','Copied ' . $old_art . ' to ' . $folder_art,'5'); @@ -282,8 +281,8 @@ function sort_move_file($song,$fullname) { if (!$results) { printf (T_('Error: Unable to copy file to %s'), $fullname); echo "\n"; return false; } /* Check the filesize */ - $new_sum = filesize($fullname); - $old_sum = filesize($song->file); + $new_sum = Core::get_filesize($fullname); + $old_sum = Core::get_filesize($song->file); if ($new_sum != $old_sum OR !$new_sum) { printf (T_('Error: Size Inconsistency, not deleting %s'), $song->file); diff --git a/sources/bin/websocket_run.inc b/sources/bin/websocket_run.inc index 4bf2d7b..56718d2 100644 --- a/sources/bin/websocket_run.inc +++ b/sources/bin/websocket_run.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/bin/write_playlists.inc b/sources/bin/write_playlists.inc index b952ee6..c585186 100644 --- a/sources/bin/write_playlists.inc +++ b/sources/bin/write_playlists.inc @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -35,7 +35,7 @@ else { // Make sure the output dir is valid and writeable if (!is_writeable($dirname)) { - printf (T_('Error: Directory %s not writeable'), $dirname); + printf (T_('Error: Directory %s is not writable'), $dirname); echo "\n"; } diff --git a/sources/broadcast.php b/sources/broadcast.php index 2328216..d81d06c 100644 --- a/sources/broadcast.php +++ b/sources/broadcast.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,12 +22,16 @@ require_once 'lib/init.php'; +if (!AmpConfig::get('broadcast')) { + UI::access_denied(); + exit; +} + UI::show_header(); /* Switch on the action passed in */ switch ($_REQUEST['action']) { case 'show_delete': - UI::show_header(); $id = $_REQUEST['id']; $next_url = AmpConfig::get('web_path') . '/broadcast.php?action=delete&id=' . scrub_out($id); @@ -40,7 +44,6 @@ switch ($_REQUEST['action']) { exit; } - UI::show_header(); $id = $_REQUEST['id']; $broadcast = new Broadcast($id); if ($broadcast->delete()) { diff --git a/sources/browse.php b/sources/browse.php index 01a8c1b..8cfd61f 100644 --- a/sources/browse.php +++ b/sources/browse.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -49,6 +49,14 @@ switch ($_REQUEST['action']) { case 'song': case 'channel': case 'broadcast': + case 'tvshow': + case 'tvshow_season': + case 'tvshow_episode': + case 'movie': + case 'clip': + case 'personal_video': + case 'label': + case 'pvmsg': $browse->set_type($_REQUEST['action']); $browse->set_simple_browse(true); break; @@ -56,6 +64,9 @@ switch ($_REQUEST['action']) { UI::show_header(); +// Browser is able to save page on current session. Only applied to main menus. +$browse->set_update_session(true); + switch ($_REQUEST['action']) { case 'file': break; @@ -65,20 +76,22 @@ switch ($_REQUEST['action']) { $browse->set_filter('catalog_enabled', '1'); } $browse->set_sort('name','ASC'); + $browse->update_browse_from_session(); // Update current index depending on what is in session. $browse->show_objects(); break; case 'tag': //FIXME: This whole thing is ugly, even though it works. $browse->set_sort('count','ASC'); // This one's a doozy + $browse_type = isset($_REQUEST['type']) ? $_REQUEST['type'] : 'song'; $browse->set_simple_browse(false); - $browse->save_objects(Tag::get_tags(/*AmpConfig::get('offset_limit')*/)); // Should add a pager? + $browse->save_objects(Tag::get_tags($browse_type, 0, 'name')); // Should add a pager? $object_ids = $browse->get_saved(); $keys = array_keys($object_ids); Tag::build_cache($keys); UI::show_box_top(T_('Tag Cloud'), 'box box_tag_cloud'); $browse2 = new Browse(); - $browse2->set_type('song'); + $browse2->set_type($browse_type); $browse2->store(); require_once AmpConfig::get('prefix') . '/templates/show_tagcloud.inc.php'; UI::show_box_bottom(); @@ -91,6 +104,7 @@ switch ($_REQUEST['action']) { $browse->set_filter('catalog_enabled', '1'); } $browse->set_sort('name','ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'song': @@ -99,6 +113,7 @@ switch ($_REQUEST['action']) { $browse->set_filter('catalog_enabled', '1'); } $browse->set_sort('title','ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'live_stream': @@ -106,6 +121,7 @@ switch ($_REQUEST['action']) { $browse->set_filter('catalog_enabled', '1'); } $browse->set_sort('name','ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'catalog': @@ -114,19 +130,23 @@ switch ($_REQUEST['action']) { case 'playlist': $browse->set_sort('type','ASC'); $browse->set_filter('playlist_type','1'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'smartplaylist': $browse->set_sort('type', 'ASC'); $browse->set_filter('playlist_type','1'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'channel': $browse->set_sort('id', 'ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'broadcast': $browse->set_sort('id', 'ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; case 'video': @@ -134,8 +154,54 @@ switch ($_REQUEST['action']) { $browse->set_filter('catalog_enabled', '1'); } $browse->set_sort('title','ASC'); + $browse->update_browse_from_session(); $browse->show_objects(); break; + case 'tvshow': + if (AmpConfig::get('catalog_disable')) { + $browse->set_filter('catalog_enabled', '1'); + } + $browse->set_sort('name','ASC'); + $browse->update_browse_from_session(); + $browse->show_objects(); + break; + case 'tvshow_season': + if (AmpConfig::get('catalog_disable')) { + $browse->set_filter('catalog_enabled', '1'); + } + $browse->set_sort('season_number','ASC'); + $browse->update_browse_from_session(); + $browse->show_objects(); + break; + case 'tvshow_episode': + case 'movie': + case 'clip': + case 'personal_video': + if (AmpConfig::get('catalog_disable')) { + $browse->set_filter('catalog_enabled', '1'); + } + $browse->update_browse_from_session(); + $browse->show_objects(); + break; + case 'label': + if (AmpConfig::get('catalog_disable')) { + $browse->set_filter('catalog_enabled', '1'); + } + $browse->set_sort('name','ASC'); + $browse->update_browse_from_session(); + $browse->show_objects(); + break; + case 'pvmsg': + $browse->set_sort('creation_date','DESC'); + $folder = $_REQUEST['folder']; + if ($folder === "sent") { + $browse->set_filter('user', $GLOBALS['user']->id); + } else { + $browse->set_filter('to_user', $GLOBALS['user']->id); + } + $browse->update_browse_from_session(); + $browse->show_objects(); + break; default: break; diff --git a/sources/channel.php b/sources/channel.php index c58854d..ddaa125 100644 --- a/sources/channel.php +++ b/sources/channel.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,13 +22,16 @@ require_once 'lib/init.php'; +if (!AmpConfig::get('channel')) { + UI::access_denied(); + exit; +} + UI::show_header(); /* Switch on the action passed in */ switch ($_REQUEST['action']) { case 'show_create': - UI::show_header(); - $type = Channel::format_type($_REQUEST['type']); if (!empty($type) && !empty($_REQUEST['id'])) { $object = new $type($_REQUEST['id']); @@ -50,7 +53,6 @@ switch ($_REQUEST['action']) { exit; } - UI::show_header(); $created = Channel::create($_REQUEST['name'], $_REQUEST['description'], $_REQUEST['url'], $_REQUEST['type'], $_REQUEST['id'], $_REQUEST['interface'], $_REQUEST['port'], $_REQUEST['admin_password'], $_REQUEST['private'] ?: 0, $_REQUEST['max_listeners'], $_REQUEST['random'] ?: 0, $_REQUEST['loop'] ?: 0, $_REQUEST['stream_type'], $_REQUEST['bitrate']); if (!$created) { @@ -62,7 +64,6 @@ switch ($_REQUEST['action']) { UI::show_footer(); exit; case 'show_delete': - UI::show_header(); $id = $_REQUEST['id']; $next_url = AmpConfig::get('web_path') . '/channel.php?action=delete&id=' . scrub_out($id); @@ -75,7 +76,6 @@ switch ($_REQUEST['action']) { exit; } - UI::show_header(); $id = $_REQUEST['id']; $channel = new Channel($id); if ($channel->delete()) { diff --git a/sources/channel/.htaccess b/sources/channel/.htaccess.dist similarity index 57% rename from sources/channel/.htaccess rename to sources/channel/.htaccess.dist index 0eb7988..e60d35c 100644 --- a/sources/channel/.htaccess +++ b/sources/channel/.htaccess.dist @@ -2,5 +2,5 @@ RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-s - RewriteRule ^([0-9]+)/(.*)$ index.php?channel=$1&target=$2 [PT,L,QSA] + RewriteRule ^([0-9]+)/(.*)$ /channel/index.php?channel=$1&target=$2 [PT,L,QSA] \ No newline at end of file diff --git a/sources/channel/index.php b/sources/channel/index.php index 4f061a1..0a9386b 100644 --- a/sources/channel/index.php +++ b/sources/channel/index.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/channel/style.css b/sources/channel/style.css index 7a10708..cae79e4 100644 --- a/sources/channel/style.css +++ b/sources/channel/style.css @@ -76,10 +76,6 @@ html, body { margin-bottom: 10px; background: url(images/icecast.png) no-repeat left center; } -.main iframe { - width: 100%; - border: 0; -} .news { font-family: Verdana, sans-serif; text-decoration: none; diff --git a/sources/config/.gitignore b/sources/config/.gitignore deleted file mode 100644 index 654ab29..0000000 --- a/sources/config/.gitignore +++ /dev/null @@ -1 +0,0 @@ -ampache.cfg.php diff --git a/sources/config/.htaccess b/sources/config/.htaccess index 896fbc5..cfd4051 100644 --- a/sources/config/.htaccess +++ b/sources/config/.htaccess @@ -1,2 +1,10 @@ -Order deny,allow -Deny from all \ No newline at end of file +# Apache 2.4 + + Require all denied + + +# Apache 2.2 + + Order deny,allow + Deny from all + \ No newline at end of file diff --git a/sources/config/ampache.cfg.php.dist b/sources/config/ampache.cfg.php.dist index 07a0102..c27949a 100644 --- a/sources/config/ampache.cfg.php.dist +++ b/sources/config/ampache.cfg.php.dist @@ -4,35 +4,41 @@ ;################### ; This value is used to detect quickly -; if this config file is up to date +; if this config file is up to date ; this is compared against a value hard-coded ; into the init script -config_version = 16 +config_version = 29 ;################### ; Path Vars # ;################### -; The http host of your server. +; The public http host of your server. ; If not set, retrieved automatically from client request. ; This setting is required for WebSocket server ; DEFAULT: "" ;http_host = "localhost" -; The path to your ampache install -; Do not put a trailing / on this path +; The public path to your ampache install +; Do not put a trailing / on this path ; For example if your site is located at http://localhost ; than you do not need to enter anything for the web_path -; if it is located at http://localhost/music you need to +; if it is located at http://localhost/music you need to ; set web_path to /music ; DEFAULT: "" ;web_path = "" +; The local http url of your server. +; If not set, retrieved automatically from server information. +; DEFAULT: "" +;local_web_path = "http://localhost/ampache" + ;############################## ; Session and Login Variables # ;############################## ; Hostname of your database +; For socket authentication, set the path to socket file (e.g. /var/run/mysqld/mysqld.sock) ; DEFAULT: localhost database_hostname = localhost @@ -50,27 +56,31 @@ database_username = username ; Password for your ampache database, this can not be blank ; this is a 'forced' security precaution, the default value -; will not work +; will not work (except if using socket authentication) ; DEFAULT: "" database_password = password +; Cryptographic secret +; This MUST BE changed with your own secret key. Ampache-specific, just pick any random string you want. +secret_key = "abcdefghijklmnoprqstuvwyz0123456" + ; Length that a session will last expressed in seconds. Default is -; one hour. +; one hour. ; DEFAULT: 3600 session_length = 3600 ; Length that the session for a single streaming instance will last -; the default is two hours. With some clients, and long songs this can +; the default is two hours. With some clients, and long songs this can ; cause playback to stop, increase this value if you experience that ; DEFAULT: 7200 stream_length = 7200 -; This length defines how long a 'remember me' session and cookie will -; last, the default is 7200, same as length. It is up to the administrator -; of the box to increase this, for reference 86400 = 1 day -; 604800 = 1 week and 2419200 = 1 month -; DEFAULT: 86400 -remember_length = 86400 +; This length defines how long a 'remember me' session and cookie will +; last, the default is 86400, same as length. It is up to the administrator +; of the box to increase this, for reference 86400 = 1 day, +; 604800 = 1 week, and 2419200 = 1 month +; DEFAULT: 604800 +remember_length = 604800 ; Name of the Session/Cookie that will sent to the browser ; default should be fine @@ -78,16 +88,16 @@ remember_length = 86400 session_name = ampache ; Lifetime of the Cookie, 0 == Forever (until browser close) , otherwise in terms of seconds -; If you want cookies to last past a browser close set this to a value in seconds. +; If you want cookies to last past a browser close set this to a value in seconds. ; DEFAULT: 0 session_cookielife = 0 ; Is the cookie a "secure" cookie? This should only be set to 1 (true) if you are -; running a secure site (HTTPS). +; running a secure site (HTTPS). ; DEFAULT: 0 session_cookiesecure = 0 -; Auth Methods +; Auth Methods ; This defines which auth methods Auth will attempt to use and in which order. ; If auto_create isn't enabled the user must exist locally. ; DEFAULT: mysql @@ -108,7 +118,7 @@ auth_methods = "mysql" ;auth_password_save = "false" ; Logout redirection target -; Defaults to our own login.php, but we can override it here if, for instance, +; Defaults to our own login.php, but we can override it here if, for instance, ; we want to redirect to an SSO provider instead. ; logout_redirect = "http://sso.example.com/logout" @@ -120,15 +130,15 @@ auth_methods = "mysql" ; This defines which file types Ampache will attempt to catalog ; You can specify any file extension you want in here separating them ; with a | -; DEFAULT: mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx|ra|ape|shn|wv -catalog_file_pattern = "mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx|ra|ape|shn|wv" +; DEFAULT: mp3|mpc|m4p|m4a|aac|ogg|oga|wav|aif|aiff|rm|wma|asf|flac|opus|spx|ra|ape|shn|wv +catalog_file_pattern = "mp3|mpc|m4p|m4a|aac|ogg|oga|wav|aif|aiff|rm|wma|asf|flac|opus|spx|ra|ape|shn|wv" ; Video Pattern ; This defines which video file types Ampache will attempt to catalog ; You can specify any file extension you want in here seperating them with ; a | but ampache may not be able to parse them -; DEAFULT: avi|mpg|flv|m4v|webm -catalog_video_pattern = "avi|mpg|flv|m4v|webm" +; DEAFULT: avi|mpg|mpeg|flv|m4v|mp4|webm|mkv|wmv|ogv|mov|divx|m2ts +catalog_video_pattern = "avi|mpg|mpeg|flv|m4v|mp4|webm|mkv|wmv|ogv|mov|divx|m2ts" ; Playlist Pattern ; This defines which playlist types Ampache will attempt to catalog @@ -149,11 +159,11 @@ catalog_prefix_pattern = "The|An|A|Die|Das|Ein|Eine|Les|Le|La" ; DEFAULT: false ;catalog_disable = "false" -; Use Access List +; Use Access List ; Toggle this on if you want ampache to pay attention to the access list -; and only allow streaming/downloading/api-rpc from known hosts api-rpc +; and only allow streaming/downloading/api-rpc from known hosts api-rpc ; will not work without this on. -; NOTE: Default Behavior is DENY FROM ALL +; NOTE: Default Behavior is DENY FROM ALL ; DEFAULT: true access_control = "true" @@ -161,7 +171,7 @@ access_control = "true" ; If this is set to true ampache will make sure that the URL passed when ; attempting to retrieve a song contains a valid Session ID This prevents ; others from guessing URL's. This setting is ignored if you have use_auth -; disabled. +; disabled. ; DEFAULT: true require_session = "true" @@ -170,7 +180,7 @@ require_session = "true" ; is passed even on hosts defined in the Local Network ACL. This setting ; has no effect if access_control is not enabled ; DEFAULT: true -require_localnet_session = "true" +require_localnet_session = "true" ; Multiple Logins ; Added by Vlet 07/25/07 @@ -188,62 +198,63 @@ require_localnet_session = "true" ; Track User IPs ; If this is enabled Ampache will log the IP of every completed login -; it will store user,ip,time at one row per login. The results are +; it will store user,ip,time at one row per login. The results are ; displayed in Admin --> Users ; DEFAULT: false ;track_user_ip = "false" ; User IP Cardinality ; This defines how many days worth of IP history Ampache will track -; As it is one row per login on high volume sites you will want to -; clear it every now and then. +; As it is one row per login on high volume sites you will want to +; clear it every now and then. ; DEFAULT: 42 days ;user_ip_cardinality = "42" ; Allow Zip Download ; This setting allows/disallows using zlib to zip up an entire ; playlist/album for download. Even if this is turned on you will -; still need to enabled downloading for the specific user you +; still need to enabled downloading for the specific user you ; want to be able to use this function ; DEFAULT: false ;allow_zip_download = "false" -; File Zip Download -; This settings tells Ampache to attempt to save the zip file -; to the filesystem instead of creating it in memory, you must -; also set tmp_dir_path in order for this to work -; DEFAULT: false -;file_zip_download = "false" +Allow Zip Types +; This setting allows/disallows zip download of specific object types +; If empty, all supported object types can be zipped. +; Otherwise, only the given object list can be zipped. +; POSSIBLE VALUES: artist, album, playlist, search, tmp_playlist +; DEFAULT: none +;allow_zip_types = "album" ; File Zip Comment ; This is an optional configuration option that adds a comment ; to your zip files, this only applies if you've got allow_zip_downloads ; DEFAULT: Ampache - Zip Batch Download -;file_zip_comment = "Ampache - Zip Batch Download" +;file_zip_comment = "Ampache - Zip Batch Download" ; Waveform ; This settings tells Ampache to attempt to generate a waveform ; for each song. It requires transcode and encode_args_wav settings. ; You must also set tmp_dir_path in order for this to work ; DEFAULT: false -;waveform = "false" +;waveform = "false" ; Waveform color ; The waveform color. ; DEFAULT: #FF0000 -;waveform_color = "#FF0000" +;waveform_color = "#FF0000" ; Temporary Directory Path -; If File Zip Download or Waveform is enabled this must be set to tell +; If Waveform is enabled this must be set to tell ; Ampache which directory to save the temporary file to. Do not put a ; trailing slash or this will not work. ; DEFAULT: false ;tmp_dir_path = "false" ; This setting throttles a persons downloading to the specified -; bytes per second. This is not a 100% guaranteed function, and +; bytes per second. This is not a 100% guaranteed function, and ; you should really use a server based rate limiter if you want -; to do this correctly. +; to do this correctly. ; DEFAULT: off ; VALUES: any whole number (in bytes per second) ;throttle_download = 10 @@ -261,13 +272,52 @@ getid3_tag_order = "id3v2,id3v1,vorbiscomment,quicktime,matroska,ape,asf,avi,mpe ; DEFAULT: false ;getid3_detect_id3v2_encoding = "false" +; This determines if file metadata should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3 = "false" + +; This determines if album art should be write back to files +; as id3 metadata when updated. +; DEFAULT: false +;write_id3_art = "false" + +; This determines if catalog manager users can delete medias from disk. +; DEFAULT: false +;delete_from_disk = "false" + ; This determines the order in which metadata sources are used (and in the ; case of plugins, checked) ; POSSIBLE VALUES (builtins): filename and getID3 -; POSSIBLE VALUES (plugins): MusicBrainz, plus any others you've installed. +; POSSIBLE VALUES (plugins): MusicBrainz,TheAudioDb, plus any others you've installed. ; DEFAULT: getID3 filename metadata_order = "getID3,filename" +; This determines the order in which metadata sources are used (and in the +; case of plugins, checked) for video files +; POSSIBLE VALUES (builtins): filename and getID3 +; POSSIBLE VALUES (plugins): Tvdb,Tmdb,Omdb, plus any others you've installed. +; DEFAULT: filename getID3 +metadata_order_video = "filename,getID3" + +; This determines if extended metadata grabbed from external services should be deferred. +; If enabled, extended metadata is retrieved when browsing the library item. +; If disabled, extended metadata is retrieved at catalog update. +; Today, only Artist information (summary, place formed, ...) can be deferred. +; DEFAULT: true +deferred_ext_metadata = "true" + +; Some taggers use delimiters other than \0 for fields +; This list specifies possible delimiters additional to \0 +; This setting takes a regex pattern. +; DEFAULT: // / \ | , ; +additional_genre_delimiters = "[/]{2}|[/|\\\\|\|,|;]" + +; This determines if a preview image should be retrieved from video files +; It requires encode_get_image transcode settings. +; DEFAULT: false +;generate_video_preview = "true" + ; Un comment if don't want ampache to follow symlinks ; DEFAULT: false ;no_symlinks = "false" @@ -284,10 +334,10 @@ use_auth = "true" ; If use_auth is set to false then this option is used ; to determine the permission level of the 'default' users ; default is administrator. This setting only takes affect -; if use_auth if false +; if use_auth is false ; POSSIBLE VALUES: user, admin, manager, guest -; DEFAULT: admin -default_auth_level = "admin" +; DEFAULT: guest +default_auth_level = "guest" ; 5 Star Ratings ; This allows ratings for almost any object in ampache @@ -310,14 +360,14 @@ directplay = "true" ; Sociable ; This turns on / off all of the "social" features of ampache ; default is on, but if you don't care and just want music -; turn this off to disable all social features. +; turn this off to disable all social features. ; DEFAULT: true sociable = "true" -; Notify -; This turns on / off all Ampache notifications -; DEFAULT: true -notify = "true" +; License +; This turns on / off all licensing features on Ampache +; DEFAULT: false +;licensing = "false" ; This options will turn on/off Demo Mode ; If Demo mode is on you can not play songs or update your catalog @@ -332,7 +382,7 @@ notify = "true" ; requirments on larger catalogs. If you have the memory this can create ; a 2-3x speed improvement. ; DEFAULT: false -;memory_cache = false +;memory_cache = "false" ; Memory Limit ; This defines the "Min" memory limit for PHP if your php.ini @@ -347,7 +397,43 @@ notify = "true" ; Especially useful if you have a front and a back image in a folder ; comment out if ampache should search for any jpg,gif or png ; DEFAULT: folder.jpg -;album_art_preferred_filename = "folder.jpg" +;album_art_preferred_filename = "folder.jpg" + +; Album Art Store on Disk +; This defines if arts should be stored on disk instead of database. +; DEFAULT: false +;album_art_store_disk = "false" + +; Local Metadata Directory +; This define a local metadata directory with write access where to store +; heavy data if enabled (album arts, ...) +; DEFAULT: none +;local_metadata_dir = "/metadata" + +; Maximal upload size +; Specify the maximal allowed upload size for images, in bytes. +; DEFAULT: 1048576 +;max_upload_size = 1048576 + +; Album Art Minimum Width +; Specify the minimum width for arts (in pixel). +; DEFAULT: none +;album_art_min_width = 100 + +; Album Art Maximum Width +; Specify the maximum width for arts (in pixel). +; DEFAULT: none +;album_art_max_width = 1024 + +; Album Art Minimum Height +; Specify the minimum height for arts (in pixel). +; DEFAULT: none +;album_art_min_height = 100 + +; Album Art Maximum Height +; Specify the maximum height for arts (in pixel). +; DEFAULT: none +;album_art_max_height = 1024 ; Resize Images * Requires PHP-GD * ; Set this to true if you want Ampache to resize the Album @@ -357,25 +443,23 @@ notify = "true" ; DEFAULT: false ;resize_images = "false" +; Statistical Graphs * Requires PHP-GD * +; Set this to true if you want Ampache to generate statistical +; graphs on usages / users. +; DEFAULT: false +;statistical_graphs = "false" + ; Art Gather Order ; Simply arrange the following in the order you would like ; ampache to search. If you want to disable one of the search ; methods simply leave it out. DB should be left as the first ; method unless you want it to overwrite what's already in the ; database -; POSSIBLE VALUES: db tags folder amazon lastfm musicbrainz google +; POSSIBLE VALUES (builtins): db tags folder lastfm musicbrainz google +; POSSIBLE VALUES (plugins): Amazon,TheAudioDb,Tmdb,Omdb,Flickr ; DEFAULT: db,tags,folder,musicbrainz,lastfm,google art_order = "db,tags,folder,musicbrainz,lastfm,google" -; Amazon Developer Key -; These are needed in order to actually use the amazon album art -; Your public key is your 'Access Key ID' -; Your private key is your 'Secret Access Key' -; DEFAULT: false -;amazon_developer_public_key = "" -;amazon_developer_private_key = "" -;amazon_developer_associate_tag = "" - ; Recommendations ; Set this to true to enable display of similar artists or albums ; while browsing. Requires Last.FM. @@ -390,14 +474,14 @@ art_order = "db,tags,folder,musicbrainz,lastfm,google" ; Last.FM API Key ; Set this to your Last.FM api key to actually use Last.FM for -; recommendations. -;lastfm_api_key = "" +; recommendations and metadata. +lastfm_api_key = "d5df942424c71b754e54ce1832505ae2" ; Wanted ; Set this to true to enable display missing albums and the ; possibility for users to mark it as wanted. ; DEFAULT: false -;wanted = "false" +wanted = "true" ; Wanted types ; Set the allowed types of wanted releases (album,compilation,single,ep,live,remix,promotion,official) @@ -413,43 +497,34 @@ wanted_types = "album,official" ; EchoNest provides several music services. Currently used for missing song 30 seconds preview. ;echonest_api_key = "" +; Labels +; Use labels to browse artists per label membership. +; DEFAULT: false +;label = "false" + ; Broadcasts ; Allow users to broadcast music. ; This feature requires advanced server configuration, please take a look on the wiki for more information. ; DEFAULT: false ;broadcast = "false" +; Channels +; Set this to true to enable channels and the +; possibility for users to create channels from playlists +; DEFAULT: true +channel = "true" + +; Live Streams +; Set this to true to enable live streams (radio) and the +; possibility for users to add new live streams. +; DEFAULT: true +live_stream = "true" + ; Web Socket address ; Declare the web socket server address ; DEFAULT: determined automatically ;websocket_address = "ws://localhost:8100" -; Amazon base urls -; An array of Amazon sites to search. -; NOTE: This will search each of these sites in turn so don't expect it -; to be lightning fast! -; It is strongly recommended that only one of these is selected at any -; one time -; POSSIBLE VALUES: -; http://webservices.amazon.com -; http://webservices.amazon.co.uk -; http://webservices.amazon.de -; http://webservices.amazon.co.jp -; http://webservices.amazon.fr -; http://webservices.amazon.ca -; Default: http://webservices.amazon.com -amazon_base_urls = "http://webservices.amazon.com" - -; max_amazon_results_pages -; The maximum number of results pages to pull from EACH amazon site -; NOTE: The art search pages through the results returned by your search -; up to this number of pages. As with the base_urls above, this is going -; to take more time, the more pages you ask it to process. -; Of course a good search will return only a few matches anyway. -; It is strongly recommended that you do _not_ change this value -; DEFAULT: 1 page (10 items) -max_amazon_results_pages = 1 - ; Debug ; If this is enabled Ampache will write debugging information to the log file ; DEFAULT: false @@ -458,7 +533,7 @@ max_amazon_results_pages = 1 ; Debug Level ; This should always be set in conjunction with the ; debug option, it defines how prolific you want the -; debugging in ampache to be. values are 1-5. +; debugging in ampache to be. values are 1-5. ; 1 == Errors only ; 2 == Error + Failures (login attempts etc.) ; 3 == ?? @@ -487,7 +562,7 @@ log_filename = "%name.%Y%m%d.log" ; DEFAULT: UTF-8 site_charset = UTF-8 -; Locale Charset +; Locale Charset ; Local charset (mainly for file operations) if different ; from site_charset. ; This is disabled by default, enable only if needed @@ -496,12 +571,19 @@ site_charset = UTF-8 ;lc_charset = "ISO8859-1" ; Refresh Limit -; This defines the default refresh limit in seconds for +; This defines the default refresh limit in seconds for ; pages with dynamic content, such as now playing ; DEFAULT: 60 ; Possible Values: Int > 5 refresh_limit = "60" +; Footer Statistics +; This defines whether statistics (Queries, Cache Hits, Load Time) +; are shown in the page footer. +; DEFAULT: true +; Possible values: true, false +show_footer_statistics = "true" + ;######################################################### ; Custom actions (optional) # ;######################################################### @@ -528,10 +610,10 @@ refresh_limit = "60" ;######################################################### ; LDAP filter string to use (required) -; For OpenLDAP use "uid" +; For OpenLDAP use "uid" ; For Microsoft Active Directory (MAD) use "sAMAccountName" ; DEFAULT: null -; ldap_filter = "sAMAccountName" +;ldap_filter = "(sAMAccountName=%v)" ; LDAP objectclass (required) ; OpanLDAP objectclass = "*" @@ -608,15 +690,15 @@ refresh_limit = "60" ; DEFAULT: false ;admin_enable_required = "false" -; This setting will allow all registrants/ldap/http users -; to be auto-approved as a user. By default, they will be +; This setting will allow all registrants/ldap/http users +; to be auto-approved as a user. By default, they will be ; added as a guest and must be promoted by the admin. ; POSSIBLE VALUES: guest, user, admin ; DEFAULT: guest ;auto_user = "guest" ; This will display the user agreement when registering -; For agreement text, edit templates/user_agreement.php +; For agreement text, edit config/registration_agreement.php ; User will need to accept the agreement before they can register ; DEFAULT: false ;user_agreement = "false" @@ -625,6 +707,24 @@ refresh_limit = "60" ; DEFAULT: false ;user_no_email_confirm = "false" +; This will display the cookie disclaimer (EU Cookie Law) +; DEFAULT: false +;cookie_disclaimer = "false" + +; The fields that will be shown on Registration page +; If a user wants to register. +; Username and email fields are forced. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: "fullname,website" +registration_display_fields = "fullname,website" + +; The fields that will be mandatory +; This controls which fields are mandatory for registration. +; Username and email fields are forced mandatory. +; POSSIBLE VALUES: fullname,website,state,city +; DEFAULT: fullname +registration_mandatory_fields = "fullname" + ;######################################################## ; These options control the dynamic downsampling based # ; on current usage # @@ -646,7 +746,7 @@ refresh_limit = "60" ;###################################################### ; These are commands used to transcode non-streaming -; formats to the target file type for streaming. +; formats to the target file type for streaming. ; This can be useful in re-encoding file types that don't stream ; very well, or if your player doesn't support some file types. ; @@ -662,25 +762,64 @@ refresh_limit = "60" ; (e.g. if you store everything in FLAC, but don't want to ever stream that.) ; transcode_TYPE = {allowed|required|false} ; DEFAULT: false +;;; Audio ;transcode_m4a = allowed ;transcode_flac = required ;transcode_mpc = required ;transcode_ogg = required +;transcode_oga = required ;transcode_wav = required +;transcode_wma = required +;transcode_aif = required +;transcode_aiff = required +;transcode_ape = required +;transcode_shn = required ;transcode_mp3 = allowed +;;; Video +;transcode_avi = allowed +;transcode_mkv = allowed +;transcode_mpg = allowed +;transcode_mpeg = allowed +;transcode_m4v = allowed +;transcode_mp4 = allowed +;transcode_mov = allowed +;transcode_wmv = allowed +;transcode_ogv = allowed +;transcode_divx = allowed +;transcode_m2ts = allowed +;transcode_webm = allowed -; Default output format +; Default audio output format ; DEFAULT: none ;encode_target = mp3 +; Default video output format +; DEFAULT: none +;encode_video_target = webm + ; Override the default output format on a per-type basis ; encode_target_TYPE = TYPE ; DEFAULT: none ;encode_target_flac = ogg +; Override the default TYPE transcoding behavior on a per-player basis +; transcode_player_PLAYER_TYPE = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;transcode_player_webplayer_m4a = required +;transcode_player_webplayer_flac = required +;transcode_player_webplayer_mpc = required + +; Override the default output format on a per-player basis +; encode_player_PLAYER_target = TYPE +; Valid PLAYER is: webplayer, api +; DEFAULT: none +;encode_player_webplayer_target = mp3 +;encode_player_api_target = mp3 + ; Allow clients to override transcode settings (output type, bitrate, codec ...) ; DEFAULT: true -transcode_player_customize = true +transcode_player_customize = "true" ; Command configuration. Substitutions will be made as follows: ; %FILE% => filename @@ -694,13 +833,16 @@ transcode_player_customize = true ; equivalent to the old default, but if you find that necessary you should be ; clever enough to figure out how on your own. ; DEFAULT: none -;transcode_cmd = "ffmpeg -i %FILE%" -;transcode_cmd = "avconv -i %FILE%" -;transcode_cmd = "/usr/bin/neatokeen %FILE%" +;transcode_cmd = "ffmpeg" +;transcode_cmd = "avconv" +;transcode_cmd = "/usr/bin/neatokeen" + +; Transcode input file argument +transcode_input = "-i %FILE%" ; Specific transcode commands ; It shouldn't be necessary in most cases, but you can override the transcode -; command for specific source formats. It still needs to accept the +; command for specific source formats. It still needs to accept the ; encoding arguments, so the easiest approach is to use your normal command as ; a clearing-house. ; transcode_cmd_TYPE = TRANSCODE_CMD @@ -708,12 +850,28 @@ transcode_player_customize = true ; Encoding arguments ; For each output format, you should provide the necessary arguments for -; your transcode_cmd. +; your transcode_cmd. ; encode_args_TYPE = TRANSCODE_CMD_ARGS -;encode_args_mp3 = "-vn -b:a %SAMPLE%K -c:a libmp3lame -f mp3 pipe:1" -;encode_args_ogg = "-vn -b:a %SAMPLE%K -c:a libvorbis -f ogg pipe:1" -;encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" -;encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" +encode_args_mp3 = "-vn -b:a %SAMPLE%K -c:a libmp3lame -f mp3 pipe:1" +encode_args_ogg = "-vn -b:a %SAMPLE%K -c:a libvorbis -f ogg pipe:1" +encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" +encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" +encode_args_flv = "-b:a %SAMPLE%K -ar 44100 -ac 2 -v 0 -f flv -c:v libx264 -preset superfast -threads 0 pipe:1" +encode_args_webm = "-q %QUALITY% -f webm -c:v libvpx -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" +encode_args_ts = "-q %QUALITY% -s %RESOLUTION% -f mpegts -c:v libx264 -c:a libmp3lame -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" + +; Encoding arguments to retrieve an image from a single frame +encode_get_image = "-ss %TIME% -f image2 -vframes 1 pipe:1" + +; Encoding argument to encrust subtitle +encode_srt = "-vf \"subtitles='%SRTFILE%'\"" + +; Encode segment frame argument +encode_ss_frame = "-ss %TIME%" + +; Encode segment duration argument +encode_ss_duration = "-t %DURATION%" + ;###################################################### ; these options allow you to configure your rss-feed @@ -721,7 +879,7 @@ transcode_player_customize = true ; song is the information in the feed. can be multiple items. ; use_rss = false (values true | false) ;DEFAULT: use_rss = false -;use_rss = false +;use_rss = "false" ;##################################################### ;############################# @@ -737,7 +895,7 @@ transcode_player_customize = true ; If Ampache is behind an https reverse proxy, force use HTTPS protocol. ;Default: false -;force_ssl = true +;force_ssl = "true" ;############################# ; Mail Settings # @@ -749,7 +907,7 @@ transcode_player_customize = true ;mail_type = "php" ;Mail domain. -;DEFAULT: example.com +;DEFAULT: example.com ;mail_domain = "example.com" ;This will be combined with mail_domain and used as the source address for @@ -796,7 +954,7 @@ transcode_player_customize = true ;Enable SMTP authentication ;DEFAULT: false -;mail_auth = true +;mail_auth = "true" ;SMTP Username ;your mail auth username. diff --git a/sources/cookie_disclaimer.php b/sources/cookie_disclaimer.php new file mode 100644 index 0000000..d46ec1f --- /dev/null +++ b/sources/cookie_disclaimer.php @@ -0,0 +1,29 @@ + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-s + RewriteRule ^(.+)$ /index.php?action=$1 [PT,L,QSA] + \ No newline at end of file diff --git a/sources/daap/index.php b/sources/daap/index.php new file mode 100644 index 0000000..00c9fa8 --- /dev/null +++ b/sources/daap/index.php @@ -0,0 +1,67 @@ += 7.0) +debug_event('daap', 'Request headers: '. print_r($headers, true), '5'); + +// Get the list of possible methods for the Plex API +$methods = get_class_methods('daap_api'); +// Define list of internal functions that should be skipped +$internal_functions = array('apiOutput', 'create_dictionary', 'createError', 'output_body', 'output_header', 'follow_stream'); + +Daap_Api::create_dictionary(); + +$params = array_filter(explode('/', $action), 'strlen'); +if (count($params) > 0) { + // Recurse through them and see if we're calling one of them + for ($i = count($params); $i > 0; $i--) { + $act = strtolower(implode('_', array_slice($params, 0, $i))); + $act = str_replace("-", "_", $act); + foreach ($methods as $method) { + if (in_array($method, $internal_functions)) { continue; } + + // If the method is the same as the action being called + // Then let's call this function! + if ($act == $method) { + call_user_func(array('daap_api', $method), array_slice($params, $i, count($params) - $i)); + // We only allow a single function to be called, and we assume it's cleaned up! + exit(); + } + + } // end foreach methods in API + } +} + +Daap_Api::createError(404); diff --git a/sources/democratic.php b/sources/democratic.php index a86d596..498f780 100644 --- a/sources/democratic.php +++ b/sources/democratic.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/docs/ACKNOWLEDGEMENTS b/sources/docs/ACKNOWLEDGEMENTS index f2d4f0b..cd454b6 100644 --- a/sources/docs/ACKNOWLEDGEMENTS +++ b/sources/docs/ACKNOWLEDGEMENTS @@ -22,4 +22,12 @@ Acknowledgements * Randy Perkins * Ben Shields * Afterster -* SUTJael \ No newline at end of file +* SUTJael +* Psy-Virus +* John Moore (jcwmoore) +* René Bigler (Razrael) +* Kaivo +* Ernest Wagner (wagnered) +* lotan +* brownl +* Deathcow \ No newline at end of file diff --git a/sources/docs/CHANGELOG.md b/sources/docs/CHANGELOG.md index e06a605..d2a223b 100755 --- a/sources/docs/CHANGELOG.md +++ b/sources/docs/CHANGELOG.md @@ -1,7 +1,207 @@ CHANGELOG ========= -3.7 +3.8.0 +---------- +- Added Portuguese (Brasil) language (thanks Ione Souza Junior) +- Updated PHPMailer version to 5.2.10 +- Fixed user stats clear +- Added user, followers and last shouts XML API functions +- Fixed transcoded process end on some systems (thanks nan4k7) +- Added ogg channel streaming support (thanks Deathcow) +- Fixed sql connection close before stream (thanks fufroma) +- Added support for several ldap filters (thanks T-Rock) +- Fixed 'Add to existing playlist' button on web player (thanks RyanCopley) +- Added 'add to existing playlist' link on album page (thanks RyanCopley) +- Added option to hide user fullname from other users +- Added playlist track information in Apache XML API (thanks RyanCopley) +- Fixed playlist remove song in Apache XML API (thanks RyanCopley) +- Fixed SubSonic API ifModifiedSince information +- Added Podcast links to albums / artists +- Added Piwik and Google Analytics plugins +- Added Apache 2.4 access control declaration in htaccess files +- Fixed performance issues on user preferences +- Added artist search by year and place +- Fixed search by comment (thanks malkavi) +- Added Paypal and Flattr plugins +- Added .maintenance page +- Fixed captcha +- Added private messages between users +- Fixed SubSonic API rating information on albums and songs +- Added latest artists and shouts RSS feeds +- Fixed tag cloud ordering +- Added Label entities associated to artists / users +- Added WebDAV backend +- Fixed SubSonic API requests with musicFolderId parameter (thanks dhsc19) +- Added footer text edition setting +- Added uploaded artist list on user page +- Added custom Ampache login logo and favicon support +- Added edition support on shared objects (thanks dhsc19) +- Fixed share feature on videos (thanks RobertoCarlo) +- Removed album year display from album name if unset +- Fixed Subsonic API Album/Artist song's link (thanks dhsc19 and daneren2005) +- Added mysql database socket authentication support on web setup (thanks AsavarTzeth) +- Fixed artist art url for mobile use (thanks dhsc19) +- Added Shoutbox home plugin +- Added catalog favorites home plugin +- Fixed search by rating (thanks iamnumbersix) +- Added UPnP localplay (thanks SeregaPru) +- Changed preferences to return the global value if preference is missing for the searched user +- Fixed special chars in songs names and tags (thanks SeregaPru) +- Fixed Subsonic API playlist edition/delation (thanks dhsc19) +- Fixed integer default value in Apache XML API +- Fixed image thumb on webplayer and search preview (thanks RobertoCarlo and eephyne) +- Fixed proxy setting on all external http requests (thanks brendankearney) +- Added QRCode view of user API key +- Fixed http status code on Subsonic API streams when using curl (thanks nicklan) +- Added Server-Sent Events on catalog actions +- Added option to enable/disable channel and live stream features +- Removed official PHP 5.3 support +- Added option to show/hide footer statistics (thanks brownl) +- Added delete from disk option on user uploaded files +- Added installation type and players helper at installation process +- Added tv_episode tag on quicktime files (thanks wagnered) +- Added new option to disable deferred extended metadata, e.g. artist details +- Added Subsonic API getAvatar function +- Fixed unsynced lyrics tags +- Fixed ldap_filter setting deactivation on ampache.cfg.php update (thanks Rouzax) +- Added Subsonic API similar artists & songs functions +- Added Subsonic API getLyrics function +- Fixed disk number and album artist metadata on quicktime files (thanks JoeDat) +- Fixed Ampache API playlist_add_song function +- Added ability to store images on disk +- Added new setting to define album art min and max width/height +- Fixed Subsonic API getAlbum returned artist id on songs +- Fixed Subsonic API cover art when PHP-GD unavailable +- Fixed localplay playlist refresh on volume changes (thanks essagl) +- Fixed web player equalizer option if visualizer is not enabled (thanks brownl) +- Fixed asx file mime type (thanks thinca) +- Added song genre parsing options (thanks Razrael and lotan) +- Added sort on languages list (thanks brownl) +- Added placeholder text to search box (thanks brownl) +- Added web player Play Next feature (thanks tan-ce) +- Fixed Plex backend administration page uri (thanks a9k) +- Fixed expired shared objects clean (thanks eephyne) +- Added missing artist search results (thanks bliptec) +- Fixed song genre id parsing (thanks lotan) +- Added Scrobble method to Subsonic API +- Added an option to add tags to child without overwriting +- Added image dimension info to image tables (thanks tsquare66) +- Replaced ArchiveLib by StreamZip-PHP to avoid temporary zip file +- Added Year field in song details and edition +- Added Subsonic API create/delete user, jukebox control and search auto suggestion +- Added few optional install tests +- Improved Share features with modal dialog choices +- Added new action on playlists to remove duplicates +- Fixed playlist addition to another playlist (thanks kszulc) +- Fixed Various Artist link on album page (thanks Jucgshu) +- Added session_destroy call when a session should be destroyed +- Added HTML5 ReplayGain track feature +- Added display and mandatory user registration fields settings +- Added .htaccess IfModule mod_access.c directives +- Fixed SmartPlayer results per user (thanks nakinigit) +- Fixed XSS vulnerability CVE-2014-8620 (thanks g0blin) +- Fixed playlist import setting on catalog update to be disabled by default (thanks DaPike) +- Added ability to browse my tags other library items than songs +- Added Stream Control plugins +- Added transcode settings per player type +- Added ability to write directly the new configuration file when it version changed +- Added `quick play url` to have permanent authenticated stream link without session +- Fixed unresponsive website on batch download (thanks Rouzax) +- Added batch download item granularity +- Fixed 'guest' user site rendering +- Added Aurora.js support in webplayer +- Added Google Maps geolocation analyze plugin +- Added statistical graphs +- Added user geolocation +- Added 'Missing Artist' search +- Fixed Ampache installation with FastCGI +- Added a new RSS Feed plugin +- Added a new 'display home' plugin type +- Added Favorite and Rating features to playlists +- Added user feedback near mouse cursor on democratic votes +- Changed header page position to be fixed +- Added external links on song page details +- Fixed Subsonic API getAlbumList2 byGenre and byYear order (thanks rrjk) +- Added html5 desktop notification +- Added album group order setting +- Fixed unwanted album merge when one of the album doesn't have mbid +- Changed video player to go outside the footer +- Added ip address in authentication failure for fail2ban scripts (thanks popindavibe) +- Added parameter to hide directplay button if number of items is above a limit +- Added Tag split (thanks jcwmoore) +- Fixed album/artist arts and stats migration on rename (thanks jcwmoore) +- Fixed get lyrics from files (thanks apastuszak) +- Fixed verify local catalog (thanks JoeDat) +- Removed Twitter code +- Added optional cookie disclaimer for the EU Cookie Law +- Replaced catalog action links to action dropdown list (thanks Psy-Virus) +- Fixed `remember me` feature (thanks ainola) +- Added email when registered user must be enabled by administrators +- Fixed local catalog clean on Windows (thanks Rouzax) +- Added Subsonic API maxBitRate parameter support (thanks philipl) +- Fixed SubSonic API special characters encode (thanks nan4k7) +- Added Beets local and remote catalog support (thanks Razrael) +- Fixed XML error code returned with invalid Ampache API handshake (thanks funkygaddafi) +- Replaced iframe to Ajax dynamic page loading +- Changed `Albums of the moment` to not necessarily have a cover +- Added Plex backend items edition support +- Added hls stream support +- Added X-Content-Duration header support on streams +- Removed Toogle Art from artist page +- Fixed track numbers when removing a song from playlist (thanks stonie08) +- Added Plex backend playlist support +- Added gather art from video files (thanks wagnered) +- Added Plex backend movie / tvshow support +- Added release group on albums +- Added Smart Playlist songs list +- Added zlib test +- Removed old Ampache themes +- Fixed SubSonic API lastModified element (thanks bikkuri10) +- Disabled `beautiful url` on XML-API for retro-compatibility +- Fixed image resource allocation (thanks greengeek1) +- Added setting to write id3 metadata to files (thanks tsquare66) +- Added check for large files manipulation +- Added video subtitle support +- Fixed Google arts to use real arts and not the small size preview +- Added Tmdb metadata plugin +- Added Omdb metadata plugin +- Added Music Clips, Movies and TV Shows support +- Added media type information on catalog +- Fixed get SmartPlaylist in XML-API (thanks opencrf) +- Added beautiful url on arts +- Improved browse list header (thanks Psy-Virus) +- Fixed user online/offline information on Reborn theme (thanks thorsforge) +- Added UPnP backend (thanks SeregaPru) +- Added DAAP backend +- Added sort options on playlists (thanks Shdwdrgn) +- Fixed XML-API tag information (thanks jcwmoore) +- Fixed multiple broadcast play (thanks uk3gaus) +- Added SmartPlaylists to Subsonic API +- Added limit option on SmartPlaylists +- Added random option on SmartPlaylists +- Added 'item count' on browse +- Added direct typed links on items tags +- Fixed SubSonic API compatibility with few players requesting information on library -1 +- Added license information on songs +- Added upload feature on web interface +- Added albumartist information on songs (thanks tsquare66) +- Fixed errors on sql table exists check +- Fixed play/pause on broadcasts (thanks uk3gaus) +- Added donation button +- Added democratic page automatic refresh +- Fixed distinct random albums +- Added collapsing menu (thanks Kaivo) +- Added 'save to playlist' feature on web player (thanks Kaivo) +- Added tag merge feature +- Fixed democratic vote with automatic logins (thanks M4DM4NZ) +- Added git pull update from web interface for development versions +- Fixed http-rang requests on streaming (thanks thejk) +- Improved installation process +- Improved French translation (thanks arnaudbey) +- Improved German translation (thanks Psy-Virus and meandor) + +3.7.0 ---------- - Added Scrutinizer analyze - Fixed playlist play with disabled songs (reported by stebe) diff --git a/sources/docs/PLUGINS b/sources/docs/PLUGINS index 4c58f39..711f076 100644 --- a/sources/docs/PLUGINS +++ b/sources/docs/PLUGINS @@ -27,9 +27,15 @@ should be implemented as a public method: get_metadata(Array $metadata) The passed array contains the best metadata we've got. save_rating(Rating $rating, int $new_value) - save_songplay(Song $song) + save_mediaplay(Media $media) get_lyrics(Song $song) process_wanted(Wanted $wanted) shortener(string $url) get_photos(string $search) - + gather_arts(string $type, array $options, int $limit) + get_song_preview(string $track_mbid, string $artist_name, string $title) + stream_song_preview(string $file) + display_home() + external_share(string $url, string $text) + display_user_field(library_item $libitem = null) + display_on_footer \ No newline at end of file diff --git a/sources/graph.php b/sources/graph.php new file mode 100644 index 0000000..b1e5fa5 --- /dev/null +++ b/sources/graph.php @@ -0,0 +1,69 @@ +render_user_hits($user_id, $object_type, $object_id, $start_date, $end_date, $zoom, $width, $height); + break; + case 'user_bandwidth': + $graph->render_user_bandwidth($user_id, $object_type, $object_id, $start_date, $end_date, $zoom, $width, $height); + break; + case 'catalog_files': + $graph->render_catalog_files($user_id, $object_type, $object_id, $start_date, $end_date, $zoom, $width, $height); + break; + case 'catalog_size': + $graph->render_catalog_size($user_id, $object_type, $object_id, $start_date, $end_date, $zoom, $width, $height); + break; +} diff --git a/sources/image.php b/sources/image.php index 0591995..dfeb43c 100644 --- a/sources/image.php +++ b/sources/image.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -32,10 +32,12 @@ define('NO_SESSION','1'); require_once 'lib/init.php'; -// Check to see if they've got an interface session or a valid API session, if not GTFO -if (!Session::exists('interface', $_COOKIE[AmpConfig::get('session_name')]) && !Session::exists('api', $_REQUEST['auth'])) { - debug_event('image','Access denied, checked cookie session:' . $_COOKIE[AmpConfig::get('session_name')] . ' and auth:' . $_REQUEST['auth'], 1); - exit; +if (AmpConfig::get('use_auth') && AmpConfig::get('require_session')) { + // Check to see if they've got an interface session or a valid API session, if not GTFO + if (!Session::exists('interface', $_COOKIE[AmpConfig::get('session_name')]) && !Session::exists('api', $_REQUEST['auth'])) { + debug_event('image','Access denied, checked cookie session:' . $_COOKIE[AmpConfig::get('session_name')] . ' and auth:' . $_REQUEST['auth'], 1); + exit; + } } // If we aren't resizing just trash thumb @@ -46,44 +48,18 @@ if (!isset($_GET['object_type'])) { $_GET['object_type'] = 'album'; } -$type = Art::validate_type($_GET['object_type']); +$type = $_GET['object_type']; +if (!Core::is_library_item($type)) + exit; /* Decide what size this image is */ -switch ($_GET['thumb']) { - case '1': - /* This is used by the now_playing stuff */ - $size['height'] = '75'; - $size['width'] = '75'; - break; - case '2': - $size['height'] = '128'; - $size['width'] = '128'; - break; - case '3': - /* This is used by the flash player */ - $size['height'] = '80'; - $size['width'] = '80'; - break; - case '4': - /* Web Player size */ - $size['height'] = 200; - $size['width'] = 200; // 200px width, set via CSS - break; - case '5': - /* Web Player size */ - $size['height'] = 32; - $size['width'] = 32; - break; - default: - $size['height'] = '275'; - $size['width'] = '275'; - if (!isset($_GET['thumb'])) { $return_raw = true; } - break; -} // define size based on thumbnail +$size = Art::get_thumb_size($_GET['thumb']); +$kind = isset($_GET['kind']) ? $_GET['kind'] : 'default'; $image = ''; $mime = ''; $filename = ''; +$etag = ''; $typeManaged = false; if (isset($_GET['type'])) { switch ($_GET['type']) { @@ -102,20 +78,47 @@ if (isset($_GET['type'])) { } } if (!$typeManaged) { - $media = new $type($_GET['id']); - $filename = $media->name; + $item = new $type($_GET['object_id']); + $filename = $item->name ?: $item->title; - $art = new Art($media->id,$type); + $art = new Art($item->id, $type, $kind); $art->get_db(); + $etag = $art->id; + + // That means the client has a cached version of the image + $reqheaders = getallheaders(); + if (isset($reqheaders['If-Modified-Since']) && isset($reqheaders['If-None-Match'])) { + $ccontrol = $reqheaders['Cache-Control']; + if ($ccontrol != 'no-cache') { + $cetagf = explode('-', $reqheaders['If-None-Match']); + $cetag = $cetagf[0]; + // Same image than the cached one? Use the cache. + if ($cetag == $etag) { + header('HTTP/1.1 304 Not Modified'); + exit; + } + } + } if (!$art->raw_mime) { - $mime = 'image/jpeg'; - $image = file_get_contents(AmpConfig::get('prefix') . - AmpConfig::get('theme_path') . - '/images/blankalbum.jpg'); + $defaultimg = AmpConfig::get('prefix') . AmpConfig::get('theme_path') . '/images/'; + switch ($type) { + case 'video': + case 'tvshow': + case 'tvshow_season': + $mime = 'image/png'; + $defaultimg .= "blankmovie.png"; + break; + default: + $mime = 'image/jpeg'; + $defaultimg .= "blankalbum.jpg"; + break; + } + $image = file_get_contents($defaultimg); } else { if ($_GET['thumb']) { $thumb_data = $art->get_thumb($size); + $etag .= '-' . $_GET['thumb']; } $mime = isset($thumb_data['thumb_mime']) ? $thumb_data['thumb_mime'] : $art->raw_mime; @@ -129,7 +132,11 @@ if (!empty($image)) { // Send the headers and output the image $browser = new Horde_Browser(); - header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + 604800)); + if (!empty($etag)) { + header('ETag: ' . $etag); + header('Cache-Control: private'); + header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', time())); + } $browser->downloadHeaders($filename, $mime, true); echo $image; } diff --git a/sources/themes/reborn/images/background.png b/sources/images/background.png similarity index 100% rename from sources/themes/reborn/images/background.png rename to sources/images/background.png diff --git a/sources/images/fileupload-border.png b/sources/images/fileupload-border.png new file mode 100644 index 0000000000000000000000000000000000000000..70b21e933764e378fd756fa0e5f0fac45a6bc8f8 GIT binary patch literal 1353 zcmeAS@N?(olHy`uVBq!ia0vp^DImc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPA){ffi_eM3D1{oGuTzrd=COM+4n&cLd=IHa;5RX-@T zIKQ+g85kdF$}r8qu)}W=NFmTQR{lkqz(`5Vami0E%}vcK@pQ3O0?O#6WTse|7`j@z z8oRkzS~wZH8W|c|7@C*?X(vY$7iSYo6LXjuSoK=Cxfwe-x;UA;ni~RjTNpaKIa(N+ zni`lmxw$&J!1Q|N6_+IDC8xsd%>>yC(d&#?ua$FAYGO%#QAmD%4lD%(WaO9R7iZ)b zC^!e3Duid|rR0|vgCYmwSC`bH%(B!Jx1#)91#swEWn#0Ppsyi%Q^*N1eV}9XK}iEC zslbGQ=?}z&Cww3Wo^?|5fcd2em@%>!9nfK5V666ZaSW-r_2yQf*I@&OhKDKB?k>3W zu(U>KPlJDRtW$u<-u4aZ`D)tV1Pp5>J8A{)PFquBpR&Pjjk3INg7=<%J7e1f=6#BI zUMTu6&`Xo2c7MHmGEeUH)@0MgziXdtJo|X-NqzmwJM2?)Y6Rpy?TC0Xz4zyeWoJ1q z&1%oe5`VT}-K>?}^LO7pXm-%K_L|K5@|{tCcRb@Db*DKw$@ zzVn>7y3;rBrG}2;wKI2FeBJyZ-%GP~;rs9JlY17v-L<&(r{%r#!a)xwna*g-l5(41 z{yD(+b|HV|x#XG0W98;rYF^r5xcB+TAF2l!LLbLJ(g?rh1}fYDSr z1<%~X^wgl##FWaylc_cg49qE+ArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XP}#GU}m6TW~gUq zY+`P1uA^XNU}&IkV5Dzoq-$tyWo%?+V4wg6Nh+i#(Mch>H3D2mX;thjEr=FDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaoz zEwNPsx)kDt+yY-;xWReF(0~F4nSMoLfxe-hfqrf-$X{U9#U(+h2xnkbT^v$bkg6Y) zTAW{6lnjiIG-a4(VA$ce2&53`8Y};zOkkuW=D6f1m*%GCm3X??DgkBmQZiGlTpis^ zjm=CPUCo`$U5yM4EeuV}fV7jNiHoy|rHMJr3~YLxEX^$q4V;~UI^7HnU5zYV9i5zA zEnQq(jEtRJElgp0J@bl767!N%VfJPM?S<-f!mHQHxhOTUB)=#mKR*YS0s=DfOY(~| z@(UE4gH08}GxJjN%Zovg1M#a%YEfocYKmJ?ey##IbgeS6*iP8j5WOkngqS|iG5VmS zfs|BWLcsI~V!{(XkOR*;sd>QsQUuHxUw?V2F)%PL_H=O!skk*~ioeT|K!M}8%`TdL z6H#3h9J?r6FmtMsQsAjF&82U&To;8}EpeXa>vBE781KG$&wg?b zD_g~1PBqUM&b$M2+;{LL?fJ*_SHU@Teq&nOKFyUa-U@nu1PffaPt6I{*WVDJA z7K!mTygR{8`M@fnZL1f@yxAt|kbdTun=9iv-jCW9zp~F6UX@Azx~g;k5tTb(p-*gr zHM-h)6Zy{@KjwCLB&qr)HPCTQYJ;EVGC>>ehn=ss@JyJpy)Q3xp7)xq#}t`LcKp{% czt6OV literal 0 HcmV?d00001 diff --git a/sources/images/icon_clean.png b/sources/images/icon_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..cf039f40bbfeb27808da2baf71534720dcff09e3 GIT binary patch literal 859 zcmV-h1El*9LGQR;QS<&hkkZ651oG9iF3!18b?~u<0#c4wwkTdq+#@cQlwffJxmc2t81z2 zN-hs;g-YfK6}uiV=fa)z{J8b|^lSZ5r}5d|`~LHJzrLTn_xS)!Pzj58piCFTwLP9t zu~S5rY~zPl>~gocnI(gyWmXee0jwth6mKP=Cf6S-(kO5Bp}@gMg%JbSGbQI#d!>-L z#ByRl0L1}CvJ2E?OIfu?K%`e`qvQ0dAH$@l=F8ym__S%WfukIGZ_ONjP%l&0ge?<;0m5boPg?XUbCo0e!^T7MLv+2@A zmw08m5K0pTuu{tX-y9Lfgm!WvsPdzru_DO#Wm9NreIaFN$hO6z1gC2CVIk{ErZenc zX9tAY7;`vd?$dZdcjGE(QAfk+)U7w}Dw0vx9)p8VmtuoLRIWPW#V*fugaCKK)a)za z!mDNq4B8c-e-Z^f%|R)3<%x88P6p=0?#Hsd@mN(TOR3DC4^;(xkh*jJ2~EI;)*43` ze6|?g^h7g&kc=NAY4i_cJihM>$BsJzSg>aeeLgExQe7MXwIzITb7B7lwA{9bflfIL zYa@QYtG^mje||Tn;g`V}G?6DMC}@su=EGPrvJlMC0@I z%&$SH(>P#%TQK%%yy=j+X7ik9@XXE4fMWtUo@EP3TO3$+HXPf1$&M#*e(8 zj@j7T?19=w2|lj%F`DyW2GefK==T-z82uT=irGl9hzER002ovPDHLkV1ff)o{j(j literal 0 HcmV?d00001 diff --git a/sources/images/icon_file_refresh.png b/sources/images/icon_file_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..e5bebdd5064e9b5fdf8a4a6237d7d13a19fc22a1 GIT binary patch literal 990 zcmV<410np0P)mzelsjP^MT>Tqo3cdUAZB2WV+8!m>Pfp zVqs!tRA*yh7P$I~ftiVs5o|NiQii`EoB#Z0`2PDZgM%tBgSi?D!^wZIx&HstcyFJx zRq)^xZ?GW%0R+^;$n*dIe`YpjCI(g}1_l*}3}3!KW%$EBmw}m;ITU69Kmf78 z{K~=%GKmowZj227{{Clp`Rg{rxi^~`o_)E_@aOJdhCjdlFv#-CG5r4Xm4QoQCD1-w zumJ!8!~)U`3M&>yCLsME%zp9nHpA)HYZ#=sgc$UMeHi`$!{hJ&zd#yj(7(S7-@ct> z@|fUwXGg()6@UO@`49FX#J^w{0NJ-co@5YV<7N=!6lXa1^bEu87q=LGgIpje$ME;h zUxsUUe*N27x?ct40)PNw`u7Log?|hXmjDBl;okcT4CkJmWmtQ8HN)3$ObmrKs~8IH z)-t>WhQ%Ks|IfGNU)Bw#oFD@L0*K|`zyDxO3}6IF1W8)G5JQ3ZFVM-$&$Kaw7*#Rw zvq>;C2Gle7x1C`)+I;#uNG(7BvHS(cIVA4?A(9Z#ufKpEVr7_pq@3aN_YYvhBCQ)4 z$`)rZeEE5bL%-n9|3AO~JOl_JmVdy+@(*ZT!}(v}5cmg5NT4MAkBQ;es|sMqe`H{1 z`@>MQzzQ4=%CZU!(IH_B*KXWoIC%KzPJjSn`2!4;zkeBk35AuxNQ?>MY#`otD40Q7 zfR8~}MHd_cI%-<*n7DQCF2jLCN6rERbU8o(vHbb@?;kL;{Qvcrf${iLpaMovNH8)8 ztF2;qaJ7ix+t-u8xc>vR@dh|8`~fEIe}9zz0xd4NzwprkfB<6o{p0UTZZ>9-a@}9t zz`z7026$NToAxl2E=*(i_URqNk+w^qsQ$;v#`IY?Y3I{h3q1~i)Bpq!3nSzIUbm7% zfxtXr2(+6EDI6iu$`H=*mw6tmb?)BZK<!M5 zt(0}}%In0I0i|-4rF@=Jxy*}~UpX_hPim#Cf6V}a7Ql5WdOp?8AOMH=hk5Ymog;)u z($%#!2ZfFxgGLD;%OVeFMUqx=M(L=Z*@2%JBoM1Ux;8`uSO0|$V004R=004l4008;_ z004mL004C`008P>0026e000+nl3&F}00063NklW@ai-Hac;(s7^sBM>A|5|9Zf~d5Z zQu8B~-0$b!&mr-r9Zbao?{Jof=RHR_=lGxT0FK-)B812T6V_Va8iP>^qZB#}nY+^S zcR&RXMqbjHvpnxL~&N-6%he_-kDgd~Z zx4C%!ksmG3vHR3zKJ;G)DD;i=%k}-oA3UoAgb=C3;Y&9ND{tZ#x1IvwA1QM&eU0uG z36ME`P+QL4dmR8E_yzEQt&K`CU>n&6RDrLY-)z~HD0GZ=$tC||KvEpS6ZIQ7=Pa0t09#Lmae`7PXu@ZI@|a$00ZC;o%h)IJOBUy07*qoM6N<$g6(w)+5i9m diff --git a/sources/images/icon_play_next.png b/sources/images/icon_play_next.png new file mode 100644 index 0000000000000000000000000000000000000000..5a30a7c539cfabc435490d4020b0bbbd6159b023 GIT binary patch literal 635 zcmV->0)+jEP)(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ;0ZBwbRCwB?kv(WrVHAd+FTH7_ajAo1ix!KBV8r3Gi%_tIA}Fo3f>VnQir^G* zb#QhH;u28@xr>4h3gRGksBM>A|5|9Zf~d5ZQu8B~-0$b!&mr-r9Zbao?{Jof=RHR_ z=lGxT0FK-)B812T6V_Va8iP>^qZB#}nY+^ScR&3)k3DHKQS`U zC5=)|A!`6Dr3#-H7g;Hln3#Oe(cYcXD3xvcMqR4`u&f=Gwln~l?so1!d>IHKa?Jr@ z2tYBJrkHN!z>XA;?~VsTh}_=ZLBHoF%x@ZIH93`UygI~T7w9aMvj`^)gS{A$$tVYklIC*a1ti(8pa=#6XQuU?HV=nL4wMz?r91rqJos6o{ zWS$s_byTA&s2HGtBM{CxlKY2A>>DZoxRtlLc>a+eEzhz0)MY;OUk51kjr7a){l_0X zs{@1(sl?$+HwY_l;up7`0^uJib1{95?iLA}1{rUgjo>{e_v(d@m2k#I5qhCrH9i!QG1UBx< okyyekrjyj;vY|a#f`LVX;nW6B8L3JEO`vWDPgg&ebxsLQ02@v!xc~qF literal 0 HcmV?d00001 diff --git a/sources/images/icon_share_facebook.png b/sources/images/icon_share_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..3053f26c4812674798354e271f92d8dfb68352c0 GIT binary patch literal 573 zcmV-D0>b@?P)Unw@iRa#xAm0F4(ob6Zn2gLJBHZ$YQY_dsC zm+a0ao44=Ho855Z-eN;UUPGi3Ff{5T&`FBI`8}<1zqZt2yEO28wqDJS9|IGESwK9E ztdT}hEXGwuZS$@)<`W8DTPr~YJ19`$Q9mfC1xbM7v=<0L1!xaNsgqSzn(TE0XST$| zB=}QDT)TW0n;YjbSCT6i&%oOCyRST2YNo(tQ74%w%_9*mojZjWk8b0iX1;WOnFpCM zvukWy#^$XH8ME`|Gj{jBbzZXjpl+xFjMzs?Y?IR`=XrzOz3)mA6-eG8wtB!82>^Z1Sfdef}Ee6rOk*~iB(KRg9fpA?MpxA!s7 z+I%)t;Mw8>+IX0JknF*bzX`v2e_;mW)7u9Q`g-sa51+o*Zv;I&BOf@GaqPZuf0V;S z1yPN*IZR+@PYMd@J)q8gFA=4)KdHS8z-|-!r?8=E>U;{lzP5N2k6~@t#1+UgHY;_% z_8j{|@;?Pu`vmM1C^lT(dY-N`xJuX8xhB{6=(X0$OWPM9&00000 LNkvXXu0mjf6+8ap literal 0 HcmV?d00001 diff --git a/sources/images/icon_share_googleplus.png b/sources/images/icon_share_googleplus.png new file mode 100644 index 0000000000000000000000000000000000000000..be37ca850d5023e6a87520abf10977d2577b3157 GIT binary patch literal 1047 zcmV+y1nB#TP)yvg-37SGXMk-;~fvP{|t-_3{0E_y$s)sZ5RLo03rb0{{hiHGynYb z{{a0H{{SnQssIAy;O+qC1^oaG8vg*gPecF$07L-0{{h@eH~#?s{{R330{{U30R95_ z=ji|;x5)tbA0zDKZ90Rsg90?2D<03Dak0P^|#0Pyzr00M~N zyq($qXA747|M}_L|9{^;{(n*K`~Q-y&i{|+PW`{`ZvLO?_qT5hA6AwzoOe)S_|3w{ zaPP=%hF1?CF#P-XkKqN7X56E!^8fA2`wZSM|1dl&w`aI@^ce#q&?hj!^z`TN2P{g` z47)8A7%uJRW-yD@VEFatHyHo;`I7-400(r>(l8W8as2P4ArNPaK`v_R zLK`TmLTHaF?%<`fDAdTDDDR&!>3TRPN4lAjEmVoxEu87{v#{4Igga|AuY9_f2QWL2 zH8q2|R7L4G!mu5FzY|*SEtCv8*Dldhop$X4*_8}6Z#-r88knO7kDGgXtrF(gIDc;e zRz8dfwpMxdQY?FdIF`hTUa%3;H%w%&kEm>KAnS+RpZcsk+>s;+Ntz;yWC;KG92x!a zRof-}Fg0_)9Ypb&=kNalR8B50Z3R&n|IIZwM#Uy-!~|@-Rq?S91rfWDTDoi(wr+%i z3rj&Hi$K>tb|Q$R-H4BWKtx2Li$W@q$bqp-l^n{l;u)-kxOjV(euZz8u)b|C&%WY77mvf(eZJD4LatZ z3_Knrwy;TkGC@HgkL{>`ndPez@L3?&H0O3Aw#fu<)!+6_edCr8P5?5*x1 zZ}WM}=E5@#pQYeRFTZC$W21Q&uStZW?h&3Qm+7kO=kw&|O?G#c`&WRNuC8efNLEzB z2KP`F4jsYE|ILksj$Cf*YybcN8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GP RWjp`?002ovPDHLkV1lc&?85*6 literal 0 HcmV?d00001 diff --git a/sources/images/icon_share_twitter.png b/sources/images/icon_share_twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..be32e128f08f04d7f91b32d4e6235416bd965b31 GIT binary patch literal 1172 zcmbVMPly{;7@s6{ciZf?MJiYk`kb|YD9y}!lT0#i*mN_=ByGT~Yc}8>JajVib~A3~ zk27ygvak}nh+8U%;Gw6|qk8C}-Qq#fQWO*sdJ}tV51vFVf}m*EH@nHUl%LKQ|4w9H!-YI_E9Wju8+}v%Dq& zRmWH)bz)e(x4tJ+9G7U>jd?m>dqp)pM=&BCq3iez&2dxHUEeU52n9{DXuEm-_b$Kfa?dJI{yDHm9$0855 zROnKk-wtZNHVaB#K!7a3lnJv4WHSQFK%Bv+fCLc~ktD)g3PDxQsSts|!!vJzwV>AZ z%D@+s@_dU@Ulqkprz3RIf)^}`2xBZlNtC1%(@2Fqmm1xa8%_=_=p-})+o!hYg2{MIse+1?tc=TY zuAI%3hBkJ?VmX_}>9VF}BnhE1QsffG5XxCiD`835!B*Xn8m>u(er@J=fW=S5s-=J! z)C(G(*B*AjY|Eow*z$Z(nv(?tYKCdM(MeRG?R<3-*l!c75_k^SVpz3ziQw_fv0N$Z zN>NcT#0Y5_et@-}hBc7|BS!7)_oDJ5ks;)J< zcbflhZcZnT_CGo~0|1+}k_*Pwb%BzjpY+X!4zNT3tD^o+~~F*8VHB z``>&x+I$AMdp;$_>-OnG$-=kew=-+~MB_nxeXV`7cw!>>ZS@oFzVhYeu{4Lr|0>mv zpZxWT(Z6@T_~N_k`vnkQy8ZI8XN~XlH%9t5W39&B>#MPs5{1}C?EQr^R6DkDV$XZO z^uYeY+~?e9e1r;P)6yUajpG7xJ4@qHlffXS(D{U8pcKHl~g_yH4RLO0;|&)=~?`~rw~ z0I@fkW{`njfNTz|25@#=V*B;|S0fPD1MyZMwgievfaL!D{fC0Fdg1rCpE*DZ~AdmxZK{qOHT_``s)><~YYss`d0K%52M0OQ z0|WFkHUk*3r2}k$cj8r8EPyQH`JAu)12Y{+`+&kg4$89uVih2kXL!2h#qS@#h)76J zx3oeHG60gXKwJ#c3=;bD<2N&sN+BSAhSdwdC0s;-)OjG@!TMrP3RqFq(OV#68JQSC zaryuMzyH7>V|e_(%~}J+fKMXkApaeJavk{JolmI0dP`n=p z;>)m5QTy?-1xSMu87N6}!R&%2xX*Z_krCMi3=G`RC 5) { +if (AmpConfig::get('refresh_limit') > 5 && AmpConfig::get('home_now_playing')) { $refresh_limit = AmpConfig::get('refresh_limit'); $ajax_url = '?page=index&action=reloadnp'; require_once AmpConfig::get('prefix') . '/templates/javascript_refresh.inc.php'; diff --git a/sources/install.php b/sources/install.php index c180b33..686c1b7 100644 --- a/sources/install.php +++ b/sources/install.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -37,6 +37,7 @@ define('INSTALL', 1); $htaccess_play_file = AmpConfig::get('prefix') . '/play/.htaccess'; $htaccess_rest_file = AmpConfig::get('prefix') . '/rest/.htaccess'; +$htaccess_channel_file = AmpConfig::get('prefix') . '/channel/.htaccess'; // Clean up incoming variables $web_path = scrub_in($_REQUEST['web_path']); @@ -65,6 +66,20 @@ if (isset($_REQUEST['transcode_template'])) { install_config_transcode_mode($mode); } +if (isset($_REQUEST['usecase'])) { + $case = $_REQUEST['usecase']; + if (Dba::check_database()) { + install_config_use_case($case); + } +} + +if (isset($_REQUEST['backends'])) { + $backends = $_REQUEST['backends']; + if (Dba::check_database()) { + install_config_backends($backends); + } +} + // Charset and gettext setup $htmllang = $_REQUEST['htmllang']; $charset = $_REQUEST['charset']; @@ -89,7 +104,7 @@ load_gettext(); header ('Content-Type: text/html; charset=' . AmpConfig::get('site_charset')); // Correct potential \ or / in the dirname -$safe_dirname = rtrim(dirname($_SERVER['PHP_SELF']),"/\\"); +$safe_dirname = get_web_path(); $web_path = $http_type . $_SERVER['HTTP_HOST'] . $safe_dirname; @@ -119,29 +134,54 @@ switch ($_REQUEST['action']) { // Now that it's inserted save the lang preference Preference::update('lang', '-1', AmpConfig::get('lang')); - - header ('Location: ' . $web_path . "/install.php?action=show_create_config&local_db=$database&local_host=$hostname&local_port=$port&htmllang=$htmllang&charset=$charset"); - break; - case 'create_config': - $download = (!isset($_POST['write'])); - $download_htaccess_rest = (isset($_POST['download_htaccess_rest'])); - $download_htaccess_play = (isset($_POST['download_htaccess_play'])); - $write_htaccess_rest = (isset($_POST['write_htaccess_rest'])); - $write_htaccess_play = (isset($_POST['write_htaccess_play'])); - - if ($write_htaccess_rest || $download_htaccess_rest) { - $created_config = install_rewrite_rules($htaccess_rest_file, $_POST['web_path'], $download_htaccess_rest); - } elseif ($write_htaccess_play || $download_htaccess_play) { - $created_config = install_rewrite_rules($htaccess_play_file, $_POST['web_path'], $download_htaccess_play); - } else { - $created_config = install_create_config($download); - } - - require_once 'templates/show_install_config.inc.php'; - break; case 'show_create_config': require_once 'templates/show_install_config.inc.php'; break; + case 'create_config': + $all = (isset($_POST['create_all'])); + $skip = (isset($_POST['skip_config'])); + if (!$skip) { + $write = (isset($_POST['write'])); + $download = (isset($_POST['download'])); + $download_htaccess_channel = (isset($_POST['download_htaccess_channel'])); + $download_htaccess_rest = (isset($_POST['download_htaccess_rest'])); + $download_htaccess_play = (isset($_POST['download_htaccess_play'])); + $write_htaccess_channel = (isset($_POST['write_htaccess_channel'])); + $write_htaccess_rest = (isset($_POST['write_htaccess_rest'])); + $write_htaccess_play = (isset($_POST['write_htaccess_play'])); + + $created_config = true; + if ($write_htaccess_channel || $download_htaccess_channel || $all) { + $created_config = $created_config && install_rewrite_rules($htaccess_channel_file, $_POST['web_path'], $download_htaccess_channel); + } + if ($write_htaccess_rest || $download_htaccess_rest || $all) { + $created_config = $created_config && install_rewrite_rules($htaccess_rest_file, $_POST['web_path'], $download_htaccess_rest); + } + if ($write_htaccess_play || $download_htaccess_play || $all) { + $created_config = $created_config && install_rewrite_rules($htaccess_play_file, $_POST['web_path'], $download_htaccess_play); + } + if ($write || $download || $all) { + $created_config = $created_config && install_create_config($download); + } + } + case 'show_create_account': + $results = parse_ini_file($configfile); + if (!isset($created_config)) $created_config = true; + + /* Make sure we've got a valid config file */ + if (!check_config_values($results) || !$created_config) { + Error::add('general', T_('Error: Config files not found or unreadable')); + require_once AmpConfig::get('prefix') . '/templates/show_install_config.inc.php'; + break; + } + + // Don't try to add administrator user on existing database + if (install_check_status($configfile)) { + require_once AmpConfig::get('prefix') . '/templates/show_install_account.inc.php'; + } else { + header ("Location: " . $web_path . '/login.php'); + } + break; case 'create_account': $results = parse_ini_file($configfile); AmpConfig::set_by_array($results, true); @@ -153,19 +193,13 @@ switch ($_REQUEST['action']) { break; } - header ("Location: " . $web_path . '/login.php'); - break; - case 'show_create_account': - $results = parse_ini_file($configfile); + // Automatically log-in the newly created user + Session::create_cookie(); + Session::create(array('type' => 'mysql', 'username' => $username)); + $_SESSION['userdata']['username'] = $username; + Session::check(); - /* Make sure we've got a valid config file */ - if (!check_config_values($results)) { - Error::add('general', T_('Error: Config file not found or unreadable')); - require_once AmpConfig::get('prefix') . '/templates/show_install_config.inc.php'; - break; - } - - require_once AmpConfig::get('prefix') . '/templates/show_install_account.inc.php'; + header ("Location: " . $web_path . '/index.php'); break; case 'init': require_once 'templates/show_install.inc.php'; diff --git a/sources/labels.php b/sources/labels.php new file mode 100644 index 0000000..4ba238d --- /dev/null +++ b/sources/labels.php @@ -0,0 +1,111 @@ +id . '`.', 1); + UI::access_denied(); + exit; + } + + if ($label->remove()) { + show_confirmation(T_('Label Deletion'), T_('Label has been deleted.'), AmpConfig::get('web_path')); + } else { + show_confirmation(T_('Label Deletion'), T_('Cannot delete this label.'), AmpConfig::get('web_path')); + } + break; + case 'add_label': + // Must be at least a content manager or edit upload enabled + if (!Access::check('interface','50') && !AmpConfig::get('upload_allow_edit')) { + UI::access_denied(); + exit; + } + + if (!Core::form_verify('add_label','post')) { + UI::access_denied(); + exit; + } + + // Remove unauthorized defined values from here + if (isset($_POST['user'])) { + unset($_POST['user']); + } + if (isset($_POST['creation_date'])) { + unset($_POST['creation_date']); + } + + $label_id = Label::create($_POST); + if (!$label_id) { + require_once AmpConfig::get('prefix') . '/templates/show_add_label.inc.php'; + } else { + $body = T_('Label Added'); + $title = ''; + show_confirmation($title, $body, AmpConfig::get('web_path') . '/browse.php?action=label'); + } + break; + case 'show': + $label_id = intval($_REQUEST['label']); + if (!$label_id) { + if (!empty($_REQUEST['name'])) { + $label_id = Label::lookup($_REQUEST); + } + } + if ($label_id > 0) { + $label = new Label($label_id); + $label->format(); + $object_ids = $label->get_artists(); + $object_type = 'artist'; + require_once AmpConfig::get('prefix') . '/templates/show_label.inc.php'; + UI::show_footer(); + exit; + } + case 'show_add_label': + if (Access::check('interface','50') || AmpConfig::get('upload_allow_edit')) { + require_once AmpConfig::get('prefix') . '/templates/show_add_label.inc.php'; + } else { + echo T_('Label cannot be found.'); + } + break; +} // end switch + +UI::show_footer(); diff --git a/sources/lib/.htaccess b/sources/lib/.htaccess index 896fbc5..cfd4051 100644 --- a/sources/lib/.htaccess +++ b/sources/lib/.htaccess @@ -1,2 +1,10 @@ -Order deny,allow -Deny from all \ No newline at end of file +# Apache 2.4 + + Require all denied + + +# Apache 2.2 + + Order deny,allow + Deny from all + \ No newline at end of file diff --git a/sources/lib/batch.lib.php b/sources/lib/batch.lib.php index 9d26e8c..e06adc3 100644 --- a/sources/lib/batch.lib.php +++ b/sources/lib/batch.lib.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,93 +21,97 @@ */ /** - * get_song_files + * get_media_files * - * Takes an array of song ids and returns an array of the actual filenames + * Takes an array of media ids and returns an array of the actual filenames * * @param array $media_ids Media IDs. */ -function get_song_files($media_ids) +function get_media_files($media_ids) { $media_files = array(); $total_size = 0; foreach ($media_ids as $element) { if (is_array($element)) { - $type = array_shift($element); - $media = new $type(array_shift($element)); + if (isset($element['object_type'])) { + $type = $element['object_type']; + $id = $element['object_id']; + } else { + $type = array_shift($element); + $id = array_shift($element); + } + $media = new $type($id); } else { $media = new Song($element); } if ($media->enabled) { - $total_size += sprintf("%.2f",($media->size/1048576)); $media->format(); - $dirname = $media->f_album_full; - //debug_event('batch.lib.php', 'Songs file {'.$media->file.'}...', '5'); + $total_size += sprintf("%.2f",($media->size/1048576)); + $dirname = ''; + $parent = $media->get_parent(); + if ($parent != null) { + $pobj = new $parent['object_type']($parent['object_id']); + $pobj->format(); + $dirname = $pobj->get_fullname(); + } if (!array_key_exists($dirname, $media_files)) { $media_files[$dirname] = array(); } - array_push($media_files[$dirname], $media->file); + array_push($media_files[$dirname], Core::conv_lc_file($media->file)); } } return array($media_files, $total_size); -} //get_song_files +} //get_media_files /** * send_zip * - * takes array of full paths to songs + * takes array of full paths to medias * zips them and sends them * * @param string $name name of the zip file to be created - * @param array $song_files array of full paths to songs to zip create w/ call to get_song_files + * @param array $media_files array of full paths to medias to zip create w/ call to get_media_files */ -function send_zip($name, $song_files) +function send_zip($name, $media_files) { - // Check if they want to save it to a file, if so then make sure they've - // got a defined path as well and that it's writable. - $basedir = ''; - if (AmpConfig::get('file_zip_download') && AmpConfig::get('tmp_dir_path')) { - // Check writeable - if (!is_writable(AmpConfig::get('tmp_dir_path'))) { - $in_memory = '1'; - debug_event('Error','File Zip Path:' . AmpConfig::get('tmp_dir_path') . ' is not writable','1'); - } else { - $in_memory = '0'; - $basedir = AmpConfig::get('tmp_dir_path'); - } - } else { - $in_memory = '1'; - } // if file downloads - /* Require needed library */ - require_once AmpConfig::get('prefix') . '/modules/archive/archive.lib.php'; - $arc = new zip_file($name . ".zip" ); + require_once AmpConfig::get('prefix') . '/modules/ZipStream/ZipStream.php'; + $arc = new ZipStream\ZipStream($name . ".zip" ); $options = array( - 'inmemory' => $in_memory, // create archive in memory - 'basedir' => $basedir, - 'storepaths' => 0, // only store file name, not full path - 'level' => 0, // no compression 'comment' => AmpConfig::get('file_zip_comment'), - 'type' => "zip" ); - $arc->set_options( $options ); - foreach ($song_files as $dir => $files) { - $arc->add_files($files, $dir); + foreach ($media_files as $dir => $files) { + foreach ($files as $file) { + $arc->addFileFromPath($dir . "/" . basename($file), $file, $options); + } } - if (count($arc->error)) { - debug_event('archive',"Error: unable to add songs",'3'); - return false; - } // if failed to add songs - - if (!$arc->create_archive()) { - debug_event('archive',"Error: unable to create archive",'3'); - return false; - } // if failed to create archive - - $arc->download_file(); - + $arc->finish(); } // send_zip + +/** + * check_can_zip + * + * Check that an object type is allowed to be zipped. + * + * @param string $object_type + */ +function check_can_zip($object_type) +{ + $allowed = true; + if (AmpConfig::get('allow_zip_types')) { + $allowed = false; + $allowed_types = explode(',', AmpConfig::get('allow_zip_types')); + foreach ($allowed_types as $atype) { + if (trim($atype) == $object_type) { + $allowed = true; + break; + } + } + } + + return $allowed; +} diff --git a/sources/lib/class/access.class.php b/sources/lib/class/access.class.php index ae09ddb..4a2c1f7 100644 --- a/sources/lib/class/access.class.php +++ b/sources/lib/class/access.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,27 +30,68 @@ class Access { // Variables from DB + + /** + * @var int $id + */ public $id; + /** + * @var string $name + */ public $name; + /** + * @var string $start + */ public $start; + /** + * @var string $end + */ public $end; + /** + * @var int $level + */ public $level; + /** + * @var int $user + */ public $user; + /** + * @var string $type + */ public $type; + /** + * @var boolean $enabled + */ public $enabled; + /** + * @var string $f_start + */ public $f_start; + /** + * @var string $f_end + */ public $f_end; + /** + * @var string $f_user + */ public $f_user; + /** + * @var string $f_level + */ public $f_level; + /** + * @var string $f_type + */ public $f_type; /** * constructor * * Takes an ID of the access_id dealie :) + * @param int|null $access_id */ - public function __construct($access_id = '') + public function __construct($access_id = null) { if (!$access_id) { return false; } @@ -69,6 +110,7 @@ class Access * _get_info * * Gets the vars for $this out of the database. + * @return array */ private function _get_info() { @@ -100,6 +142,9 @@ class Access * _verify_range * * This outputs an error if the IP range is bad. + * @param string $startp + * @param string $endp + * @return boolean */ private static function _verify_range($startp, $endp) { @@ -128,8 +173,10 @@ class Access * * This function takes a named array as a datasource and updates the current * access list entry. + * @param array $data + * @return boolean */ - public function update($data) + public function update(array $data) { if (!self::_verify_range($data['start'], $data['end'])) { return false; @@ -156,8 +203,10 @@ class Access * * This takes a keyed array of data and trys to insert it as a * new ACL entry + * @param array $data + * @return boolean */ - public static function create($data) + public static function create(array $data) { if (!self::_verify_range($data['start'], $data['end'])) { return false; @@ -191,8 +240,10 @@ class Access * * This sees if the ACL that we've specified already exists in order to * prevent duplicates. The name is ignored. + * @param array $data + * @return boolean */ - public static function exists($data) + public static function exists(array $data) { $start = inet_pton($data['start']); $end = inet_pton($data['end']); @@ -214,6 +265,7 @@ class Access * delete * * deletes the specified access_list entry + * @param int $id */ public static function delete($id) { @@ -224,24 +276,26 @@ class Access * check_function * * This checks if specific functionality is enabled. + * @param string $type + * @return boolean */ public static function check_function($type) { switch ($type) { case 'download': - return AmpConfig::get('download'); + return make_bool(AmpConfig::get('download')); case 'batch_download': if (!function_exists('gzcompress')) { debug_event('access', 'ZLIB extension not loaded, batch download disabled', 3); return false; } - if (AmpConfig::get('allow_zip_download') AND $GLOBALS['user']->has_access('25')) { - return AmpConfig::get('download'); + if (AmpConfig::get('allow_zip_download') AND $GLOBALS['user']->has_access('5')) { + return make_bool(AmpConfig::get('download')); } break; - default: - return false; } + + return false; } /** @@ -249,6 +303,11 @@ class Access * * This takes a type, ip, user, level and key and then returns whether they * are allowed. The IP is passed as a dotted quad. + * @param string $type + * @param int|string $user + * @param int $level + * @param string $ip + * @return boolean */ public static function check_network($type, $user, $level, $ip=null) { @@ -313,8 +372,12 @@ class Access * * Everything uses the global 0,5,25,50,75,100 stuff. GLOBALS['user'] is * always used. + * @param string $type + * @param int $level + * @param int|null $user + * @return boolean */ - public static function check($type, $level) + public static function check($type, $level, $user_id=null) { if (AmpConfig::get('demo_mode')) { return true; @@ -323,6 +386,10 @@ class Access return true; } + $user = $GLOBALS['user']; + if ($user_id) { + $user = new User($user_id); + } $level = intval($level); // Switch on the type @@ -330,10 +397,10 @@ class Access case 'localplay': // Check their localplay_level return (AmpConfig::get('localplay_level') >= $level - || $GLOBALS['user']->access >= 100); + || $user->access >= 100); case 'interface': // Check their standard user level - return ($GLOBALS['user']->access >= $level); + return ($user->access >= $level); default: return false; } @@ -344,6 +411,8 @@ class Access * * This validates the specified type; it will always return a valid type, * even if you pass in an invalid one. + * @param string $type + * @return string */ public static function validate_type($type) { @@ -360,6 +429,7 @@ class Access /** * get_access_lists * returns a full listing of all access rules on this server + * @return array */ public static function get_access_lists() { @@ -380,6 +450,7 @@ class Access * get_level_name * * take the int level and return a named level + * @return string */ public function get_level_name() { @@ -401,6 +472,7 @@ class Access * get_user_name * * Return a name for the users covered by this ACL. + * @return string */ public function get_user_name() { @@ -414,6 +486,7 @@ class Access * get_type_name * * This function returns the pretty name for our current type. + * @return string */ public function get_type_name() { diff --git a/sources/lib/class/ajax.class.php b/sources/lib/class/ajax.class.php index 83a6cbd..057ce1e 100644 --- a/sources/lib/class/ajax.class.php +++ b/sources/lib/class/ajax.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -31,6 +31,7 @@ class Ajax { private static $include_override; + private static $counter = 0; /** * constructor @@ -46,6 +47,11 @@ class Ajax * observe * This returns a string with the correct and full ajax 'observe' stuff * from jQuery + * @param string $source + * @param string $method + * @param string $action + * @param string $confirm + * @return string */ public static function observe($source, $method, $action, $confirm='') { @@ -59,10 +65,15 @@ class Ajax $observe = ""; @@ -73,6 +84,8 @@ class Ajax /** * url * This takes a string and makes an URL + * @param string $action + * @return string */ public static function url($action) { @@ -83,6 +96,10 @@ class Ajax * action * This takes the action, the source and the post (if passed) and * generates the full ajax link + * @param string $action + * @param string $source + * @param string $post + * @return string */ public static function action($action, $source, $post='') { @@ -110,6 +127,14 @@ class Ajax * button * This prints out an img of the specified icon with the specified alt * text and then sets up the required ajax for it. + * @param string $action + * @param string $icon + * @param string $alt + * @param string $source + * @param string $post + * @param string $class + * @param string $confirm + * @return string */ public static function button($action, $icon, $alt, $source='', $post='', $class='', $confirm='') { @@ -138,24 +163,35 @@ class Ajax * text * This prints out the specified text as a link and sets up the required * ajax for the link so it works correctly + * @param string $action + * @param string $text + * @param string $source + * @param string $post + * @param string $class + * @return string */ public static function text($action, $text, $source, $post='', $class='') { - // Avoid duplicate id - $source .= '_' . time(); + // Temporary workaround to avoid sorting on custom base requests + if (!defined("NO_BROWSE_SORTING") || strpos($source, "sort_") === false) { + // Avoid duplicate id + $source .= '_' . time() . '_' . self::$counter++; - // Format the string we wanna use - $ajax_string = self::action($action, $source, $post); + // Format the string we wanna use + $ajax_string = self::action($action, $source, $post); - // If they passed a span class - if ($class) { - $class = ' class="' . $class . '"'; + // If they passed a span class + if ($class) { + $class = ' class="' . $class . '"'; + } + + $string = "$text\n"; + + $string .= self::observe($source, 'click', $ajax_string); + } else { + $string = $text; } - $string = "$text\n"; - - $string .= self::observe($source, 'click', $ajax_string); - return $string; } // text @@ -163,6 +199,7 @@ class Ajax /** * run * This runs the specified action no questions asked + * @param string $action */ public static function run($action) { @@ -176,6 +213,7 @@ class Ajax * set_include_override * This sets the including div override, used only one place. Kind of a * hack. + * @param bool $value */ public static function set_include_override($value) { @@ -187,6 +225,8 @@ class Ajax * start_container * This checks to see if we're AJAXin'. If we aren't then it echoes out * the html needed to start a container that can be replaced by Ajax. + * @param string $name + * @param string $class */ public static function start_container($name, $class = '') { diff --git a/sources/lib/class/album.class.php b/sources/lib/class/album.class.php index c5bd40e..be71bee 100644 --- a/sources/lib/class/album.class.php +++ b/sources/lib/class/album.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * Copyright 2001 - 2015 Ampache.org * * * This program is free software; you can redistribute it and/or @@ -28,39 +28,153 @@ * it is related to the album table in the database. * */ -class Album extends database_object +class Album extends database_object implements library_item { /* Variables from DB */ - public $id; - public $name; - public $disk; - public $year; - public $prefix; - public $mbid; // MusicBrainz ID + /** + * @var int $id + */ + public $id; + /** + * @var string $name + */ + public $name; + /** + * @var int $album_artist + */ + public $album_artist; + /** + * @var int $disk + */ + public $disk; + /** + * @var int $year + */ + public $year; + /** + * @var string $prefix + */ + public $prefix; + /** + * @var string $mbid + */ + public $mbid; // MusicBrainz ID + /** + * @var string $mbid_group + */ + public $mbid_group; // MusicBrainz Release Group ID + /** + * @var string $release_type + */ + public $release_type; + + /** + * @var int $catalog_id + */ + public $catalog_id; + /** + * @var int $song_count + */ public $song_count; + /** + * @var string $artist_prefix + */ public $artist_prefix; + /** + * @var string $artist_name + */ public $artist_name; + /** + * @var int $artist_id + */ public $artist_id; + /** + * @var array $tags + */ public $tags; + /** + * @var string $full_name + */ public $full_name; // Prefix + Name, generated + /** + * @var int $artist_count + */ public $artist_count; + /** + * @var string $f_artist_name + */ public $f_artist_name; + /** + * @var string $f_artist_link + */ public $f_artist_link; + /** + * @var string $f_artist + */ public $f_artist; + /** + * @var string $album_artist_name + */ + public $album_artist_name; + /** + * @var string $f_album_artist_name + */ + public $f_album_artist_name; + /** + * @var string $f_album_artist_link + */ + public $f_album_artist_link; + /** + * @var string $f_name + */ public $f_name; - public $f_name_link; - public $f_link_src; + /** + * @var string $link + */ + public $link; + /** + * @var string $f_link + */ public $f_link; + /** + * @var string $f_tags + */ public $f_tags; + /** + * @var string $f_year + */ + public $f_year; + /** + * @var string $f_title + */ public $f_title; + /** + * @var string $f_release_type + */ + public $f_release_type; // cached information + /** + * @var boolean $_fake + */ public $_fake; + /** + * @var array $_songs + */ public $_songs = array(); + /** + * @var array $_mapcache + */ private static $_mapcache = array(); + /** + * @var array $album_suite + */ public $album_suite = array(); + /** + * @var boolean $allow_group_disks + */ public $allow_group_disks = false; /** @@ -69,8 +183,9 @@ class Album extends database_object * to this album from the database it does not * pull the album or thumb art by default or * get any of the counts. + * @param int|null $id */ - public function __construct($id='') + public function __construct($id=null) { if (!$id) { return false; } @@ -98,8 +213,10 @@ class Album extends database_object * construct_from_array * This is often used by the metadata class, it fills out an album object from a * named array, _fake is set to true + * @param array $data + * @return Album */ - public static function construct_from_array($data) + public static function construct_from_array(array $data) { $album = new Album(0); foreach ($data as $key=>$value) { @@ -126,8 +243,10 @@ class Album extends database_object * build_cache * This takes an array of object ids and caches all of their information * with a single query + * @param array $ids + * @return boolean */ - public static function build_cache($ids) + public static function build_cache(array $ids) { // Nothing to do if they pass us nothing if (!is_array($ids) OR !count($ids)) { @@ -140,7 +259,7 @@ class Album extends database_object $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { - parent::add_to_cache('album',$row['id'],$row); + parent::add_to_cache('album', $row['id'], $row); } return true; @@ -151,9 +270,14 @@ class Album extends database_object * _get_extra_info * This pulls the extra information from our tables, this is a 3 table join, which is why we don't normally * do it + * @return array */ - private function _get_extra_info() + private function _get_extra_info($limit_threshold = '') { + if (!$this->id) { + return array(); + } + if (parent::is_cached('album_extra', $this->id)) { return parent::get_from_cache('album_extra', $this->id); } @@ -203,7 +327,7 @@ class Album extends database_object $results['has_thumb'] = make_bool($art->thumb); if (AmpConfig::get('show_played_times')) { - $results['object_cnt'] = Stats::get_object_count('album', $this->id); + $results['object_cnt'] = Stats::get_object_count('album', $this->id, $limit_threshold); } parent::add_to_cache('album_extra', $this->id, $results); @@ -212,18 +336,55 @@ class Album extends database_object } // _get_extra_info + public function can_edit($user = null) + { + if (!$user) { + $user = $GLOBALS['user']->id; + } + + if (!$user) + return false; + + if ($this->user !== null && $user == $this->user) + return true; + + if (Access::check('interface', 50, $user)) + return true; + + if (!$this->album_artist) + return false; + + if (!AmpConfig::get('upload_allow_edit')) + return false; + + $owner = $this->get_user_owner(); + return ($owner && $owner === $user); + } + /** * check * * Searches for an album; if none is found, insert a new one. + * @param string $name + * @param int $year + * @param int $disk + * @param string $mbid + * @param string $mbid_group + * @param string $album_artist + * @param string $release_type + * @param boolean $readonly + * @return int|null */ - public static function check($name, $year = 0, $disk = 0, $mbid = null, $readonly = false) + public static function check($name, $year = 0, $disk = 0, $mbid = null, $mbid_group = null, $album_artist = null, $release_type = null, $readonly = false) { - if ($mbid == '') $mbid = null; - $trimmed = Catalog::trim_prefix(trim($name)); $name = $trimmed['string']; $prefix = $trimmed['prefix']; + $album_artist = intval($album_artist); + $album_artist = ($album_artist <= 0) ? null : $album_artist; + $mbid = empty($mbid) ? null : $mbid; + $mbid_group = empty($mbid_group) ? null : $mbid_group; + $release_type = empty($release_type) ? null : $release_type; // Not even sure if these can be negative, but better safe than llama. $year = abs(intval($year)); @@ -233,35 +394,36 @@ class Album extends database_object $name = T_('Unknown (Orphaned)'); $year = 0; $disk = 0; + $album_artist = null; + } + if (isset(self::$_mapcache[$name][$disk][$mbid][$album_artist])) { + return self::$_mapcache[$name][$disk][$mbid][$album_artist]; } - if (isset(self::$_mapcache[$name][$year][$disk][$mbid])) { - return self::$_mapcache[$name][$year][$disk][$mbid]; - } - - $sql = 'SELECT `id` FROM `album` WHERE `name` = ? AND `disk` = ? AND `year` = ? AND `mbid` '; - $params = array($name, $disk, $year); + $sql = 'SELECT `album`.`id` FROM `album` WHERE `album`.`name` = ? AND `album`.`disk` = ? '; + $params = array($name, $disk); if ($mbid) { - $sql .= '= ? '; + $sql .= 'AND `album`.`mbid` = ? '; $params[] = $mbid; } else { - $sql .= 'IS NULL '; + $sql .= 'AND `album`.`mbid` IS NULL '; + } + if ($prefix) { + $sql .= 'AND `album`.`prefix` = ? '; + $params[] = $prefix; } - $sql .= 'AND `prefix` '; - if ($prefix) { - $sql .= '= ?'; - $params[] = $prefix; - } else { - $sql .= 'IS NULL'; + if ($album_artist) { + $sql .= 'AND `album`.`album_artist` = ? '; + $params[] = $album_artist; } $db_results = Dba::read($sql, $params); if ($row = Dba::fetch_assoc($db_results)) { $id = $row['id']; - self::$_mapcache[$name][$year][$disk][$mbid] = $id; + self::$_mapcache[$name][$disk][$mbid][$album_artist] = $id; return $id; } @@ -269,9 +431,9 @@ class Album extends database_object return null; } - $sql = 'INSERT INTO `album` (`name`, `prefix`, `year`, `disk`, `mbid`) VALUES (?, ?, ?, ?, ?)'; + $sql = 'INSERT INTO `album` (`name`, `prefix`, `year`, `disk`, `mbid`, `mbid_group`, `release_type`, `album_artist`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; - $db_results = Dba::write($sql, array($name, $prefix, $year, $disk, $mbid)); + $db_results = Dba::write($sql, array($name, $prefix, $year, $disk, $mbid, $mbid_group, $release_type, $album_artist)); if (!$db_results) { return null; } @@ -287,7 +449,7 @@ class Album extends database_object } } - self::$_mapcache[$name][$year][$disk][$mbid] = $id; + self::$_mapcache[$name][$disk][$mbid][$album_artist] = $id; return $id; } @@ -296,6 +458,9 @@ class Album extends database_object * gets the songs for this album takes an optional limit * and an optional artist, if artist is passed it only gets * songs with this album + specified artist + * @param int $limit + * @param string $artist + * @return int[] */ public function get_songs($limit = 0,$artist='') { @@ -331,6 +496,8 @@ class Album extends database_object /** * get_http_album_query_ids * return the html album parameters with all album suite ids + * @param string $url_param_name + * @return string */ public function get_http_album_query_ids($url_param_name) { @@ -346,6 +513,7 @@ class Album extends database_object /** * get_group_disks_ids * return all album suite ids or current album if no albums + * @return int[] */ public function get_group_disks_ids() { @@ -360,14 +528,16 @@ class Album extends database_object /** * get_album_suite * gets the album ids with the same musicbrainz identifier + * @param int $catalog + * return int[] */ - public function get_album_suite($catalog = '') + public function get_album_suite($catalog = 0) { $results = array(); $catalog_where = ""; $catalog_join = "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog`"; - if (!empty($catalog)) { + if ($catalog) { $catalog_where .= " AND `catalog`.`id` = '$catalog'"; } if (AmpConfig::get('catalog_disable')) { @@ -390,6 +560,8 @@ class Album extends database_object /** * has_track * This checks to see if this album has a track of the specified title + * @param string $title + * @return array */ public function has_track($title) { @@ -402,39 +574,69 @@ class Album extends database_object } // has_track + /** + * get_addtime_first_song + * Get the add date of first added song. + * @return int + */ + public function get_addtime_first_song() + { + $time = 0; + + $sql = "SELECT MIN(`addition_time`) FROM `song` WHERE `album` = ?"; + $db_results = Dba::read($sql, array($this->id)); + if ($data = Dba::fetch_row($db_results)) { + $time = $data[0]; + } + + return $time; + } + /** * format * This is the format function for this object. It sets cleaned up * album information with the base required * f_link, f_name */ - public function format() + public function format($details = true, $limit_threshold = '') { $web_path = AmpConfig::get('web_path'); - /* Pull the advanced information */ - $data = $this->_get_extra_info(); - foreach ($data as $key=>$value) { $this->$key = $value; } + if ($details) { + /* Pull the advanced information */ + $data = $this->_get_extra_info($limit_threshold); + foreach ($data as $key=>$value) { $this->$key = $value; } + + if ($this->album_artist) { + $Album_artist = new Artist($this->album_artist); + $Album_artist->format(); + $this->album_artist_name = $Album_artist->name; + $this->f_album_artist_name = $Album_artist->f_name; + $this->f_album_artist_link = "album_artist . "\" title=\"" . scrub_out($this->album_artist_name) . "\">" . $this->f_album_artist_name . ""; + } + + $this->tags = Tag::get_top_tags('album', $this->id); + $this->f_tags = Tag::get_display($this->tags, true, 'album'); + } /* Truncate the string if it's to long */ $this->f_name = $this->full_name; - $this->f_link_src = $web_path . '/albums.php?action=show&album=' . scrub_out($this->id); - $this->f_name_link = "f_link_src . "\" title=\"" . scrub_out($this->full_name) . "\">" . scrub_out($this->f_name); + $this->link = $web_path . '/albums.php?action=show&album=' . scrub_out($this->id); + $this->f_link = "link . "\" title=\"" . scrub_out($this->full_name) . "\">" . scrub_out($this->f_name); // Looking if we need to combine or display disks if ($this->disk && (!$this->allow_group_disks || ($this->allow_group_disks && !AmpConfig::get('album_group')))) { - $this->f_name_link .= " [" . T_('Disk') . " " . $this->disk . "]"; + $this->f_link .= " [" . T_('Disk') . " " . $this->disk . "]"; } - $this->f_name_link .=""; + $this->f_link .=""; - $this->f_link = $this->f_name_link; $this->f_title = $this->full_name; if ($this->artist_count == '1') { $artist = trim(trim($this->artist_prefix) . ' ' . trim($this->artist_name)); $this->f_artist_name = $artist; - $this->f_artist_link = "artist_id . "\" title=\"" . scrub_out($this->artist_name) . "\">" . $artist . ""; + $this->f_artist_link = "artist_id . "\" title=\"" . scrub_out($this->artist_name) . "\">" . $artist . ""; $this->f_artist = $artist; } else { $this->f_artist_link = "artist_count " . T_('Artists') . "\">" . T_('Various') . ""; @@ -442,18 +644,180 @@ class Album extends database_object $this->f_artist_name = $this->f_artist; } - if ($this->year == '0') { - $this->year = "N/A"; + if (!$this->year) { + $this->f_year = "N/A"; } - $this->tags = Tag::get_top_tags('album', $this->id); - $this->f_tags = Tag::get_display($this->tags); + $this->f_release_type = ucwords($this->release_type); } // format + /** + * Get item keywords for metadata searches. + * @return array + */ + public function get_keywords() + { + $keywords = array(); + $keywords['mb_albumid'] = array('important' => false, + 'label' => T_('Album MusicBrainzID'), + 'value' => $this->mbid); + $keywords['mb_albumid_group'] = array('important' => false, + 'label' => T_('Release Group MusicBrainzID'), + 'value' => $this->mbid_group); + $keywords['artist'] = array('important' => true, + 'label' => T_('Artist'), + 'value' => (($this->artist_count < 2) ? $this->f_artist_name : '')); + $keywords['album'] = array('important' => true, + 'label' => T_('Album'), + 'value' => $this->f_name); + + return $keywords; + } + + /** + * Get item fullname. + * @return string + */ + public function get_fullname() + { + return $this->f_name; + } + + /** + * Get parent item description. + * @return array|null + */ + public function get_parent() + { + if ($this->artist_count == 1) { + return array('object_type' => 'artist', 'object_id' => $this->artist_id); + } + + return null; + } + + /** + * Get item childrens. + * @return array + */ + public function get_childrens() + { + return $this->get_medias(); + } + + /** + * Search for item childrens. + * @param string $name + * @return array + */ + public function search_childrens($name) + { + $search['type'] = "song"; + $search['rule_0_input'] = $name; + $search['rule_0_operator'] = 4; + $search['rule_0'] = "title"; + $search['rule_1_input'] = $this->name; + $search['rule_1_operator'] = 4; + $search['rule_1'] = "album"; + $search['rule_2_input'] = $this->album_artist_name; + $search['rule_2_operator'] = 4; + $search['rule_2'] = "artist"; + $songs = Search::run($search); + + $childrens = array(); + foreach ($songs as $song) { + $childrens[] = array( + 'object_type' => 'song', + 'object_id' => $song + ); + } + + return $childrens; + } + + /** + * Get all childrens and sub-childrens medias. + * @param string $filter_type + * @return array + */ + public function get_medias($filter_type = null) + { + $medias = array(); + if (!$filter_type || $filter_type == 'song') { + $songs = $this->get_songs(); + foreach ($songs as $song_id) { + $medias[] = array( + 'object_type' => 'song', + 'object_id' => $song_id + ); + } + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array($this->catalog_id); + } + + /** + * Get item's owner. + * @return int|null + */ + public function get_user_owner() + { + if (!$this->album_artist) + return null; + + $artist = new Artist($this->album_artist); + return $artist->get_user_owner(); + } + + /** + * Get default art kind for this item. + * @return string + */ + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + // Album description is not supported yet, always return artist description + $artist = new Artist($this->artist_id); + return $artist->get_description(); + } + + public function display_art($thumb = 2) + { + $id = null; + $type = null; + + if (Art::has_db($this->id, 'album')) { + $id = $this->id; + $type = 'album'; + } else if (Art::has_db($this->artist_id, 'artist')) { + $id = $this->artist_id; + $type = 'artist'; + } + + if ($id !== null && $type !== null) { + Art::display($type, $id, $this->get_fullname(), $thumb, $this->link); + } + } + /** * get_random_songs * gets a random number, and a random assortment of songs from this album + * @return int[] */ public function get_random_songs() { @@ -481,17 +845,27 @@ class Album extends database_object * update * This function takes a key'd array of data and updates this object * as needed + * @param array $data + * @return int */ - public function update($data) + public function update(array $data) { - $year = $data['year']; - $artist = $data['artist']; - $name = $data['name']; - $disk = $data['disk']; - $mbid = $data['mbid']; + $year = isset($data['year']) ? $data['year'] : $this->year; + $artist = isset($data['artist']) ? intval($data['artist']) : $this->artist_id; + $album_artist = isset($data['album_artist']) ? intval($data['album_artist']) : $this->album_artist; + $name = isset($data['name']) ? $data['name'] : $this->name; + $disk = isset($data['disk']) ? $data['disk']: $this->disk; + $mbid = isset($data['mbid']) ? $data['mbid'] : $this->mbid; + $mbid_group = isset($data['mbid_group']) ? $data['mbid_group'] : $this->mbid_group; + $release_type = isset($data['release_type']) ? $data['release_type'] : $this->release_type; $current_id = $this->id; + if (!empty($data['album_artist_name'])) { + // Need to create new artist according the name + $album_artist = Artist::check($data['album_artist_name']); + } + $updated = false; $songs = null; if ($artist != $this->artist_id AND $artist) { @@ -504,17 +878,31 @@ class Album extends database_object Artist::gc(); } - $album_id = self::check($name, $year, $disk, $mbid); + $album_id = self::check($name, $year, $disk, $mbid, $mbid_group, $album_artist, $release_type); if ($album_id != $this->id) { if (!is_array($songs)) { $songs = $this->get_songs(); } foreach ($songs as $song_id) { Song::update_album($album_id,$song_id); Song::update_year($year,$song_id); + Song::write_id3_for_song($song_id); } $current_id = $album_id; $updated = true; + Stats::migrate('album', $this->id, $album_id); + Art::migrate('album', $this->id, $album_id); self::gc(); + } else { + Album::update_year($year, $album_id); + Album::update_mbid_group($mbid_group, $album_id); + Album::update_release_type($release_type, $album_id); } + $this->year = $year; + $this->mbid_group = $mbid_group; + $this->release_type = $release_type; + $this->name = $name; + $this->disk = $disk; + $this->mbid = $mbid; + $this->album_artist = $album_artist; if ($updated && is_array($songs)) { foreach ($songs as $song_id) { @@ -525,11 +913,19 @@ class Album extends database_object Userflag::gc(); } // if updated - $override_songs = false; - if ($data['apply_childs'] == 'checked') { - $override_songs = true; + $override_childs = false; + if ($data['overwrite_childs'] == 'checked') { + $override_childs = true; + } + + $add_to_childs = false; + if ($data['add_to_childs'] == 'checked') { + $add_to_childs = true; + } + + if (isset($data['edit_tags'])) { + $this->update_tags($data['edit_tags'], $override_childs, $add_to_childs, $current_id, true); } - $this->update_tags($data['edit_tags'], $override_songs, $current_id); return $current_id; @@ -539,27 +935,106 @@ class Album extends database_object * update_tags * * Update tags of albums and/or songs + * @param string $tags_comma + * @param boolean $override_childs + * @param boolean $add_to_childs + * @param int|null $current_id */ - public function update_tags($tags_comma, $override_songs, $current_id = null) + public function update_tags($tags_comma, $override_childs, $add_to_childs, $current_id = null, $force_update = false) { if ($current_id == null) { $current_id = $this->id; } - Tag::update_tag_list($tags_comma, 'album', $current_id); + // When current_id not empty we force to overwrite current object + Tag::update_tag_list($tags_comma, 'album', $current_id, $force_update ? true : $override_childs); - if ($override_songs) { + if ($override_childs || $add_to_childs) { $songs = $this->get_songs(); foreach ($songs as $song_id) { - Tag::update_tag_list($tags_comma, 'song', $song_id); + Tag::update_tag_list($tags_comma, 'song', $song_id, $override_childs); } } } + public function remove_from_disk() + { + $deleted = true; + $song_ids = $this->get_songs(); + foreach ($song_ids as $id) { + $song = new Song($id); + $deleted = $song->remove_from_disk(); + if (!$deleted) { + debug_event('album', 'Error when deleting the song `' . $id .'`.', 1); + break; + } + } + + if ($deleted) { + $sql = "DELETE FROM `album` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + if ($deleted) { + Art::gc('album', $this->id); + Userflag::gc('album', $this->id); + Rating::gc('album', $this->id); + Shoutbox::gc('album', $this->id); + } + } + + return $deleted; + } + + /** + * Update album year. + * @param int $year + * @param int $album_id + */ + public static function update_year($year, $album_id) + { + self::update_field('year', $year, $album_id); + } + + /** + * Update album mbid group. + * @param string $mbid_group + * @param int $album_id + */ + public static function update_mbid_group($mbid_group, $album_id) + { + $mbid_group = (!empty($mbid_group)) ? $mbid_group : null; + self::update_field('mbid_group', $mbid_group, $album_id); + } + + /** + * Update album release type. + * @param string $release_type + * @param int $album_id + */ + public static function update_release_type($release_type, $album_id) + { + $release_type = (!empty($release_type)) ? $release_type : null; + self::update_field('release_type', $release_type, $album_id); + } + + /** + * Update an album field. + * @param string $field + * @param int $album_id + * @return boolean + */ + private static function update_field($field, $value, $album_id) + { + $sql = "UPDATE `album` SET `" . $field . "` = ? WHERE `id` = ?"; + return Dba::write($sql, array($value, $album_id)); + } + /** * get_random * * This returns a number of random albums. + * @param int $count + * @param boolean $with_art + * @return int[] */ public static function get_random($count = 1, $with_art = false) { @@ -569,7 +1044,7 @@ class Album extends database_object $count = 1; } - $sql = "SELECT `album`.`id` FROM `album` " . + $sql = "SELECT DISTINCT `album`.`id` FROM `album` " . "LEFT JOIN `song` ON `song`.`album` = `album`.`id` "; if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; diff --git a/sources/lib/class/ampache_rss.class.php b/sources/lib/class/ampache_rss.class.php index 3c00107..2704716 100644 --- a/sources/lib/class/ampache_rss.class.php +++ b/sources/lib/class/ampache_rss.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -27,6 +27,9 @@ */ class Ampache_RSS { + /** + * @var string $type + */ private $type; public $data; @@ -44,33 +47,56 @@ class Ampache_RSS * get_xml * This returns the xmldocument for the current rss type, it calls a sub function that gathers the data * and then uses the xmlDATA class to build the document + * @return string */ - public function get_xml() + public function get_xml($params = null) { - // Function call name - $data_function = 'load_' . $this->type; - $pub_date_function = 'pubdate_' . $this->type; + if ($this->type === "podcast") { + if ($params != null && is_array($params)) { + $object_type = $params['object_type']; + $object_id = $params['object_id']; + if (Core::is_library_item($object_type)) { + $libitem = new $object_type($object_id); + if ($libitem->id) { + $libitem->format(); + return XML_Data::podcast($libitem); + } + } + } + } else { + // Function call name + $data_function = 'load_' . $this->type; + $pub_date_function = 'pubdate_' . $this->type; - $data = call_user_func(array('Ampache_RSS',$data_function)); - $pub_date = call_user_func(array('Ampache_RSS',$pub_date_function)); + $data = call_user_func(array('Ampache_RSS',$data_function)); + $pub_date = null; + if (method_exists('Ampache_RSS', $data_function)) { + $pub_date = call_user_func(array('Ampache_RSS',$pub_date_function)); + } - XML_Data::set_type('rss'); - $xml_document = XML_Data::rss_feed($data,$this->get_title(),$this->get_description(),$pub_date); + XML_Data::set_type('rss'); + $xml_document = XML_Data::rss_feed($data,$this->get_title(),$this->get_description(),$pub_date); - return $xml_document; + return $xml_document; + } + + return null; } // get_xml /** * get_title * This returns the standardized title for the rss feed based on this->type + * @return string */ public function get_title() { $titles = array('now_playing' => T_('Now Playing'), - 'recently_played' => T_('Recently Played'), - 'latest_album' => T_('Newest Albums'), - 'latest_artist' => T_('Newest Artists')); + 'recently_played' => T_('Recently Played'), + 'latest_album' => T_('Newest Albums'), + 'latest_artist' => T_('Newest Artists'), + 'latest_shout' => T_('Newest Shouts') + ); return scrub_out(AmpConfig::get('site_title')) . ' - ' . $titles[$this->type]; @@ -79,6 +105,7 @@ class Ampache_RSS /** * get_description * This returns the standardized description for the rss feed based on this->type + * @return string */ public function get_description() { @@ -90,11 +117,12 @@ class Ampache_RSS /** * validate_type * this returns a valid type for an rss feed, if the specified type is invalid it returns a default value + * @param string $type + * @return string */ public static function validate_type($type) { - $valid_types = array('now_playing','recently_played','latest_album','latest_artist','latest_song', - 'popular_song','popular_album','popular_artist'); + $valid_types = array('now_playing','recently_played','latest_album','latest_artist','latest_shout','podcast'); if (!in_array($type,$valid_types)) { return 'now_playing'; @@ -107,13 +135,28 @@ class Ampache_RSS /** * get_display * This dumps out some html and an icon for the type of rss that we specify + * @param string $type + * @param string $title + * @param array|null $params + * @return string */ - public static function get_display($type='now_playing') + public static function get_display($type='now_playing', $title = '', $params = null) { // Default to now playing $type = self::validate_type($type); - $string = '' . UI::get_icon('feed', T_('RSS Feed')) . ''; + $strparams = ""; + if ($params != null && is_array($params)) { + foreach ($params as $key => $value) { + $strparams .= "&" . scrub_out($key) . "=" . scrub_out($value); + } + } + + $string = '' . UI::get_icon('feed', T_('RSS Feed')); + if (!empty($title)) { + $string .= '  ' . $title; + } + $string .= ''; return $string; @@ -125,6 +168,7 @@ class Ampache_RSS * load_now_playing * This loads in the now playing information. This is just the raw data with key=>value pairs that could be turned * into an xml document if we so wished + * @return array */ public static function load_now_playing() { @@ -152,7 +196,7 @@ class Ampache_RSS 'title' => $title, 'link' => $song->link, 'description' => $description, - 'comments' => $client->fullname . ' - ' . $element['agent'], + 'comments' => $client->f_name . ' - ' . $element['agent'], 'pubDate' => date('r', $element['expire']) ); $results[] = $xml_array; @@ -166,6 +210,7 @@ class Ampache_RSS * pubdate_now_playing * this is the pub date we should use for the now playing information, * this is a little specific as it uses the 'newest' expire we can find + * @return int */ public static function pubdate_now_playing() { @@ -181,6 +226,7 @@ class Ampache_RSS /** * load_recently_played * This loads in the recently played information and formats it up real nice like + * @return array */ public static function load_recently_played() { @@ -237,9 +283,104 @@ class Ampache_RSS } // load_recently_played + /** + * load_latest_album + * This loads in the latest added albums + * @return array + */ + public static function load_latest_album() + { + $ids = Stats::get_newest('album', 10); + + $results = array(); + + foreach ($ids as $id) { + $album = new Album($id); + $album->format(); + + $xml_array = array('title' => $album->f_name, + 'link' => $album->link, + 'description' => $album->f_artist_name . ' - ' . $album->f_name, + 'image' => Art::url($album->id, 'album', null, 2), + 'comments' => '', + 'pubDate' => date("c", $album->get_addtime_first_song()) + ); + $results[] = $xml_array; + + } // end foreach + + return $results; + + } // load_latest_album + + /** + * load_latest_artist + * This loads in the latest added artists + * @return array + */ + public static function load_latest_artist() + { + $ids = Stats::get_newest('artist', 10); + + $results = array(); + + foreach ($ids as $id) { + $artist = new Artist($id); + $artist->format(); + + $xml_array = array('title' => $artist->f_name, + 'link' => $artist->link, + 'description' => $artist->summary, + 'image' => Art::url($artist->id, 'artist', null, 2), + 'comments' => '', + 'pubDate' => '' + ); + $results[] = $xml_array; + + } // end foreach + + return $results; + + } // load_latest_artist + + /** + * load_latest_shout + * This loads in the latest added shouts + * @return array + */ + public static function load_latest_shout() + { + $ids = Shoutbox::get_top(10); + + $results = array(); + + foreach ($ids as $id) { + $shout = new Shoutbox($id); + $shout->format(); + $object = Shoutbox::get_object($shout->object_type, $shout->object_id); + $object->format(); + $user = new User($shout->user); + $user->format(); + + $xml_array = array('title' => $user->username . ' ' . T_('on') . ' ' . $object->get_fullname(), + 'link' => $object->link, + 'description' => $shout->text, + 'image' => Art::url($shout->object_id, $shout->object_type, null, 2), + 'comments' => '', + 'pubDate' => date("c", $shout->date) + ); + $results[] = $xml_array; + + } // end foreach + + return $results; + + } // load_latest_shout + /** * pubdate_recently_played * This just returns the 'newest' recently played entry + * @return int */ public static function pubdate_recently_played() { diff --git a/sources/lib/class/ampconfig.class.php b/sources/lib/class/ampconfig.class.php index 665cfaf..ccb5177 100644 --- a/sources/lib/class/ampconfig.class.php +++ b/sources/lib/class/ampconfig.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -35,6 +35,9 @@ */ class AmpConfig { + /** + * @var array $_global + */ private static $_global = array(); public function __construct() @@ -46,6 +49,7 @@ class AmpConfig * get * * This returns a config value. + * @param string $name */ public static function get($name) { @@ -60,6 +64,7 @@ class AmpConfig * get_all * * This returns all of the current config variables as an array. + * @return array */ public static function get_all() { @@ -70,6 +75,8 @@ class AmpConfig * set * * This sets config values. + * @param string $name + * @param boolean $clobber */ public static function set($name, $value, $clobber = false) { @@ -87,6 +94,8 @@ class AmpConfig * * This is the same as the set function except it takes an array as * input. + * @param array $array + * @param boolean $clobber */ public static function set_by_array($array, $clobber = false) { diff --git a/sources/lib/class/api.class.php b/sources/lib/class/api.class.php index 6e22ca5..e7cd5b6 100644 --- a/sources/lib/class/api.class.php +++ b/sources/lib/class/api.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,9 +30,18 @@ */ class Api { + /** + * @var string $auth_version + */ public static $auth_version = '350001'; - public static $version = '370001'; + /** + * @var string $version + */ + public static $version = '380001'; + /** + * @var Browse $browse + */ private static $browse = null; /** @@ -62,6 +71,9 @@ class Api * the filters in a slightly different and vastly simpler way to the * end users--so we have to do a little extra work to make them work * internally. + * @param string $filter + * @param int|string|boolean|null $value + * @return boolean */ public static function set_filter($filter,$value) { @@ -108,6 +120,8 @@ class Api * * This is the function that handles verifying a new handshake * Takes a timestamp, auth key, and username. + * @param array + * @return boolean */ public static function handshake($input) { @@ -155,6 +169,7 @@ class Api ($timestamp > (time() + 1800))) { debug_event('API', 'Login Failed: timestamp out of range ' . $timestamp . '/' . time(), 1); Error::add('api', T_('Login Failed: timestamp out of range')); + echo XML_Data::error('401', T_('Error Invalid Handshake - ') . T_('Login Failed: timestamp out of range')); return false; } @@ -166,6 +181,7 @@ class Api if (!$realpwd) { debug_event('API', 'Unable to find user with userid of ' . $user_id, 1); Error::add('api', T_('Invalid Username/Password')); + echo XML_Data::error('401', T_('Error Invalid Handshake - ') . T_('Invalid Username/Password')); return false; } @@ -184,6 +200,18 @@ class Api $data['username'] = $client->username; $data['type'] = 'api'; $data['value'] = $timestamp; + if (isset($input['client'])) { + $data['agent'] = $input['client']; + } + if (isset($input['geo_latitude'])) { + $data['geo_latitude'] = $input['geo_latitude']; + } + if (isset($input['geo_longitude'])) { + $data['geo_longitude'] = $input['geo_longitude']; + } + if (isset($input['geo_name'])) { + $data['geo_name'] = $input['geo_name']; + } $token = Session::create($data); debug_event('API', 'Login Success, passphrase matched', 1); @@ -233,14 +261,16 @@ class Api } // end while debug_event('API','Login Failed, unable to match passphrase','1'); - XML_Data::error('401', T_('Error Invalid Handshake - ') . T_('Invalid Username/Password')); + echo XML_Data::error('401', T_('Error Invalid Handshake - ') . T_('Invalid Username/Password')); + return false; } // handshake /** * ping * This can be called without being authenticated, it is useful for determining if what the status * of the server is, and what version it is running/compatible with + * @param array $input */ public static function ping($input) { @@ -264,6 +294,7 @@ class Api * This takes a collection of inputs and returns * artist objects. This function is deprecated! * //DEPRECATED + * @param array $input */ public static function artists($input) { @@ -291,6 +322,7 @@ class Api * artist * This returns a single artist based on the UID of said artist * //DEPRECATED + * @param array $input */ public static function artist($input) { @@ -302,6 +334,7 @@ class Api /** * artist_albums * This returns the albums of an artist + * @param array $input */ public static function artist_albums($input) { @@ -320,6 +353,7 @@ class Api /** * artist_songs * This returns the songs of the specified artist + * @param array $input */ public static function artist_songs($input) { @@ -337,6 +371,7 @@ class Api /** * albums * This returns albums based on the provided search filters + * @param array $input */ public static function albums($input) { @@ -361,6 +396,7 @@ class Api /** * album * This returns a single album based on the UID provided + * @param array $input */ public static function album($input) { @@ -372,6 +408,7 @@ class Api /** * album_songs * This returns the songs of a specified album + * @param array $input */ public static function album_songs($input) { @@ -390,6 +427,7 @@ class Api /** * tags * This returns the tags based on the specified filter + * @param array $input */ public static function tags($input) { @@ -413,6 +451,7 @@ class Api /** * tag * This returns a single tag based on UID + * @param array $input */ public static function tag($input) { @@ -425,38 +464,43 @@ class Api /** * tag_artists * This returns the artists associated with the tag in question as defined by the UID + * @param array $input */ public static function tag_artists($input) { $artists = Tag::get_tag_objects('artist',$input['filter']); + if ($artists) { + XML_Data::set_offset($input['offset']); + XML_Data::set_limit($input['limit']); - XML_Data::set_offset($input['offset']); - XML_Data::set_limit($input['limit']); - - ob_end_clean(); - echo XML_Data::artists($artists); + ob_end_clean(); + echo XML_Data::artists($artists); + } } // tag_artists /** * tag_albums * This returns the albums associated with the tag in question + * @param array $input */ public static function tag_albums($input) { $albums = Tag::get_tag_objects('album',$input['filter']); + if ($albums) { + XML_Data::set_offset($input['offset']); + XML_Data::set_limit($input['limit']); - XML_Data::set_offset($input['offset']); - XML_Data::set_limit($input['limit']); - - ob_end_clean(); - echo XML_Data::albums($albums); + ob_end_clean(); + echo XML_Data::albums($albums); + } } // tag_albums /** * tag_songs * returns the songs for this tag + * @param array $input */ public static function tag_songs($input) { @@ -473,6 +517,7 @@ class Api /** * songs * Returns songs based on the specified filter + * @param array $input */ public static function songs($input) { @@ -499,6 +544,7 @@ class Api /** * song * returns a single song + * @param array $input */ public static function song($input) { @@ -513,6 +559,7 @@ class Api * url_to_song * * This takes a url and returns the song object in question + * @param array $input */ public static function url_to_song($input) { @@ -525,6 +572,7 @@ class Api /** * playlists * This returns playlists based on the specified filter + * @param array $input */ public static function playlists($input) { @@ -548,6 +596,7 @@ class Api /** * playlist * This returns a single playlist + * @param array $input */ public static function playlist($input) { @@ -561,6 +610,7 @@ class Api /** * playlist_songs * This returns the songs for a playlist + * @param array $input */ public static function playlist_songs($input) { @@ -577,13 +627,14 @@ class Api XML_Data::set_offset($input['offset']); XML_Data::set_limit($input['limit']); ob_end_clean(); - echo XML_Data::songs($songs); + echo XML_Data::songs($songs,$items); } // playlist_songs /** * playlist_create * This create a new playlist and return it + * @param array $input */ public static function playlist_create($input) { @@ -600,6 +651,7 @@ class Api /** * playlist_delete * This delete a playlist + * @param array $input */ public static function playlist_delete($input) { @@ -616,12 +668,13 @@ class Api /** * playlist_add_song * This add a song to a playlist + * @param array $input */ public static function playlist_add_song($input) { ob_end_clean(); $playlist = new Playlist($input['filter']); - $song = new Playlist($input['song']); + $song = $input['song']; if (!$playlist->has_access()) { echo XML_Data::error('401', T_('Access denied to this playlist.')); } else { @@ -634,12 +687,13 @@ class Api /** * playlist_remove_song * This remove a song from a playlist + * @param array $input */ public static function playlist_remove_song($input) { ob_end_clean(); $playlist = new Playlist($input['filter']); - $track = new Playlist($input['track']); + $track = scrub_in($input['track']); if (!$playlist->has_access()) { echo XML_Data::error('401', T_('Access denied to this playlist.')); } else { @@ -652,6 +706,7 @@ class Api /** * search_songs * This searches the songs and returns... songs + * @param array $input */ public static function search_songs($input) { @@ -675,6 +730,7 @@ class Api /** * videos * This returns video objects! + * @param array $input */ public static function videos($input) { @@ -697,6 +753,7 @@ class Api /** * video * This returns a single video + * @param array $input */ public static function video($input) { @@ -710,6 +767,7 @@ class Api /** * localplay * This is for controling localplay + * @param array $input */ public static function localplay($input) { @@ -737,6 +795,7 @@ class Api /** * democratic * This is for controlling democratic play + * @param array $input */ public static function democratic($input) { @@ -781,7 +840,7 @@ class Api $objects = $democratic->get_items(); Song::build_cache($democratic->object_ids); Democratic::build_vote_cache($democratic->vote_ids); - XML_Data::democratic($objects); + echo XML_Data::democratic($objects); break; case 'play': $url = $democratic->play_url(); @@ -795,12 +854,18 @@ class Api } // democratic + /** + * This get library stats. + * @param array $input + */ public static function stats($input) { $type = $input['type']; $offset = $input['offset']; $limit = $input['limit']; + $username = $input['username']; + $albums = null; if ($type == "newest") { $albums = Stats::get_newest("album", $limit, $offset); } else if ($type == "highest") { @@ -808,7 +873,16 @@ class Api } else if ($type == "frequent") { $albums = Stats::get_top("album", $limit, '', $offset); } else if ($type == "recent") { - $albums = Stats::get_recent("album", $limit, $offset); + if (!empty($username)) { + $user = User::get_from_username($username); + if ($user !== null) { + $albums = $user->get_recently_played($limit, 'album'); + } else { + debug_event('api', 'User `' . $username . '` cannot be found.', 1); + } + } else { + $albums = Stats::get_recent("album", $limit, $offset); + } } else if ($type == "flagged") { $albums = Userflag::get_latest('album'); } else { @@ -818,8 +892,109 @@ class Api $albums = Album::get_random($limit); } - ob_end_clean(); - echo XML_Data::albums($albums); - } + if ($albums !== null) { + ob_end_clean(); + echo XML_Data::albums($albums); + } + } // stats + + /** + * user + * This get an user public information + * @param array $input + */ + public static function user($input) + { + $username = $input['username']; + if (!empty($username)) { + $user = User::get_from_username($username); + if ($user !== null) { + ob_end_clean(); + echo XML_Data::user($user); + } else { + debug_event('api', 'User `' . $username . '` cannot be found.', 1); + } + } else { + debug_event('api', 'Username required on user function call.', 1); + } + } // user + + /** + * followers + * This get an user followers + * @param array $input + */ + public static function followers($input) + { + if (AmpConfig::get('sociable')) { + $username = $input['username']; + if (!empty($username)) { + $user = User::get_from_username($username); + if ($user !== null) { + $users = $user->get_followers(); + ob_end_clean(); + echo XML_Data::users($user); + } else { + debug_event('api', 'User `' . $username . '` cannot be found.', 1); + } + } else { + debug_event('api', 'Username required on followers function call.', 1); + } + } else { + debug_event('api', 'Sociable feature is not enabled.', 3); + } + } // followers + + /** + * following + * This get the user list followed by an user + * @param array $input + */ + public static function following($input) + { + if (AmpConfig::get('sociable')) { + $username = $input['username']; + if (!empty($username)) { + $user = User::get_from_username($username); + if ($user !== null) { + $users = $user->get_following(); + ob_end_clean(); + echo XML_Data::users($user); + } else { + debug_event('api', 'User `' . $username . '` cannot be found.', 1); + } + } else { + debug_event('api', 'Username required on following function call.', 1); + } + } else { + debug_event('api', 'Sociable feature is not enabled.', 3); + } + } // following + + /** + * last_shouts + * This get the latest posted shouts + * @param array $input + */ + public static function last_shouts($input) + { + $limit = intval($input['limit']); + if ($limit < 1) { + $limit = AmpConfig::get('popular_threshold'); + } + if (AmpConfig::get('sociable')) { + $username = $input['username']; + if (!empty($username)) { + $shouts = Shoutbox::get_top($limit, $username); + } else { + $shouts = Shoutbox::get_top($limit); + } + + ob_end_clean(); + echo XML_Data::shouts($shouts); + } else { + debug_event('api', 'Sociable feature is not enabled.', 3); + } + } // last_shouts } // API class diff --git a/sources/lib/class/art.class.php b/sources/lib/class/art.class.php index 41c371f..afe2765 100644 --- a/sources/lib/class/art.class.php +++ b/sources/lib/class/art.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -32,25 +32,60 @@ use MusicBrainz\Clients\RequestsMbClient; */ class Art extends database_object { + /** + * @var int $id + */ + public $id; + /** + * @var string $type + */ public $type; + /** + * @var int $uid + */ public $uid; // UID of the object not ID because it's not the ART.ID + /** + * @var string $raw + */ public $raw; // Raw art data + /** + * @var string $raw_mime + */ public $raw_mime; + /** + * @var string $kind + */ + public $kind; + /** + * @var string $thumb + */ public $thumb; + /** + * @var string $thumb_mime + */ public $thumb_mime; + /** + * @var bool $enabled + */ private static $enabled; /** * Constructor * Art constructor, takes the UID of the object and the * object type. + * @param int $uid + * @param string $type + * @param string $kind */ - public function __construct($uid, $type = 'album') + public function __construct($uid, $type = 'album', $kind = 'default') { - $this->type = Art::validate_type($type); - $this->uid = $uid; + if (!Core::is_library_item($type)) + return false; + $this->type = $type; + $this->uid = intval($uid); + $this->kind = $kind; } // constructor @@ -59,6 +94,8 @@ class Art extends database_object * This attempts to reduce # of queries by asking for everything in the * browse all at once and storing it in the cache, this can help if the * db connection is the slow point + * @param int[] $object_ids + * @return bool */ public static function build_cache($object_ids) { @@ -97,6 +134,7 @@ class Art extends database_object /** * is_enabled * Checks whether the user currently wants art + * @return boolean */ public static function is_enabled() { @@ -110,6 +148,7 @@ class Art extends database_object /** * set_enabled * Changes the value of enabled + * @param bool|null $value */ public static function set_enabled($value = null) { @@ -123,27 +162,11 @@ class Art extends database_object //setcookie('art_enabled', self::$enabled, time() + 31536000, "/"); } - /** - * validate_type - * This validates the type - */ - public static function validate_type($type) - { - switch ($type) { - case 'album': - case 'artist': - case 'video': - case 'user': - return $type; - default: - return 'album'; - } - - } // validate_type - /** * extension * This returns the file extension for the currently loaded art + * @param string $mime + * @return string */ public static function extension($mime) { @@ -159,6 +182,8 @@ class Art extends database_object /** * test_image * Runs some sanity checks on the putative image + * @param string $source + * @return boolean */ public static function test_image($source) { @@ -167,17 +192,25 @@ class Art extends database_object return false; } + // Check image size doesn't exceed the limit + if (strlen($source) > AmpConfig::get('max_upload_size')) { + debug_event('Art', 'Image size (' . strlen($source) . ') exceed the limit (' . AmpConfig::get('max_upload_size') . ').', 1); + return false; + } + + $test = true; // Check to make sure PHP:GD exists. If so, we can sanity check // the image. if (function_exists('ImageCreateFromString')) { $image = ImageCreateFromString($source); if (!$image || imagesx($image) < 5 || imagesy($image) < 5) { debug_event('Art', 'Image failed PHP-GD test',1); - return false; + $test = false; } + @imagedestroy($image); } - return true; + return $test; } //test_image /** @@ -186,6 +219,8 @@ class Art extends database_object * look in the database and will return the thumb if it * exists, if it doesn't depending on settings it will try * to create it. + * @param boolean $raw + * @return string */ public function get($raw=false) { @@ -208,38 +243,45 @@ class Art extends database_object * This pulls the information out from the database, depending * on if we want to resize and if there is not a thumbnail go * ahead and try to resize + * @return boolean */ public function get_db() { - $type = Dba::escape($this->type); - $id = Dba::escape($this->uid); - - $sql = "SELECT `image`, `mime`, `size` FROM `image` WHERE `object_type`='$type' AND `object_id`='$id'"; - $db_results = Dba::read($sql); + $sql = "SELECT `id`, `image`, `mime`, `size` FROM `image` WHERE `object_type` = ? AND `object_id` = ? AND `kind` = ?"; + $db_results = Dba::read($sql, array($this->type, $this->uid, $this->kind)); while ($results = Dba::fetch_assoc($db_results)) { if ($results['size'] == 'original') { - $this->raw = $results['image']; + if (AmpConfig::get('album_art_store_disk')) { + $this->raw = self::read_from_dir($results['size'], $this->type, $this->uid, $this->kind); + } else { + $this->raw = $results['image']; + } $this->raw_mime = $results['mime']; - } else if (AmpConfig::get('resize_images') && - $results['size'] == '275x275') { - $this->thumb = $results['image']; + } else if (AmpConfig::get('resize_images') && $results['size'] == '275x275') { + if (AmpConfig::get('album_art_store_disk')) { + $this->thumb = self::read_from_dir($results['size'], $this->type, $this->uid, $this->kind); + } else { + $this->thumb = $results['image']; + } $this->raw_mime = $results['mime']; } + $this->id = $results['id']; } // If we get nothing return false if (!$this->raw) { return false; } // If there is no thumb and we want thumbs if (!$this->thumb && AmpConfig::get('resize_images')) { - $data = $this->generate_thumb($this->raw, array('width' => 275, 'height' => 275), $this->raw_mime); + $size = array('width' => 275, 'height' => 275); + $data = $this->generate_thumb($this->raw, $size, $this->raw_mime); // If it works save it! if ($data) { - $this->save_thumb($data['thumb'], $data['thumb_mime'], '275x275'); + $this->save_thumb($data['thumb'], $data['thumb_mime'], $size); $this->thumb = $data['thumb']; $this->thumb_mime = $data['thumb_mime']; } else { - debug_event('Art','Unable to retrieve or generate thumbnail for ' . $type . '::' . $id,1); + debug_event('Art','Unable to retrieve or generate thumbnail for ' . $this->type . '::' . $this->id,1); } } // if no thumb, but art and we want to resize @@ -247,12 +289,60 @@ class Art extends database_object } // get_db + /** + * This check if an object has an associated image in db. + * @param int $object_id + * @param string $object_type + * @param string $kind + * @return boolean + */ + public static function has_db($object_id, $object_type, $kind = 'default') + { + $sql = "SELECT COUNT(`id`) AS `nb_img` FROM `image` WHERE `object_type` = ? AND `object_id` = ? AND `kind` = ?"; + $db_results = Dba::read($sql, array($object_type, $object_id, $kind)); + $nb_img = 0; + if ($results = Dba::fetch_assoc($db_results)) { + $nb_img = $results['nb_img']; + } + + return ($nb_img > 0); + } + + /** + * This insert art from url. + * @param string $url + */ + public function insert_url($url) + { + debug_event('art', 'Insert art from url ' . $url, '5'); + $image = Art::get_from_source(array('url' => $url), $this->type); + $rurl = pathinfo($url); + $mime = "image/" . $rurl['extension']; + $this->insert($image, $mime); + } + + /** + * This insert art from file on disk. + * @param string $filepath + */ + public function insert_from_file($filepath) + { + debug_event('art', 'Insert art from file on disk ' . $filepath, '5'); + $image = Art::get_from_source(array('file' => $filepath), $this->type); + $rfile = pathinfo($filepath); + $mime = "image/" . $rfile['extension']; + $this->insert($image, $mime); + } + /** * insert * This takes the string representation of an image and inserts it into * the database. You must also pass the mime type. + * @param string $source + * @param string $mime + * @return boolean */ - public function insert($source, $mime) + public function insert($source, $mime = '') { // Disabled in demo mode cause people suck and upload porn if (AmpConfig::get('demo_mode')) { return false; } @@ -265,36 +355,199 @@ class Art extends database_object // Default to image/jpeg if they don't pass anything $mime = $mime ? $mime : 'image/jpeg'; - - $image = Dba::escape($source); - $mime = Dba::escape($mime); - $uid = Dba::escape($this->uid); - $type = Dba::escape($this->type); - // Blow it away! $this->reset(); + if (AmpConfig::get('write_id3_art')) { + if ($this->type == 'album') { + $album = new Album($this->uid ); + debug_event('Art', 'Inserting image Album ' . $album->name . ' on songs.', 5); + $songs = $album->get_songs(); + foreach ($songs as $song_id) { + $song = new Song($song_id); + $song->format(); + $id3 = new vainfo($song->file); + $data = $id3->read_id3(); + if (isset($data['tags']['id3v2'])) { + $image_from_tag = ''; + if (isset($data['id3v2']['APIC'][0]['data'])) { + $image_from_tag = $data['id3v2']['APIC'][0]['data']; + } + if ($image_from_tag != $source) { + $ndata = array(); + $ndata['APIC']['data'] = $source; + $ndata['APIC']['mime'] = $mime; + $ndata = array_merge($ndata, $song->get_metadata()); + $id3->write_id3($ndata); + } + } + } + } + } + + $dimensions = Core::image_dimensions($source); + $width = intval($dimensions['width']); + $height = intval($dimensions['height']); + $sizetext = 'original'; + + if (!self::check_dimensions($dimensions)) { + return false; + } + + if (AmpConfig::get('album_art_store_disk')) { + self::write_to_dir($source, $sizetext, $this->type, $this->uid, $this->kind); + $source = null; + } + // Insert it! - $sql = "INSERT INTO `image` (`image`, `mime`, `size`, `object_type`, `object_id`) VALUES('$image', '$mime', 'original', '$type', '$uid')"; - Dba::write($sql); + $sql = "INSERT INTO `image` (`image`, `mime`, `size`, `width`, `height`, `object_type`, `object_id`, `kind`) VALUES(?, ?, ?, ?, ?, ?, ?, ?)"; + Dba::write($sql, array($source, $mime, $sizetext, $width, $height, $this->type, $this->uid, $this->kind)); return true; } // insert + public static function check_dimensions($dimensions) + { + $w = intval($dimensions['width']); + $h = intval($dimensions['height']); + + if ($w > 0 && $h > 0) { + $minw = AmpConfig::get('album_art_min_width'); + $maxw = AmpConfig::get('album_art_max_width'); + $minh = AmpConfig::get('album_art_min_height'); + $maxh = AmpConfig::get('album_art_max_height'); + + if ($minw > 0 && ($w < $minw || $w > $maxw)) { + debug_event('Art', 'Image width not in range.', 1); + return false; + } + + if ($minh > 0 && ($h < $minh || $h > $maxh)) { + debug_event('Art', 'Image height not in range.', 1); + return false; + } + } + + return true; + } + + private static function get_dir_on_disk($type, $uid, $kind = '', $autocreate = false) + { + $path = AmpConfig::get('local_metadata_dir'); + if (!$path) { + debug_event('Art', 'local_metadata_dir setting is required to store arts on disk.', 1); + return false; + } + + // Correctly detect the slash we need to use here + if (strpos($path, '/') !== false) { + $slash_type = '/'; + } else { + $slash_type = '\\'; + } + + $path .= $slash_type . $type; + if ($autocreate && !Core::is_readable($path)) { + mkdir($path); + } + + $path .= $slash_type . $uid; + if ($autocreate && !Core::is_readable($path)) { + mkdir($path); + } + + if (!empty($kind)) { + $path .= $slash_type . $kind; + if ($autocreate && !Core::is_readable($path)) { + mkdir($path); + } + } + $path .= $slash_type; + + return $path; + } + + private static function write_to_dir($source, $sizetext, $type, $uid, $kind) + { + $path = self::get_dir_on_disk($type, $uid, $kind, true); + if ($path === false) { + return false; + } + $path .= "art-" . $sizetext . ".jpg"; + if (Core::is_readable($path)) { + unlink($path); + } + $fp = fopen($path, "wb"); + fwrite($fp, $source); + fclose ($fp); + + return true; + } + + private static function read_from_dir($sizetext, $type, $uid, $kind) + { + $path = self::get_dir_on_disk($type, $uid, $kind); + if ($path === false) { + return null; + } + $path .= "art-" . $sizetext . ".jpg"; + if (!Core::is_readable($path)) { + debug_event('Art', 'Local image art ' . $path . ' cannot be read.', 1); + return null; + } + + $image = ''; + $fp = fopen($path, "rb"); + do { + $image .= fread($fp, 2048); + } while (!feof($fp)); + fclose($fp); + + return $image; + } + + private static function delete_from_dir($type, $uid, $kind = '') + { + if ($type && $uid) { + $path = self::get_dir_on_disk($type, $uid, $kind); + self::delete_rec_dir($path); + } + } + + private static function delete_rec_dir($path) + { + debug_event('Art', 'Deleting ' . $path . ' directory...', 5); + + if (Core::is_readable($path)) { + foreach (scandir($path) as $file) { + if ('.' === $file || '..' === $file) continue; + elseif (is_dir($path . '/' . $file)) self::delete_rec_dir($path . '/' . $file); + else unlink($path . '/' . $file); + } + rmdir($path); + } + } + /** * reset * This resets the art in the database */ public function reset() { - $sql = "DELETE FROM `image` WHERE `object_id` = ? AND `object_type` = ?"; - Dba::write($sql, array($this->uid, $this->type)); + if (AmpConfig::get('album_art_store_disk')) { + self::delete_from_dir($this->type, $this->uid, $this->kind); + } + $sql = "DELETE FROM `image` WHERE `object_id` = ? AND `object_type` = ? AND `kind` = ?"; + Dba::write($sql, array($this->uid, $this->type, $this->kind)); } // reset /** * save_thumb * This saves the thumbnail that we're passed + * @param string $source + * @param string $mime + * @param array $size */ public function save_thumb($source, $mime, $size) { @@ -304,44 +557,45 @@ class Art extends database_object return false; } - $source = Dba::escape($source); - $mime = Dba::escape($mime); - $size = Dba::escape($size); - $uid = Dba::escape($this->uid); - $type = Dba::escape($this->type); + $width = intval($size['width']); + $height = intval($size['height']); + $sizetext = $width . 'x' . $height; - $sql = "DELETE FROM `image` WHERE `object_id`='$uid' AND `object_type`='$type' AND `size`='$size'"; - Dba::write($sql); + $sql = "DELETE FROM `image` WHERE `object_id` = ? AND `object_type` = ? AND `size` = ? AND `kind` = ?"; + Dba::write($sql, array($this->uid, $this->type, $sizetext, $this->kind)); - $sql = "INSERT INTO `image` (`image`, `mime`, `size`, `object_type`, `object_id`) VALUES('$source', '$mime', '$size', '$type', '$uid')"; - Dba::write($sql); + if (AmpConfig::get('album_art_store_disk')) { + self::write_to_dir($source, $sizetext, $this->type, $this->uid, $this->kind); + $source = null; + } + $sql = "INSERT INTO `image` (`image`, `mime`, `size`, `width`, `height`, `object_type`, `object_id`, `kind`) VALUES(?, ?, ?, ?, ?, ?, ?, ?)"; + Dba::write($sql, array($source, $mime, $sizetext, $width, $height, $this->type, $this->uid, $this->kind)); } // save_thumb /** * get_thumb * Returns the specified resized image. If the requested size doesn't * already exist, create and cache it. + * @param array $size + * @return string */ public function get_thumb($size) { $sizetext = $size['width'] . 'x' . $size['height']; - $sizetext = Dba::escape($sizetext); - $type = Dba::escape($this->type); - $uid = Dba::escape($this->uid); - - $sql = "SELECT `image`, `mime` FROM `image` WHERE `size`='$sizetext' AND `object_type`='$type' AND `object_id`='$uid'"; - $db_results = Dba::read($sql); + $sql = "SELECT `image`, `mime` FROM `image` WHERE `size` = ? AND `object_type` = ? AND `object_id` = ? AND `kind` = ?"; + $db_results = Dba::read($sql, array($sizetext, $this->type, $this->uid, $this->kind)); $results = Dba::fetch_assoc($db_results); if (count($results)) { - return array('thumb' => $results['image'], + return array( + 'thumb' => (AmpConfig::get('album_art_store_disk')) ? self::read_from_dir($sizetext, $this->type, $this->uid, $this->kind) : $results['image'], 'thumb_mime' => $results['mime']); } // If we didn't get a result $results = $this->generate_thumb($this->raw, $size, $this->raw_mime); if ($results) { - $this->save_thumb($results['thumb'], $results['thumb_mime'], $sizetext); + $this->save_thumb($results['thumb'], $results['thumb_mime'], $size); } return $results; @@ -353,8 +607,12 @@ class Art extends database_object * Automatically resizes the image for thumbnail viewing. * Only works on gif/jpg/png/bmp. Fails if PHP-GD isn't available * or lacks support for the requested image type. + * @param string $image + * @param array $size + * @param string $mime + * @return string */ - public function generate_thumb($image,$size,$mime) + public function generate_thumb($image, $size, $mime) { $data = explode("/",$mime); $type = strtolower($data['1']); @@ -401,8 +659,11 @@ class Art extends database_object if (!imagecopyresampled($thumbnail, $source, 0, 0, 0, 0, $size['width'], $size['height'], $source_size['width'], $source_size['height'])) { debug_event('Art','Unable to create resized image',1); + imagedestroy($source); + imagedestroy($thumbnail); return false; } + imagedestroy($source); // Start output buffer ob_start(); @@ -434,6 +695,7 @@ class Art extends database_object $data = ob_get_contents(); ob_end_clean(); + imagedestroy($thumbnail); if (!strlen($data)) { debug_event('Art', 'Unknown Error resizing art', 1); return false; @@ -451,6 +713,9 @@ class Art extends database_object * ['url'] = URL *** OPTIONAL *** * ['file'] = FILENAME *** OPTIONAL *** * ['raw'] = Actual Image data, already captured + * @param array $data + * @param string $type + * @return string|null */ public static function get_from_source($data, $type = 'album') { @@ -461,12 +726,8 @@ class Art extends database_object // If it came from the database if (isset($data['db'])) { - // Repull it - $uid = Dba::escape($data['db']); - $type = Dba::escape($type); - - $sql = "SELECT * FROM `image` WHERE `object_type`='$type' AND `object_id`='$uid' AND `size`='original'"; - $db_results = Dba::read($sql); + $sql = "SELECT * FROM `image` WHERE `object_type` = ? AND `object_id` =? AND `size`='original'"; + $db_results = Dba::read($sql, array($type, $data['db'])); $row = Dba::fetch_assoc($db_results); return $row['art']; } // came from the db @@ -474,23 +735,22 @@ class Art extends database_object // Check to see if it's a URL if (isset($data['url'])) { $options = array(); - if (AmpConfig::get('proxy_host') AND AmpConfig::get('proxy_port')) { - $proxy = array(); - $proxy[] = AmpConfig::get('proxy_host') . ':' . AmpConfig::get('proxy_port'); - if (AmpConfig::get('proxy_user')) { - $proxy[] = AmpConfig::get('proxy_user'); - $proxy[] = AmpConfig::get('proxy_pass'); - } - $options['proxy'] = $proxy; + try { + $options['timeout'] = 3; + $request = Requests::get($data['url'], array(), Core::requests_options($options)); + $raw = $request->body; + } catch (Exception $e) { + debug_event('Art', 'Error getting art: ' . $e->getMessage(), '1'); + $raw = null; } - $request = Requests::get($data['url'], array(), $options); - return $request->body; + + return $raw; } // Check to see if it's a FILE if (isset($data['file'])) { $handle = fopen($data['file'],'rb'); - $image_data = fread($handle,filesize($data['file'])); + $image_data = fread($handle,Core::get_filesize($data['file'])); fclose($handle); return $image_data; } @@ -511,18 +771,32 @@ class Art extends database_object } } // if data song - return false; + return null; } // get_from_source /** * url * This returns the constructed URL for the art in question + * @param int $uid + * @param string $type + * @param string $sid + * @param int|null $thumb + * @return string */ - public static function url($uid,$type,$sid=false) + public static function url($uid,$type,$sid=null,$thumb=null) { - $sid = $sid ? scrub_out($sid) : scrub_out(session_id()); - $type = self::validate_type($type); + if (!Core::is_library_item($type)) + return null; + + if (AmpConfig::get('use_auth') && AmpConfig::get('require_session')) { + $sid = $sid ? scrub_out($sid) : scrub_out(session_id()); + if ($sid == null) { + $sid = Session::create(array( + 'type' => 'api' + )); + } + } $key = $type . $uid; @@ -551,8 +825,25 @@ class Art extends database_object $mime = isset($thumb_mime) ? $thumb_mime : (isset($mime) ? $mime : null); $extension = self::extension($mime); - $name = 'art.' . $extension; - $url = AmpConfig::get('web_path') . '/image.php?id=' . scrub_out($uid) . '&object_type=' . scrub_out($type) . '&auth=' . $sid . '&name=' . $name; + if (AmpConfig::get('stream_beautiful_url')) { + if (empty($extension)) { + $extension = 'jpg'; + } + $url = AmpConfig::get('web_path') . '/play/art/' . $sid . '/' . scrub_out($type) . '/' . scrub_out($uid) . '/thumb'; + if ($thumb) { + $url .= $thumb; + } + $url .= '.' . $extension; + } else { + $url = AmpConfig::get('web_path') . '/image.php?object_id=' . scrub_out($uid) . '&object_type=' . scrub_out($type) . '&auth=' . $sid; + if ($thumb) { + $url .= '&thumb=' . $thumb; + } + if (!empty($extension)) { + $name = 'art.' . $extension; + $url .= '&name=' . $name; + } + } return $url; @@ -562,38 +853,99 @@ class Art extends database_object * gc * This cleans up art that no longer has a corresponding object */ - public static function gc() + public static function gc($object_type = null, $object_id = null) { - // iterate over our types and delete the images - foreach (array('album', 'artist') as $type) { - $sql = "DELETE FROM `image` USING `image` LEFT JOIN `" . - $type . "` ON `" . $type . "`.`id`=" . - "`image`.`object_id` WHERE `object_type`='" . - $type . "' AND `" . $type . "`.`id` IS NULL"; - Dba::write($sql); - } // foreach + $types = array('album', 'artist','tvshow','tvshow_season','video','user'); + + if ($object_type != null) { + if (in_array($object_type, $types)) { + if (AmpConfig::get('album_art_store_disk')) { + self::delete_from_dir($object_type, $object_id); + } + $sql = "DELETE FROM `image` WHERE `object_type` = ? AND `object_id` = ?"; + Dba::write($sql, array($object_type, $object_id)); + } else { + debug_event('art', 'Garbage collect on type `' . $object_type . '` is not supported.', 1); + } + } else { + // iterate over our types and delete the images + foreach ($types as $type) { + if (AmpConfig::get('album_art_store_disk')) { + $sql = "SELECT `image`.`object_id`, `image`.`object_type` FROM `image` LEFT JOIN `" . + $type . "` ON `" . $type . "`.`id`=" . + "`image`.`object_id` WHERE `object_type`='" . + $type . "' AND `" . $type . "`.`id` IS NULL"; + $db_results = Dba::read($sql); + while ($row = Dba::fetch_row($db_results)) { + self::delete_from_dir($row[1], $row[0]); + } + } + $sql = "DELETE FROM `image` USING `image` LEFT JOIN `" . + $type . "` ON `" . $type . "`.`id`=" . + "`image`.`object_id` WHERE `object_type`='" . + $type . "' AND `" . $type . "`.`id` IS NULL"; + Dba::write($sql); + } // foreach + } + } + + /** + * Migrate an object associate images to a new object + * @param string $object_type + * @param int $old_object_id + * @param int $new_object_id + * @return boolean + */ + public static function migrate($object_type, $old_object_id, $new_object_id) + { + $sql = "UPDATE `image` SET `object_id` = ? WHERE `object_type` = ? AND `object_id` = ?"; + return Dba::write($sql, array($new_object_id, $object_type, $old_object_id)); + } + + /** + * Duplicate an object associate images to a new object + * @param string $object_type + * @param int $old_object_id + * @param int $new_object_id + * @return boolean + */ + public static function duplicate($object_type, $old_object_id, $new_object_id) + { + if (AmpConfig::get('album_art_store_disk')) { + $sql = "SELECT `size`, `kind` FROM `image` WHERE `object_type` = ? AND `object_id` = ?"; + $db_results = Dba::read($sql, array($object_type, $old_object_id)); + while ($row = Dba::fetch_assoc($db_results)) { + $image = self::read_from_dir($row['size'], $object_type, $old_object_id, $row['kind']); + if ($image != null) { + self::write_to_dir($image, $row['size'], $object_type, $new_object_id, $row['kind']); + } + } + } + + $sql = "INSERT INTO `image` (`image`, `mime`, `size`, `object_type`, `object_id`, `kind`) SELECT `image`, `mime`, `size`, `object_type`, ? as `object_id`, `kind` FROM `image` WHERE `object_type` = ? AND `object_id` = ?"; + return Dba::write($sql, array($new_object_id, $object_type, $old_object_id)); } /** * gather * This tries to get the art in question + * @param array $options + * @param int $limit + * @return array */ - public function gather($options = array(), $limit = false) + public function gather($options = array(), $limit = 0) { // Define vars $results = array(); - - switch ($this->type) { - case 'album': - $allowed_methods = array('db','lastfm','folder','amazon','google','musicbrainz','tags'); - break; - case 'artist': - case 'video': - default: - $allowed_methods = array(); - break; + $type = $this->type; + if (isset($options['type'])) { + $type = $options['type']; } + if (count($options) == 0) { + debug_event('Art', 'No options for art search, skipped.', 3); + return array(); + } $config = AmpConfig::get('art_order'); $methods = get_class_methods('Art'); @@ -608,40 +960,42 @@ class Art extends database_object debug_event('Art','Searching using:' . json_encode($config), 3); + $plugin_names = Plugin::get_plugins('gather_arts'); foreach ($config as $method) { - - if (!in_array($method, $allowed_methods)) { - debug_event('Art', "$method not in allowed_methods, skipping", 3); - continue; - } - $method_name = "gather_" . $method; - if (in_array($method_name, $methods)) { + $data = array(); + if (in_array($method, $plugin_names)) { + $plugin = new Plugin($method); + $installed_version = Plugin::get_plugin_version($plugin->_plugin->name); + if ($installed_version) { + if ($plugin->load($GLOBALS['user'])) { + $data = $plugin->_plugin->gather_arts($type, $options, $limit); + } + } + } else if (in_array($method_name, $methods)) { debug_event('Art', "Method used: $method_name", 3); // Some of these take options! switch ($method_name) { - case 'gather_amazon': - $data = $this->{$method_name}($limit, $options['keyword']); - break; case 'gather_lastfm': $data = $this->{$method_name}($limit, $options); break; + case 'gather_google': + $data = $this->{$method_name}($limit, $options); + break; default: $data = $this->{$method_name}($limit); break; } + } else { + debug_event("Art", $method_name . " not defined", 1); + } - // Add the results we got to the current set - $results = array_merge($results, (array) $data); + // Add the results we got to the current set + $results = array_merge($results, (array) $data); - if ($limit && count($results) >= $limit) { - return array_slice($results, 0, $limit); - } - - } // if the method exists - else { - debug_event("Art", "$method_name not defined", 1); + if ($limit && count($results) >= $limit) { + return array_slice($results, 0, $limit); } } // end foreach @@ -658,6 +1012,8 @@ class Art extends database_object * gather_db * This function retrieves art that's already in the database * + * @param int|null $limit + * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function gather_db($limit = null) @@ -672,20 +1028,21 @@ class Art extends database_object * gather_musicbrainz * This function retrieves art based on MusicBrainz' Advanced * Relationships + * @param int $limit + * @param array $data + * @return array */ - public function gather_musicbrainz($limit = 5) + public function gather_musicbrainz($limit = 5, $data = array()) { $images = array(); $num_found = 0; - if ($this->type == 'album') { - $album = new Album($this->uid); - } else { + if ($this->type != 'album') { return $images; } - if ($album->mbid) { - debug_event('mbz-gatherart', "Album MBID: " . $album->mbid, '5'); + if ($data['mbid']) { + debug_event('mbz-gatherart', "Album MBID: " . $data['mbid'], '5'); } else { return $images; } @@ -695,12 +1052,12 @@ class Art extends database_object 'url-rels' ); try { - $release = $mb->lookup('release', $album->mbid, $includes); + $release = $mb->lookup('release', $data['mbid'], $includes); } catch (Exception $e) { return $images; } - $asin = $release->asin; + $asin = $release['asin']; if ($asin) { debug_event('mbz-gatherart', "Found ASIN: " . $asin, '5'); @@ -715,23 +1072,14 @@ class Art extends database_object // to avoid complicating things even further, we only look for large cover art $url = 'http://' . $base_url . '/images/P/' . $asin . '.' . $server_num . '.LZZZZZZZ.jpg'; debug_event('mbz-gatherart', "Evaluating Amazon URL: " . $url, '5'); - $options = array(); - if (AmpConfig::get('proxy_host') AND AmpConfig::get('proxy_port')) { - $proxy = array(); - $proxy[] = AmpConfig::get('proxy_host') . ':' . AmpConfig::get('proxy_port'); - if (AmpConfig::get('proxy_user')) { - $proxy[] = AmpConfig::get('proxy_user'); - $proxy[] = AmpConfig::get('proxy_pass'); - } - $options['proxy'] = $proxy; - } - $request = Requests::get($url, array(), $options); + $request = Requests::get($url, array(), Core::requests_options()); if ($request->status_code == 200) { $num_found++; debug_event('mbz-gatherart', "Amazon URL added: " . $url, '5'); $images[] = array( 'url' => $url, 'mime' => 'image/jpeg', + 'title' => 'MusicBrainz' ); if ($num_found >= $limit) { return $images; @@ -814,6 +1162,7 @@ class Art extends database_object $images[] = array( 'url' => $url, 'mime' => 'image/jpeg', + 'title' => 'MusicBrainz' ); if ($num_found >= $limit) { return $images; @@ -827,152 +1176,20 @@ class Art extends database_object } // gather_musicbrainz - /** - * gather_amazon - * This takes keywords and performs a search of the Amazon website - * for the art. It returns an array of found objects with mime/url keys - */ - public function gather_amazon($limit = 5, $keywords = '') - { - $images = array(); - $final_results = array(); - $possible_keys = array( - 'LargeImage', - 'MediumImage', - 'SmallImage' - ); - - if ($this->type == 'album') { - $album = new Album($this->uid); - } else { - return $images; - } - - // Prevent the script from timing out - set_time_limit(0); - - if (empty($keywords)) { - $keywords = $album->full_name; - /* If this isn't a various album combine with artist name */ - if ($album->artist_count == '1') { $keywords .= ' ' . $album->artist_name; } - } - - /* Attempt to retrieve the album art order */ - $amazon_base_urls = AmpConfig::get('amazon_base_urls'); - - /* If it's not set */ - if (!count($amazon_base_urls)) { - $amazon_base_urls = array('http://webservices.amazon.com'); - } - - /* Foreach through the base urls that we should check */ - foreach ($amazon_base_urls as $amazon_base) { - - // Create the Search Object - $amazon = new AmazonSearch(AmpConfig::get('amazon_developer_public_key'), AmpConfig::get('amazon_developer_private_key'), AmpConfig::get('amazon_developer_associate_tag'), $amazon_base); - if (AmpConfig::get('proxy_host') AND AmpConfig::get('proxy_port')) { - $proxyhost = AmpConfig::get('proxy_host'); - $proxyport = AmpConfig::get('proxy_port'); - $proxyuser = AmpConfig::get('proxy_user'); - $proxypass = AmpConfig::get('proxy_pass'); - debug_event('amazon', 'setProxy', 5); - $amazon->setProxy($proxyhost, $proxyport, $proxyuser, $proxypass); - } - - $search_results = array(); - - /* Set up the needed variables */ - $max_pages_to_search = max(AmpConfig::get('max_amazon_results_pages'),$amazon->_default_results_pages); - // while we have pages to search - do { - $raw_results = $amazon->search(array('artist'=>'', 'album'=>'', 'keywords'=>$keywords)); - $total = count($raw_results) + count($search_results); - - // If we've gotten more then we wanted - if ($limit && $total > $limit) { - $raw_results = array_slice($raw_results, 0, -($total - $limit), true); - - debug_event('amazon-xml', "Found $total, limit $limit; reducing and breaking from loop", 5); - // Merge the results and BREAK! - $search_results = array_merge($search_results,$raw_results); - break; - } // if limit defined - - $search_results = array_merge($search_results,$raw_results); - $pages_to_search = min($max_pages_to_search, $amazon->_maxPage); - debug_event('amazon-xml', "Searched results page " . ($amazon->_currentPage+1) . "/" . $pages_to_search,'5'); - $amazon->_currentPage++; - - } while ($amazon->_currentPage < $pages_to_search); - - - // Only do the second search if the first actually returns something - if (count($search_results)) { - $final_results = $amazon->lookup($search_results); - } - - /* Log this if we're doin debug */ - debug_event('amazon-xml',"Searched using $keywords with " . AmpConfig::get('amazon_developer_key') . " as key, results: " . count($final_results), 5); - - // If we've hit our limit - if (!empty($limit) && count($final_results) >= $limit) { - break; - } - - } // end foreach - - /* Foreach through what we've found */ - foreach ($final_results as $result) { - - $key = ''; - /* Recurse through the images found */ - foreach ($possible_keys as $k) { - if (strlen($result[$k])) { - $key = $k; - break; - } - } // foreach - - // Rudimentary image type detection, only JPG and GIF allowed. - if (substr($result[$key], -4) == '.jpg') { - $mime = "image/jpeg"; - } elseif (substr($result[$key], -4) == '.gif') { - $mime = "image/gif"; - } elseif (substr($result[$key], -4) == '.png') { - $mime = "image/png"; - } else { - /* Just go to the next result */ - continue; - } - - $data = array(); - $data['url'] = $result[$key]; - $data['mime'] = $mime; - - $images[] = $data; - - if (!empty($limit)) { - if (count($images) >= $limit) { - return $images; - } - } - - } // if we've got something - - return $images; - - } // gather_amazon - /** * gather_folder * This returns the art from the folder of the files * If a limit is passed or the preferred filename is found the current * results set is returned + * @param int $limit + * @return array */ public function gather_folder($limit = 5) { - $media = new Album($this->uid); - $songs = $media->get_songs(); + if (!$limit) { + $limit = 5; + } + $results = array(); $preferred = false; // For storing which directories we've already done @@ -991,10 +1208,20 @@ class Art extends database_object 'png' ); - foreach ($songs as $song_id) { - $song = new Song($song_id); - $dir = dirname($song->file); + $dirs = array(); + if ($this->type == 'album') { + $media = new Album($this->uid); + $songs = $media->get_songs(); + foreach ($songs as $song_id) { + $song = new Song($song_id); + $dirs[] = Core::conv_lc_file( dirname($song->file) ); + } + } else if ($this->type == 'video') { + $media = new Video($this->uid); + $dirs[] = Core::conv_lc_file( dirname($media->file) ); + } + foreach ($dirs as $dir) { if (isset($processed[$dir])) { continue; } @@ -1013,7 +1240,7 @@ class Art extends database_object $processed[$dir] = true; // Recurse through this dir and create the files array - while ($file = readdir($handle)) { + while (false !== ($file = readdir($handle))) { $extension = pathinfo($file); $extension = $extension['extension']; @@ -1025,7 +1252,7 @@ class Art extends database_object $full_filename = $dir . '/' . $file; // Make sure it's got something in it - if (!filesize($full_filename)) { + if (!Core::get_filesize($full_filename)) { debug_event('folder_art', "Empty file, rejecting $file", 5); continue; } @@ -1045,7 +1272,8 @@ class Art extends database_object debug_event('folder_art', "Found preferred image file: $file", 5); $preferred[$index] = array( 'file' => $full_filename, - 'mime' => 'image/' . $extension + 'mime' => 'image/' . $extension, + 'title' => 'Folder' ); break; } @@ -1053,13 +1281,14 @@ class Art extends database_object debug_event('folder_art', "Found image file: $file", 5); $results[$index] = array( 'file' => $full_filename, - 'mime' => 'image/' . $extension + 'mime' => 'image/' . $extension, + 'title' => 'Folder' ); } // end while reading dir closedir($handle); - } // end foreach songs + } // end foreach dirs if (is_array($preferred)) { // We found our favourite filename somewhere, so we need @@ -1080,8 +1309,42 @@ class Art extends database_object * gather_tags * This looks for the art in the meta-tags of the file * itself + * @param int $limit + * @return array */ public function gather_tags($limit = 5) + { + if (!$limit) { + $limit = 5; + } + + if ($this->type == "video") { + $data = $this->gather_video_tags(); + } elseif ($this->type == 'album') { + $data = $this->gather_song_tags($limit); + } else { + $data = array(); + } + + return $data; + } + + /** + * Gather tags from video files. + * @return array + */ + public function gather_video_tags() + { + $video = new Video($this->uid); + return $this->gather_media_tags($video); + } + + /** + * Gather tags from audio files. + * @param int $limit + * @return array + */ + public function gather_song_tags($limit = 5) { // We need the filenames $album = new Album($this->uid); @@ -1093,72 +1356,109 @@ class Art extends database_object // Foreach songs in this album foreach ($songs as $song_id) { $song = new Song($song_id); - // If we find a good one, stop looking - $getID3 = new getID3(); - try { $id3 = $getID3->analyze($song->file); } catch (Exception $error) { - debug_event('getid3', $error->getMessage(), 1); - } - - if (isset($id3['asf']['extended_content_description_object']['content_descriptors']['13'])) { - $image = $id3['asf']['extended_content_description_object']['content_descriptors']['13']; - $data[] = array( - 'song' => $song->file, - 'raw' => $image['data'], - 'mime' => $image['mime']); - } - - if (isset($id3['id3v2']['APIC'])) { - // Foreach in case they have more then one - foreach ($id3['id3v2']['APIC'] as $image) { - $data[] = array( - 'song' => $song->file, - 'raw' => $image['data'], - 'mime' => $image['mime']); - } - } + $data = array_merge($data, $this->gather_media_tags($song)); if ($limit && count($data) >= $limit) { return array_slice($data, 0, $limit); } + } - } // end foreach + return $data; + } + + /** + * Gather tags from files. + * @param media $media + * @return array + */ + protected function gather_media_tags($media) + { + $mtype = strtolower(get_class($media)); + $data = array(); + $getID3 = new getID3(); + try { $id3 = $getID3->analyze($media->file); } catch (Exception $error) { + debug_event('getid3', $error->getMessage(), 1); + } + + if (isset($id3['asf']['extended_content_description_object']['content_descriptors']['13'])) { + $image = $id3['asf']['extended_content_description_object']['content_descriptors']['13']; + $data[] = array( + $mtype => $media->file, + 'raw' => $image['data'], + 'mime' => $image['mime'], + 'title' => 'ID3'); + } + + if (isset($id3['id3v2']['APIC'])) { + // Foreach in case they have more then one + foreach ($id3['id3v2']['APIC'] as $image) { + $data[] = array( + $mtype => $media->file, + 'raw' => $image['data'], + 'mime' => $image['mime'], + 'title' => 'ID3'); + } + } + + if (isset($id3['comments']['picture']['0'])) { + $image = $id3['comments']['picture']['0']; + $data[] = array( + $mtype => $media->file, + 'raw' => $image['data'], + 'mime' => $image['image_mime'], + 'title' => 'ID3'); + return $data; + } return $data; - } // gather_tags + } /** * gather_google * Raw google search to retrieve the art, not very reliable * + * @param int $limit + * @param array $data + * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function gather_google($limit = 5) + public function gather_google($limit = 5, $data = array()) { + if (!$limit) { + $limit = 5; + } + $images = array(); - $media = new $this->type($this->uid); - $media->format(); - - $search = $media->full_name; - - if ($media->artist_count == '1') - $search = $media->artist_name . ', ' . $search; - - $search = rawurlencode($search); - + $search = rawurlencode($data['keyword']); $size = '&imgsz=m'; // Medium - //$size = '&imgsz=l'; // Large - $html = file_get_contents("http://images.google.com/images?source=hp&q=$search&oq=&um=1&ie=UTF-8&sa=N&tab=wi&start=0&tbo=1$size"); + $url = "http://images.google.com/images?source=hp&q=" . $search . "&oq=&um=1&ie=UTF-8&sa=N&tab=wi&start=0&tbo=1" . $size; + debug_event('Art', 'Search url: ' . $url, '5'); - if(preg_match_all("|\ssrc\=\"(http.+?)\"|", $html, $matches, PREG_PATTERN_ORDER)) - foreach ($matches[1] as $match) { - $extension = "image/jpeg"; + try { + // Need this to not be considered as a bot (are we? ^^) + $headers = array( + 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11', + ); + $query = Requests::get($url, $headers, Core::requests_options()); + $html = $query->body; - if (strrpos($extension, '.') !== false) $extension = substr($extension, strrpos($extension, '.') + 1); + if (preg_match_all("|imgres\?imgurl\=(http.+?)&|", $html, $matches, PREG_PATTERN_ORDER)) { + foreach ($matches[1] as $match) { + $match = rawurldecode($match); + debug_event('Art', 'Found image at: ' . $match, '5'); + $results = pathinfo($match); + $mime = 'image/' . $results['extension']; - $images[] = array('url' => $match, 'mime' => $extension); + $images[] = array('url' => $match, 'mime' => $mime, 'title' => 'Google'); + if ($limit > 0 && count($images) >= $limit) + break; + } } + } catch (Exception $e) { + debug_event('Art', 'Error getting google images: ' . $e->getMessage(), '1'); + } return $images; @@ -1168,64 +1468,270 @@ class Art extends database_object * gather_lastfm * This returns the art from lastfm. It doesn't currently require an * account but may in the future. + * @param int $limit + * @param array $data + * @return array */ - public function gather_lastfm($limit, $options = false) + public function gather_lastfm($limit = 5, $data = array()) { - $data = array(); - // Create the parser object - $lastfm = new LastFMSearch(); + if (!$limit) { + $limit = 5; + } - switch ($this->type) { - case 'album': - if (is_array($options)) { - $artist = $options['artist']; - $album = $options['album_name']; - } else { - $media = new Album($this->uid); - $media->format(); - $artist = $media->artist_name; - $album = $media->full_name; + $images = array(); + + if ($this->type != 'album' || empty($data['artist']) || empty($data['album'])) { + return $images; + } + + try { + $xmldata = Recommendation::album_search($data['artist'], $data['album']); + + if (!count($xmldata)) { return array(); } + + $coverart = (array) $xmldata->coverart; + if (!$coverart) { return array(); } + + ksort($coverart); + foreach ($coverart as $url) { + // We need to check the URL for the /noimage/ stuff + if (strpos($url, '/noimage/') !== false) { + debug_event('LastFM', 'Detected as noimage, skipped ' . $url, 3); + continue; } - break; - default: - return $data; - } - if (AmpConfig::get('proxy_host') AND AmpConfig::get('proxy_port')) { - $proxyhost = AmpConfig::get('proxy_host'); - $proxyport = AmpConfig::get('proxy_port'); - $proxyuser = AmpConfig::get('proxy_user'); - $proxypass = AmpConfig::get('proxy_pass'); - debug_event('LastFM', 'proxy set', 5); - $lastfm->setProxy($proxyhost, $proxyport, $proxyuser, $proxypass); - } - - $raw_data = $lastfm->album_search($artist, $album); - - if (!count($raw_data)) { return array(); } - - $coverart = $raw_data['coverart']; - if (!is_array($coverart)) { return array(); } - - ksort($coverart); - foreach ($coverart as $url) { - // We need to check the URL for the /noimage/ stuff - if (strpos($url, '/noimage/') !== false) { - debug_event('LastFM', 'Detected as noimage, skipped ' . $url, 3); - continue; - } - - // HACK: we shouldn't rely on the extension to determine file type + // HACK: we shouldn't rely on the extension to determine file type $results = pathinfo($url); - $mime = 'image/' . $results['extension']; - $data[] = array('url' => $url, 'mime' => $mime); - if ($limit && count($data) >= $limit) { - return $data; - } - } // end foreach + $mime = 'image/' . $results['extension']; + $images[] = array('url' => $url, 'mime' => $mime, 'title' => 'LastFM'); + if ($limit && count($images) >= $limit) { + return $images; + } + } // end foreach + } catch (Exception $e) { + debug_event('art', 'LastFM error: ' . $e->getMessage(), 5); + } - return $data; + return $images; } // gather_lastfm + /** + * Gather metadata from plugin. + * @param string $type + * @param array $options + * @return array + */ + public static function gather_metadata_plugin($plugin, $type, $options) + { + $gtypes = array(); + $media_info = array(); + switch ($type) { + case 'tvshow': + case 'tvshow_season': + case 'tvshow_episode': + $gtypes[] = 'tvshow'; + $media_info['tvshow'] = $options['tvshow']; + $media_info['tvshow_season'] = $options['tvshow_season']; + $media_info['tvshow_episode'] = $options['tvshow_episode']; + break; + case 'song': + $media_info['mb_trackid'] = $options['mb_trackid']; + $media_info['title'] = $options['title']; + $media_info['artist'] = $options['artist']; + $media_info['album'] = $options['album']; + $gtypes[] = 'song'; + break; + case 'album': + $media_info['mb_albumid'] = $options['mb_albumid']; + $media_info['mb_albumid_group'] = $options['mb_albumid_group']; + $media_info['artist'] = $options['artist']; + $media_info['title'] = $options['album']; + $gtypes[] = 'music'; + $gtypes[] = 'album'; + break; + case 'artist': + $media_info['mb_artistid'] = $options['mb_artistid']; + $media_info['title'] = $options['artist']; + $gtypes[] = 'music'; + $gtypes[] = 'artist'; + break; + case 'movie': + $gtypes[] = 'movie'; + $media_info['title'] = $options['keyword']; + break; + } + + $meta = $plugin->get_metadata($gtypes, $media_info); + $images = array(); + + if ($meta['art']) { + $url = $meta['art']; + $ures = pathinfo($url); + $images[] = array('url' => $url, 'mime' => 'image/' . $ures['extension'], 'title' => $plugin->name); + } + if ($meta['tvshow_season_art']) { + $url = $meta['tvshow_season_art']; + $ures = pathinfo($url); + $images[] = array('url' => $url, 'mime' => 'image/' . $ures['extension'], 'title' => $plugin->name); + } + if ($meta['tvshow_art']) { + $url = $meta['tvshow_art']; + $ures = pathinfo($url); + $images[] = array('url' => $url, 'mime' => 'image/' . $ures['extension'], 'title' => $plugin->name); + } + + return $images; + } + + /** + * Get thumb size from thumb type. + * @param int $thumb + * @return array + */ + public static function get_thumb_size($thumb) + { + $size = array(); + + switch ($thumb) { + case 1: + /* This is used by the now_playing / browse stuff */ + $size['height'] = 100; + $size['width'] = 100; + break; + case 2: + $size['height'] = 128; + $size['width'] = 128; + break; + case 3: + /* This is used by the embedded web player */ + $size['height'] = 80; + $size['width'] = 80; + break; + case 4: + /* Web Player size */ + $size['height'] = 200; + $size['width'] = 200; // 200px width, set via CSS + break; + case 5: + /* Web Player size */ + $size['height'] = 32; + $size['width'] = 32; + break; + case 6: + /* Video browsing size */ + $size['height'] = 150; + $size['width'] = 100; + break; + case 7: + /* Video page size */ + $size['height'] = 300; + $size['width'] = 200; + break; + case 8: + /* Video preview size */ + $size['height'] = 200; + $size['width'] = 470; + break; + case 9: + /* Video preview size */ + $size['height'] = 100; + $size['width'] = 235; + break; + case 10: + /* Search preview size */ + $size['height'] = 24; + $size['width'] = 24; + break; + default: + $size['height'] = 275; + $size['width'] = 275; + break; + } + + return $size; + } + + /** + * Display an item art. + * @param library_item $item + * @param int $thumb + * @param string $link + * @return boolean + */ + public static function display_item($item, $thumb, $link = null) + { + return self::display($item->type, $item->id, $item->get_fullname(), $thumb, $link); + } + + /** + * Display an item art. + * @param string $object_type + * @param int $object_id + * @param string $name + * @param int $thumb + * @param string $link + * @param boolean $show_default + * @param string $kind + * @return boolean + */ + public static function display($object_type, $object_id, $name, $thumb, $link = null, $show_default = true, $kind = 'default') + { + if (!Core::is_library_item($object_type)) + return false; + + if (!$show_default) { + // Don't show any image if not available + if (!self::has_db($object_id, $object_type, $kind)) { + return false; + } + } + $size = self::get_thumb_size($thumb); + $prettyPhoto = ($link == null); + if ($link == null) { + $link = AmpConfig::get('web_path') . "/image.php?object_id=" . $object_id . "&object_type=" . $object_type; + if (AmpConfig::get('use_auth') && AmpConfig::get('require_session')) { + $link .= "&auth=" . session_id(); + } + if ($kind != 'default') { + $link .= '&kind=' . $kind; + } + } + echo ""; + + return true; + } + } // Art diff --git a/sources/lib/class/artist.class.php b/sources/lib/class/artist.class.php index ee286a4..11587a8 100644 --- a/sources/lib/class/artist.class.php +++ b/sources/lib/class/artist.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,42 +20,123 @@ * */ -class Artist extends database_object +class Artist extends database_object implements library_item { /* Variables from DB */ - public $id; - public $name; - public $summary; - public $placeformed; - public $yearformed; - public $last_update; - public $songs; - public $albums; - public $prefix; - public $mbid; // MusicBrainz ID - public $catalog_id; - public $time; + /** + * @var int $id + */ + public $id; + /** + * @var string $name + */ + public $name; + /** + * @var string $summary + */ + public $summary; + /** + * @var string $placeformed + */ + public $placeformed; + /** + * @var int $yearformed + */ + public $yearformed; + /** + * @var int $last_update + */ + public $last_update; + /** + * @var int $songs + */ + public $songs; + /** + * @var int $albums + */ + public $albums; + /** + * @var string $prefix + */ + public $prefix; + /** + * @var string $mbid + */ + public $mbid; // MusicBrainz ID + /** + * @var int $catalog_id + */ + public $catalog_id; + /** + * @var int $time + */ + public $time; + /** + * @var int $user + */ + public $user; + + /** + * @var array $tags + */ public $tags; + /** + * @var string $f_tags + */ public $f_tags; + /** + * @var array $labels + */ + public $labels; + /** + * @var string $f_labels + */ + public $f_labels; + /** + * @var int $object_cnt + */ public $object_cnt; + /** + * @var string $f_name + */ public $f_name; + /** + * @var string $f_full_name + */ public $f_full_name; + /** + * @var string $link + */ + public $link; + /** + * @var string $f_link + */ public $f_link; - public $f_name_link; + /** + * @var string $f_time + */ public $f_time; // Constructed vars + /** + * @var boolean $_fake + */ public $_fake = false; // Set if construct_from_array() used + /** + * @var array $_mapcache + */ private static $_mapcache = array(); /** * Artist * Artist class, for modifing a artist * Takes the ID of the artist and pulls the info from the db + * @param int|null $id + * @param int $catalog_init */ - public function __construct($id='',$catalog_init=0) + public function __construct($id=null,$catalog_init=0) { /* If they failed to pass in an id, just run for it */ if (!$id) { return false; } @@ -76,6 +157,8 @@ class Artist extends database_object * construct_from_array * This is used by the metadata class specifically but fills out a Artist object * based on a key'd array, it sets $_fake to true + * @param array $data + * @return Artist */ public static function construct_from_array($data) { @@ -98,13 +181,20 @@ class Artist extends database_object */ public static function gc() { - Dba::write('DELETE FROM `artist` USING `artist` LEFT JOIN `song` ON `song`.`artist` = `artist`.`id` LEFT JOIN `wanted` ON `wanted`.`artist` = `artist`.`id` WHERE `song`.`id` IS NULL AND `wanted`.`id` IS NULL'); + Dba::write('DELETE FROM `artist` USING `artist` LEFT JOIN `song` ON `song`.`artist` = `artist`.`id` ' . + 'LEFT JOIN `album` ON `album`.`album_artist` = `artist`.`id` ' . + 'LEFT JOIN `wanted` ON `wanted`.`artist` = `artist`.`id` ' . + 'LEFT JOIN `clip` ON `clip`.`artist` = `artist`.`id` ' . + 'WHERE `song`.`id` IS NULL AND `album`.`id` IS NULL AND `wanted`.`id` IS NULL AND `clip`.`id` IS NULL'); } /** * this attempts to build a cache of the data from the passed albums all in one query + * @param int[] $ids + * @param boolean $extra + * @return boolean */ - public static function build_cache($ids,$extra=false) + public static function build_cache($ids, $extra=false, $limit_threshold = '') { if (!is_array($ids) OR !count($ids)) { return false; } @@ -119,14 +209,14 @@ class Artist extends database_object // If we need to also pull the extra information, this is normally only used when we are doing the human display if ($extra) { - $sql = "SELECT `song`.`artist`, COUNT(`song`.`id`) AS `song_count`, COUNT(DISTINCT `song`.`album`) AS `album_count`, SUM(`song`.`time`) AS `time` FROM `song` WHERE `song`.`artist` IN $idlist GROUP BY `song`.`artist`"; + $sql = "SELECT `song`.`artist`, COUNT(DISTINCT `song`.`id`) AS `song_count`, COUNT(DISTINCT `song`.`album`) AS `album_count`, SUM(`song`.`time`) AS `time` FROM `song` WHERE `song`.`artist` IN $idlist GROUP BY `song`.`artist`"; debug_event("Artist", "build_cache sql: " . $sql, "6"); $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { if (AmpConfig::get('show_played_times')) { - $row['object_cnt'] = Stats::get_object_count('artist', $row['artist']); + $row['object_cnt'] = Stats::get_object_count('artist', $row['artist'], $limit_threshold); } parent::add_to_cache('artist_extra',$row['artist'],$row); } @@ -140,6 +230,8 @@ class Artist extends database_object /** * get_from_name * This gets an artist object based on the artist name + * @param string $name + * @return Artist */ public static function get_from_name($name) { @@ -158,13 +250,17 @@ class Artist extends database_object * get_albums * gets the album ids that this artist is a part * of + * @param int|null $catalog + * @param boolean $ignoreAlbumGroups + * @param boolean $group_release_type + * @return int[] */ - public function get_albums($catalog = null, $ignoreAlbumGroups = false) + public function get_albums($catalog = null, $ignoreAlbumGroups = false, $group_release_type = false) { $catalog_where = ""; $catalog_join = "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog`"; if ($catalog) { - $catalog_where .= " AND `catalog`.`id` = '$catalog'"; + $catalog_where .= " AND `catalog`.`id` = '" . $catalog . "'"; } if (AmpConfig::get('catalog_disable')) { $catalog_where .= " AND `catalog`.`enabled` = '1'"; @@ -190,14 +286,38 @@ class Artist extends database_object } $sql_group = "COALESCE($sql_group_type, `album`.`id`)"; - $sql = "SELECT `album`.`id` FROM album LEFT JOIN `song` ON `song`.`album`=`album`.`id` $catalog_join " . - "WHERE `song`.`artist`='$this->id' $catalog_where GROUP BY $sql_group ORDER BY $sql_sort"; + $sql = "SELECT `album`.`id`, `album`.`release_type` FROM album LEFT JOIN `song` ON `song`.`album`=`album`.`id` $catalog_join " . + "WHERE (`song`.`artist`='$this->id' OR `album`.`album_artist`='$this->id') $catalog_where GROUP BY $sql_group ORDER BY $sql_sort"; debug_event("Artist", "$sql", "6"); $db_results = Dba::read($sql); while ($r = Dba::fetch_assoc($db_results)) { - $results[] = $r['id']; + if ($group_release_type) { + // We assume undefined release type is album + $rtype = $r['release_type'] ?: 'album'; + if (!isset($results[$rtype])) { + $results[$rtype] = array(); + } + $results[$rtype][] = $r['id']; + + $sort = AmpConfig::get('album_release_type_sort'); + if ($sort) { + $results_sort = array(); + $asort = explode(',', $sort); + + foreach ($asort as $rtype) { + if (array_key_exists($rtype, $results)) { + $results_sort[$rtype] = $results[$rtype]; + unset($results[$rtype]); + } + } + + $results = array_merge($results_sort, $results); + } + } else { + $results[] = $r['id']; + } } return $results; @@ -207,6 +327,7 @@ class Artist extends database_object /** * get_songs * gets the songs for this artist + * @return int[] */ public function get_songs() { @@ -214,12 +335,12 @@ class Artist extends database_object if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } - $sql .= "WHERE `song`.`artist`='" . Dba::escape($this->id) . "' "; + $sql .= "WHERE `song`.`artist` = ? "; if (AmpConfig::get('catalog_disable')) { $sql .= "AND `catalog`.`enabled` = '1' "; } - $sql .= "ORDER BY album, track"; - $db_results = Dba::read($sql); + $sql .= "ORDER BY `song`.`album`, `song`.`track`"; + $db_results = Dba::read($sql, array($this->id)); $results = array(); while ($r = Dba::fetch_assoc($db_results)) { @@ -233,6 +354,7 @@ class Artist extends database_object /** * get_random_songs * Gets the songs from this artist in a random order + * @return int[] */ public function get_random_songs() { @@ -242,12 +364,12 @@ class Artist extends database_object if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } - $sql .= "WHERE `song`.`artist`='$this->id' "; + $sql .= "WHERE `song`.`artist` = ? "; if (AmpConfig::get('catalog_disable')) { $sql .= "AND `catalog`.`enabled` = '1' "; } $sql .= "ORDER BY RAND()"; - $db_results = Dba::read($sql); + $db_results = Dba::read($sql, array($this->id)); while ($r = Dba::fetch_assoc($db_results)) { $results[] = $r['id']; @@ -260,15 +382,17 @@ class Artist extends database_object /** * _get_extra info * This returns the extra information for the artist, this means totals etc + * @param int $catalog + * @return array */ - private function _get_extra_info($catalog=FALSE) + private function _get_extra_info($catalog=0, $limit_threshold ='') { // Try to find it in the cache and save ourselves the trouble if (parent::is_cached('artist_extra',$this->id) ) { $row = parent::get_from_cache('artist_extra',$this->id); } else { $uid = Dba::escape($this->id); - $sql = "SELECT `song`.`artist`,COUNT(`song`.`id`) AS `song_count`, COUNT(DISTINCT `song`.`album`) AS `album_count`, SUM(`song`.`time`) AS `time` FROM `song` LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` " . + $sql = "SELECT `song`.`artist`,COUNT(DISTINCT `song`.`id`) AS `song_count`, COUNT(DISTINCT `song`.`album`) AS `album_count`, SUM(`song`.`time`) AS `time`, `song`.`catalog` as `catalog_id` FROM `song` LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` " . "WHERE `song`.`artist`='$uid' "; if ($catalog) { $sql .= "AND (`song`.`catalog` = '$catalog') "; @@ -282,7 +406,7 @@ class Artist extends database_object $db_results = Dba::read($sql); $row = Dba::fetch_assoc($db_results); if (AmpConfig::get('show_played_times')) { - $row['object_cnt'] = Stats::get_object_count('artist', $row['artist']); + $row['object_cnt'] = Stats::get_object_count('artist', $row['artist'], $limit_threshold); } parent::add_to_cache('artist_extra',$row['artist'],$row); } @@ -291,6 +415,7 @@ class Artist extends database_object $this->songs = $row['song_count']; $this->albums = $row['album_count']; $this->time = $row['time']; + $this->catalog_id = $row['catalog_id']; return $row; @@ -302,48 +427,225 @@ class Artist extends database_object * information and reformats the relevent values * so they can be displayed in a table for example * it changes the title into a full link. + * @return boolean */ - public function format() + public function format($details = true, $limit_threshold = '') { /* Combine prefix and name, trim then add ... if needed */ $name = trim($this->prefix . " " . $this->name); $this->f_name = $name; $this->f_full_name = trim(trim($this->prefix) . ' ' . trim($this->name)); - // If this is a fake object, we're done here - if ($this->_fake) { return true; } + // If this is a memory-only object, we're done here + if (!$this->id) { return true; } if ($this->catalog_id) { - $this->f_link = AmpConfig::get('web_path') . '/artists.php?action=show&catalog=' . $this->catalog_id . '&artist=' . $this->id; - $this->f_name_link = "f_link . "\" title=\"" . $this->f_full_name . "\">" . $name . ""; + $this->link = AmpConfig::get('web_path') . '/artists.php?action=show&catalog=' . $this->catalog_id . '&artist=' . $this->id; + $this->f_link = "link . "\" title=\"" . $this->f_full_name . "\">" . $name . ""; } else { - $this->f_link = AmpConfig::get('web_path') . '/artists.php?action=show&artist=' . $this->id; - $this->f_name_link = "f_link . "\" title=\"" . $this->f_full_name . "\">" . $name . ""; + $this->link = AmpConfig::get('web_path') . '/artists.php?action=show&artist=' . $this->id; + $this->f_link = "link . "\" title=\"" . $this->f_full_name . "\">" . $name . ""; } - // Get the counts - $extra_info = $this->_get_extra_info($this->catalog_id); - //Format the new time thingy that we just got - $min = sprintf("%02d",(floor($extra_info['time']/60)%60)); + if ($details) { + // Get the counts + $extra_info = $this->_get_extra_info($this->catalog_id, $limit_threshold); - $sec = sprintf("%02d",($extra_info['time']%60)); - $hours = floor($extra_info['time']/3600); + //Format the new time thingy that we just got + $min = sprintf("%02d",(floor($extra_info['time']/60)%60)); - $this->f_time = ltrim($hours . ':' . $min . ':' . $sec,'0:'); + $sec = sprintf("%02d",($extra_info['time']%60)); + $hours = floor($extra_info['time']/3600); - $this->tags = Tag::get_top_tags('artist', $this->id); - $this->f_tags = Tag::get_display($this->tags); + $this->f_time = ltrim($hours . ':' . $min . ':' . $sec,'0:'); - $this->object_cnt = $extra_info['object_cnt']; + $this->tags = Tag::get_top_tags('artist', $this->id); + $this->f_tags = Tag::get_display($this->tags, true, 'artist'); + + if (AmpConfig::get('label')) { + $this->labels = Label::get_labels($this->id); + $this->f_labels = Label::get_display($this->labels, true); + } + + $this->object_cnt = $extra_info['object_cnt']; + } return true; } // format + /** + * Get item keywords for metadata searches. + * @return array + */ + public function get_keywords() + { + $keywords = array(); + $keywords['mb_artistid'] = array('important' => false, + 'label' => T_('Artist MusicBrainzID'), + 'value' => $this->mbid); + $keywords['artist'] = array('important' => true, + 'label' => T_('Artist'), + 'value' => $this->f_full_name); + + return $keywords; + } + + /** + * Get item fullname. + * @return string + */ + public function get_fullname() + { + return $this->f_full_name; + } + + /** + * Get parent item description. + * @return array|null + */ + public function get_parent() + { + return null; + } + + /** + * Get item childrens. + * @return array + */ + public function get_childrens() + { + $medias = array(); + $albums = $this->get_albums(); + foreach ($albums as $album_id) { + $medias[] = array( + 'object_type' => 'album', + 'object_id' => $album_id + ); + } + return array('album' => $medias); + } + + /** + * Search for item childrens. + * @param string $name + * @return array + */ + public function search_childrens($name) + { + $search['type'] = "album"; + $search['rule_0_input'] = $name; + $search['rule_0_operator'] = 4; + $search['rule_0'] = "title"; + $search['rule_1_input'] = $this->name; + $search['rule_1_operator'] = 4; + $search['rule_1'] = "artist"; + $albums = Search::run($search); + + $childrens = array(); + foreach ($albums as $album) { + $childrens[] = array( + 'object_type' => 'album', + 'object_id' => $album + ); + } + return $childrens; + } + + /** + * Get all childrens and sub-childrens medias. + * @param string $filter_type + * @return array + */ + public function get_medias($filter_type = null) + { + $medias = array(); + if (!$filter_type || $filter_type == 'song') { + $songs = $this->get_songs(); + foreach ($songs as $song_id) { + $medias[] = array( + 'object_type' => 'song', + 'object_id' => $song_id + ); + } + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array($this->catalog_id); + } + + /** + * Get item's owner. + * @return int|null + */ + public function get_user_owner() + { + return $this->user; + } + + /** + * Get default art kind for this item. + * @return string + */ + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + return $this->summary; + } + + public function display_art($thumb = 2) + { + $id = null; + $type = null; + + if (Art::has_db($this->id, 'artist')) { + $id = $this->id; + $type = 'artist'; + } + + if ($id !== null && $type !== null) { + Art::display($type, $id, $this->get_fullname(), $thumb, $this->link); + } + } + + public function can_edit($user = null) + { + if (!$user) { + $user = $GLOBALS['user']->id; + } + + if (!$user) + return false; + + if (AmpConfig::get('upload_allow_edit')) { + if ($this->user !== null && $user == $this->user) + return true; + } + + return Access::check('interface', 50, $user); + } + /** * check * * Checks for an existing artist; if none exists, insert one. + * @param string $name + * @param string $mbid + * @param boolean $readonly + * @return int|null */ public static function check($name, $mbid = null, $readonly = false) { @@ -429,27 +731,37 @@ class Artist extends database_object /** * update * This takes a key'd array of data and updates the current artist + * @param array $data + * @return int */ - public function update($data) + public function update(array $data) { // Save our current ID + $name = isset($data['name']) ? $data['name'] : $this->name; + $mbid = isset($data['mbid']) ? $data['mbid'] : $this->mbid; + $summary = isset($data['summary']) ? $data['summary'] : $this->summary; + $placeformed = isset($data['placeformed']) ? $data['placeformed'] : $this->placeformed; + $yearformed = isset($data['yearformed']) ? $data['yearformed'] : $this->yearformed; + $current_id = $this->id; // Check if name is different than current name - if ($this->name != $data['name']) { - $artist_id = self::check($data['name'], $this->mbid); + if ($this->name != $name) { + $artist_id = self::check($name, $mbid, true); $updated = false; $songs = array(); // If it's changed we need to update - if ($artist_id != $this->id) { + if ($artist_id != null && $artist_id != $this->id) { $songs = $this->get_songs(); foreach ($songs as $song_id) { Song::update_artist($artist_id,$song_id); } $updated = true; $current_id = $artist_id; + Stats::migrate('artist', $this->id, $artist_id); + Art::migrate('artist', $this->id, $artist_id); self::gc(); } // end if it changed @@ -461,24 +773,41 @@ class Artist extends database_object Rating::gc(); Userflag::gc(); } // if updated - } else if ($this->mbid != $data['mbid']) { + } else if ($this->mbid != $mbid) { $sql = 'UPDATE `artist` SET `mbid` = ? WHERE `id` = ?'; - Dba::write($sql, array($data['mbid'], $current_id)); + Dba::write($sql, array($mbid, $current_id)); } // Update artist name (if we don't want to use the MusicBrainz name) - $trimmed = Catalog::trim_prefix(trim($data['name'])); + $trimmed = Catalog::trim_prefix(trim($name)); $name = $trimmed['string']; if ($name != '' && $name != $this->name) { $sql = 'UPDATE `artist` SET `name` = ? WHERE `id` = ?'; Dba::write($sql, array($name, $current_id)); } + $this->update_artist_info($summary, $placeformed, $yearformed); + + $this->name = $name; + $this->mbid = $mbid; + $override_childs = false; - if ($data['apply_childs'] == 'checked') { + if ($data['overwrite_childs'] == 'checked') { $override_childs = true; } - $this->update_tags($data['edit_tags'], $override_childs, $current_id); + + $add_to_childs = false; + if ($data['add_to_childs'] == 'checked') { + $add_to_childs = true; + } + + if (isset($data['edit_tags'])) { + $this->update_tags($data['edit_tags'], $override_childs, $add_to_childs, $current_id, true); + } + + if (AmpConfig::get('label') && isset($data['edit_labels'])) { + Label::update_label_list($data['edit_labels'], $this->id, true); + } return $current_id; @@ -488,28 +817,82 @@ class Artist extends database_object * update_tags * * Update tags of artists and/or albums + * @param string $tags_comma + * @param boolean $override_childs + * @param int|null $current_id */ - public function update_tags($tags_comma, $override_childs, $current_id = null) + public function update_tags($tags_comma, $override_childs, $add_to_childs, $current_id = null, $force_update = false) { if ($current_id == null) { $current_id = $this->id; } - Tag::update_tag_list($tags_comma, 'artist', $current_id); + Tag::update_tag_list($tags_comma, 'artist', $current_id, $force_update ? true : $override_childs); - if ($override_childs) { + if ($override_childs || $add_to_childs) { $albums = $this->get_albums(null, true); foreach ($albums as $album_id) { $album = new Album($album_id); - $album->update_tags($tags_comma, $override_childs); + $album->update_tags($tags_comma, $override_childs, $add_to_childs); } } } + /** + * Update artist information. + * @param string $summary + * @param string $placeformed + * @param int $yearformed + * @return boolean + */ public function update_artist_info($summary, $placeformed, $yearformed) { $sql = "UPDATE `artist` SET `summary` = ?, `placeformed` = ?, `yearformed` = ?, `last_update` = ? WHERE `id` = ?"; - return Dba::write($sql, array($summary, $placeformed, $yearformed, time(), $this->id)); + $sqlret = Dba::write($sql, array($summary, $placeformed, $yearformed, time(), $this->id)); + + $this->summary = $summary; + $this->placeformed = $placeformed; + $this->yearformed = $yearformed; + + return $sqlret; + } + + /** + * Update artist associated user. + * @param int $user + * @return boolean + */ + public function update_artist_user($user) + { + $sql = "UPDATE `artist` SET `user` = ? WHERE `id` = ?"; + return Dba::write($sql, array($user, $this->id)); + } + + public function remove_from_disk() + { + $deleted = true; + $album_ids = $this->get_albums(); + foreach ($album_ids as $id) { + $album = new Album($id); + $deleted = $album->remove_from_disk(); + if (!$deleted) { + debug_event('artist', 'Error when deleting the album `' . $id .'`.', 1); + break; + } + } + + if ($deleted) { + $sql = "DELETE FROM `artist` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + if ($deleted) { + Art::gc('artist', $this->id); + Userflag::gc('artist', $this->id); + Rating::gc('artist', $this->id); + Shoutbox::gc('artist', $this->id); + } + } + + return $deleted; } } // end of artist class diff --git a/sources/lib/class/artist_event.class.php b/sources/lib/class/artist_event.class.php index 5ddd68c..7b33a13 100644 --- a/sources/lib/class/artist_event.class.php +++ b/sources/lib/class/artist_event.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,8 +33,10 @@ class Artist_Event /** * get_upcoming_events * Returns a list of upcoming events + * @param Artist $artist + * @return SimpleXMLElement|boolean */ - public static function get_upcoming_events($artist) + public static function get_upcoming_events(Artist $artist) { if (isset($artist->mbid)) { $query = 'mbid=' . rawurlencode($artist->mbid); @@ -59,8 +61,10 @@ class Artist_Event /** * get_past_events * Returns a list of past events + * @param Artist $artist + * @return SimpleXMLElement|boolean */ - public static function get_past_events($artist) + public static function get_past_events(Artist $artist) { if (isset($artist->mbid)) { $query = 'mbid=' . rawurlencode($artist->mbid); diff --git a/sources/lib/class/auth.class.php b/sources/lib/class/auth.class.php index 3d22a6e..3748b21 100644 --- a/sources/lib/class/auth.class.php +++ b/sources/lib/class/auth.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -44,6 +44,8 @@ class Auth * This is called when you want to log out and nuke your session. * This is the function used for the Ajax logouts, if no id is passed * it tries to find one from the session, + * @param string $key + * @param boolean $relogin */ public static function logout($key='', $relogin = true) { @@ -82,6 +84,10 @@ class Auth * * This takes a username and password and then returns the results * based on what happens when we try to do the auth. + * @param string $username + * @param string $password + * @param boolean $allow_ui + * @return array */ public static function login($username, $password, $allow_ui = false) { @@ -104,6 +110,8 @@ class Auth * login_step2 * * This process authenticate step2 for an auth module + * @param string $auth_mod + * @return array */ public static function login_step2($auth_mod) { @@ -122,6 +130,9 @@ class Auth * mysql_auth * * This is the core function of our built-in authentication. + * @param string $username + * @param string $password + * @return array */ private static function mysql_auth($username, $password) { @@ -168,6 +179,9 @@ class Auth * * Check to make sure the pam_auth function is implemented (module is * installed), then check the credentials. + * @param string $username + * @param string $password + * @return array */ private static function pam_auth($username, $password) { @@ -197,6 +211,9 @@ class Auth * * Calls an external program compatible with mod_authnz_external * such as pwauth. + * @param string $username + * @param string $password + * @return array */ private static function external_auth($username, $password) { @@ -221,7 +238,7 @@ class Auth fclose($pipes[0]); fclose($pipes[1]); if ($stderr = fread($pipes[2], 8192)) { - debug_event('external_auth', $stderr, 5); + debug_event('external_auth', "fread error: " . $stderr, 5); } fclose($pipes[2]); } else { @@ -257,6 +274,9 @@ class Auth * the DN fetched from the LDAP directory" * * require-attribute "an attribute fetched from the LDAP * directory matches the given value" + * @param string $username + * @param string $password + * @return array */ private static function ldap_auth($username, $password) { @@ -285,6 +305,13 @@ class Auth return $results; } + if (strpos($ldap_filter, "%v") >= 0) { + $ldap_filter = str_replace("%v", $username, $ldap_filter); + } else { + // This to support previous configuration where only the fieldname was set + $ldap_filter = "($ldap_filter=$username)"; + } + $ldap_name_field = AmpConfig::get('ldap_name_field'); $ldap_email_field = AmpConfig::get('ldap_email_field'); @@ -300,7 +327,9 @@ class Auth return $results; } // If bind fails - $sr = ldap_search($ldap_link, $ldap_dn, "(&(objectclass=$ldap_class)($ldap_filter=$username))"); + $searchstr = "(&(objectclass=$ldap_class)$ldap_filter)"; + debug_event('ldap_auth', 'ldap_search: ' . $searchstr, 5); + $sr = ldap_search($ldap_link, $ldap_dn, $searchstr); $info = ldap_get_entries($ldap_link, $sr); if ($info["count"] == 1) { @@ -367,6 +396,9 @@ class Auth * http_auth * This auth method relies on HTTP auth from the webserver * + * @param string $username + * @param string $password + * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ private static function http_auth($username, $password) @@ -390,6 +422,9 @@ class Auth * openid_auth * Authenticate user with OpenID * + * @param string $username + * @param string $password + * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ private static function openid_auth($username, $password) @@ -468,6 +503,7 @@ class Auth /** * openid_auth_2 * Authenticate user with OpenID, step 2 + * @return array */ private static function openid_auth_2() { diff --git a/sources/lib/class/autoupdate.class.php b/sources/lib/class/autoupdate.class.php index 2593124..c114820 100644 --- a/sources/lib/class/autoupdate.class.php +++ b/sources/lib/class/autoupdate.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -38,6 +38,10 @@ class AutoUpdate // static class } + /** + * Check if current version is a development version. + * @return boolean + */ protected static function is_develop() { $version = AmpConfig::get('version'); @@ -46,39 +50,56 @@ class AutoUpdate return ($vspart[count($vspart) - 1] == 'develop'); } + /** + * Check if current version is a git repository. + * @return boolean + */ protected static function is_git_repository() { return is_dir(AmpConfig::get('prefix') . '/.git'); } + /** + * Check if branch develop exists in git repository. + * @return boolean + */ protected static function is_branch_develop_exists() { return is_readable(AmpConfig::get('prefix') . '/.git/refs/heads/develop'); } + /** + * Perform a GitHub request. + * @param string $action + * @return string|null + */ public static function github_request($action) { try { + // https is mandatory $url = "https://api.github.com/repos/ampache/ampache" . $action; - $request = Requests::get($url); + $request = Requests::get($url, array(), Core::requests_options()); // Not connected / API rate limit exceeded: just ignore, it will pass next time if ($request->status_code != 200) { debug_event('autoupdate', 'Github API request ' . $url . ' failed with http code ' . $request->status_code, '1'); - return; + return null; } return json_decode($request->body); } catch (Exception $e) { debug_event('autoupdate', 'Request error: ' . $e->getMessage(), '1'); - return ""; + return null; } } + /** + * Check if last github check expired. + * @return boolean + */ protected static function lastcheck_expired() { $lastcheck = AmpConfig::get('autoupdate_lastcheck'); if (!$lastcheck) { - User::rebuild_all_preferences(); Preference::update('autoupdate_lastcheck', $GLOBALS['user']->id, '1'); AmpConfig::set('autoupdate_lastcheck', '1', true); } @@ -86,11 +107,21 @@ class AutoUpdate return ((time() - (3600 * 3)) > $lastcheck); } + /** + * Get latest available version from GitHub. + * @param boolean $force + * @return string + */ public static function get_latest_version($force = false) { $lastversion = ''; // Forced or last check expired, check latest version from Github if ($force || (self::lastcheck_expired() && AmpConfig::get('autoupdate'))) { + // Always update last check time to avoid infinite check on permanent errors (proxy, firewall, ...) + $time = time(); + Preference::update('autoupdate_lastcheck', $GLOBALS['user']->id, $time); + AmpConfig::set('autoupdate_lastcheck', $time, true); + // Development version, get latest commit on develop branch if (self::is_develop()) { $commits = self::github_request('/commits/develop'); @@ -98,9 +129,6 @@ class AutoUpdate $lastversion = $commits->sha; Preference::update('autoupdate_lastversion', $GLOBALS['user']->id, $lastversion); AmpConfig::set('autoupdate_lastversion', $lastversion, true); - $time = time(); - Preference::update('autoupdate_lastcheck', $GLOBALS['user']->id, $time); - AmpConfig::set('autoupdate_lastcheck', $time, true); $available = self::is_update_available(true); Preference::update('autoupdate_lastversion_new', $GLOBALS['user']->id, $available); AmpConfig::set('autoupdate_lastversion_new', $available, true); @@ -113,9 +141,6 @@ class AutoUpdate $lastversion = $tags[0]->name; Preference::update('autoupdate_lastversion', $GLOBALS['user']->id, $lastversion); AmpConfig::set('autoupdate_lastversion', $lastversion, true); - $time = time(); - Preference::update('autoupdate_lastcheck', $GLOBALS['user']->id, $time); - AmpConfig::set('autoupdate_lastcheck', $time, true); $available = self::is_update_available(true); Preference::update('autoupdate_lastversion_new', $GLOBALS['user']->id, $available); AmpConfig::set('autoupdate_lastversion_new', $available, true); @@ -130,6 +155,10 @@ class AutoUpdate return $lastversion; } + /** + * Get current local version. + * @return string + */ public static function get_current_version() { if (self::is_develop()) { @@ -139,15 +168,24 @@ class AutoUpdate } } + /** + * Get current local git commit. + * @return string + */ public static function get_current_commit() { if (self::is_branch_develop_exists()) { return trim(file_get_contents(AmpConfig::get('prefix') . '/.git/refs/heads/develop')); } - return false; + return ''; } + /** + * Check if an update is available. + * @param boolean $force + * @return boolean + */ public static function is_update_available($force = false) { if (!$force && (!self::lastcheck_expired() || !AmpConfig::get('autoupdate'))) { @@ -183,6 +221,9 @@ class AutoUpdate return $available; } + /** + * Display new version information and update link if possible. + */ public static function show_new_version() { echo '
'; @@ -191,6 +232,23 @@ class AutoUpdate echo T_('See') . ' ' . T_('changes') . ' '; echo T_('or') . ' ' . T_('download') . '.'; + if (self::is_git_repository()) { + echo ' | .:: Update ::.'; + } echo '
'; } + + /** + * Update local git repository. + */ + public static function update_files() + { + echo T_('Updating Ampache sources with `git pull` ...') . '
'; + ob_flush(); + chdir(AmpConfig::get('prefix')); + exec('git pull https://github.com/ampache/ampache.git'); + echo T_('Done') . '
'; + ob_flush(); + self::get_latest_version(true); + } } diff --git a/sources/lib/class/broadcast.class.php b/sources/lib/class/broadcast.class.php index 7007a19..56669cc 100644 --- a/sources/lib/class/broadcast.class.php +++ b/sources/lib/class/broadcast.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,22 +20,61 @@ * */ -class Broadcast extends database_object +class Broadcast extends database_object implements library_item { + /** + * @var int $id + */ public $id; + /** + * @var boolean $started + */ public $started; + /** + * @var int $listeners + */ public $listeners; + /** + * @var int $song + */ public $song; + /** + * @var int $song_position + */ public $song_position; + /** + * @var string $name + */ public $name; + /** + * @var int $user + */ + public $user; + /** + * @var array $tags + */ public $tags; + /** + * @var string $f_name + */ public $f_name; + /** + * @var string $f_link + */ public $f_link; + /** + * @var string $f_tags + */ public $f_tags; + /** + * @var boolean $is_private + */ + public $is_private; /** * Constructor + * @param int $id */ public function __construct($id=0) { @@ -52,6 +91,11 @@ class Broadcast extends database_object return true; } //constructor + /** + * Update broadcast state. + * @param boolean $started + * @param string $key + */ public function update_state($started, $key='') { $sql = "UPDATE `broadcast` SET `started` = ?, `key` = ?, `song` = '0', `listeners` = '0' WHERE `id` = ?"; @@ -60,6 +104,10 @@ class Broadcast extends database_object $this->started = $started; } + /** + * Update broadcast listeners. + * @param int $listeners + */ public function update_listeners($listeners) { $sql = "UPDATE `broadcast` SET `listeners` = ? " . @@ -68,6 +116,10 @@ class Broadcast extends database_object $this->listeners = $listeners; } + /** + * Update broadcast current song. + * @param int $song_id + */ public function update_song($song_id) { $sql = "UPDATE `broadcast` SET `song` = ? " . @@ -77,12 +129,22 @@ class Broadcast extends database_object $this->song_position = 0; } + /** + * Delete the broadcast. + * @return boolean + */ public function delete() { $sql = "DELETE FROM `broadcast` WHERE `id` = ?"; return Dba::write($sql, array($this->id)); } + /** + * Create a broadcast + * @param string $name + * @param string $description + * @return int + */ public static function create($name, $description='') { if (!empty($name)) { @@ -95,26 +157,144 @@ class Broadcast extends database_object return 0; } - public function update($data) + /** + * Update a broadcast from data array. + * @param array $data + * @return int + */ + public function update(array $data) { if (isset($data['edit_tags'])) { - Tag::update_tag_list($data['edit_tags'], 'broadcast', $this->id); + Tag::update_tag_list($data['edit_tags'], 'broadcast', $this->id, true); } $sql = "UPDATE `broadcast` SET `name` = ?, `description` = ?, `is_private` = ? " . "WHERE `id` = ?"; $params = array($data['name'], $data['description'], !empty($data['private']), $this->id); - return Dba::write($sql, $params); + Dba::write($sql, $params); + + return $this->id; } - public function format() + public function format($details = true) { $this->f_name = $this->name; $this->f_link = '' . scrub_out($this->f_name) . ''; - $this->tags = Tag::get_top_tags('broadcast', $this->id); - $this->f_tags = Tag::get_display($this->tags); + if ($details) { + $this->tags = Tag::get_top_tags('broadcast', $this->id); + $this->f_tags = Tag::get_display($this->tags, true, 'broadcast'); + } } + /** + * Get item keywords for metadata searches. + * @return array + */ + public function get_keywords() + { + return array(); + } + + /** + * Get item fullname. + * @return string + */ + public function get_fullname() + { + return $this->f_name; + } + + /** + * Get parent item description. + * @return array|null + */ + public function get_parent() + { + return null; + } + + /** + * Get item childrens. + * @return array + */ + public function get_childrens() + { + return array(); + } + + /** + * Search for item childrens. + * @param string $name + * @return array + */ + public function search_childrens($name) + { + return array(); + } + + /** + * Get all childrens and sub-childrens medias. + * @param string $filter_type + * @return array + */ + public function get_medias($filter_type = null) + { + // Not a media, shouldn't be that + $medias = array(); + if (!$filter_type || $filter_type == 'broadcast') { + $medias[] = array( + 'object_type' => 'broadcast', + 'object_id' => $this->id + ); + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array(); + } + + /** + * Get item's owner. + * @return int|null + */ + public function get_user_owner() + { + return $this->user; + } + + /** + * Get default art kind for this item. + * @return string + */ + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + return null; + } + + public function display_art($thumb = 2) + { + if (Art::has_db($this->id, 'broadcast')) { + Art::display('broadcast', $this->id, $this->get_fullname(), $thumb, $this->link); + } + } + + /** + * Get all broadcasts sql query. + * @return string + */ public static function get_broadcast_list_sql() { $sql = "SELECT `id` FROM `broadcast` WHERE `started` = '1' "; @@ -122,6 +302,10 @@ class Broadcast extends database_object return $sql; } + /** + * Get all broadcasts. + * @return int[] + */ public static function get_broadcast_list() { $sql = self::get_broadcast_list_sql(); @@ -135,12 +319,21 @@ class Broadcast extends database_object return $results; } + /** + * Generate a new broadcast key. + * @return string + */ public static function generate_key() { // Should be improved for security reasons! return md5(uniqid(rand(), true)); } + /** + * Get broadcast from its key. + * @param string $key + * @return Broadcast|null + */ public static function get_broadcast($key) { $sql = "SELECT `id` FROM `broadcast` WHERE `key` = ?"; @@ -153,16 +346,23 @@ class Broadcast extends database_object return null; } + /** + * Show action buttons. + */ public function show_action_buttons() { if ($this->id) { if ($GLOBALS['user']->has_access('75')) { - echo "id . "\" onclick=\"showEditDialog('broadcast_row', '" . $this->id . "', 'edit_broadcast_" . $this->id . "', '" . T_('Broadcast edit') . "', 'broadcast_row_', 'refresh_broadcast')\">" . UI::get_icon('edit', T_('Edit')) . ""; + echo "id . "\" onclick=\"showEditDialog('broadcast_row', '" . $this->id . "', 'edit_broadcast_" . $this->id . "', '" . T_('Broadcast edit') . "', 'broadcast_row_')\">" . UI::get_icon('edit', T_('Edit')) . ""; echo " id ."\">" . UI::get_icon('delete', T_('Delete')) . ""; } } } + /** + * Get broadcast link. + * @return string + */ public static function get_broadcast_link() { $link = "
"; @@ -171,6 +371,11 @@ class Broadcast extends database_object return $link; } + /** + * Get unbroadcast link. + * @param int $id + * @return string + */ public static function get_unbroadcast_link($id) { $link = "
"; @@ -180,6 +385,11 @@ class Broadcast extends database_object return $link; } + /** + * Get broadcasts from an user. + * @param int $user_id + * @return int[] + */ public static function get_broadcasts($user_id) { $sql = "SELECT `id` FROM `broadcast` WHERE `user` = ?"; @@ -192,11 +402,22 @@ class Broadcast extends database_object return $broadcasts; } + public static function gc() + { + + } + /* + * Get play url. * + * @param int $oid + * @param string $additional_params + * @param string $player + * @param boolean $local + * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public static function play_url($oid, $additional_params='') + public static function play_url($oid, $additional_params='', $player=null, $local=false) { return $oid; } diff --git a/sources/lib/class/broadcast_server.class.php b/sources/lib/class/broadcast_server.class.php index 5ded480..bd1d558 100644 --- a/sources/lib/class/broadcast_server.class.php +++ b/sources/lib/class/broadcast_server.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -36,9 +36,21 @@ class Broadcast_Server implements MessageComponentInterface const BROADCAST_AUTH_SID = "AUTH_SID"; public $verbose; + /** + * @var ConnectionInterface[] $clients + */ protected $clients; + /** + * @var string[] $sids + */ protected $sids; + /** + * @var ConnectionInterface[] $listeners + */ protected $listeners; + /** + * @var Broadcast[] $broadcasters + */ protected $broadcasters; public function __construct() @@ -50,11 +62,20 @@ class Broadcast_Server implements MessageComponentInterface $this->broadcasters = array(); } + /** + * + * @param \Ratchet\ConnectionInterface $conn + */ public function onOpen(ConnectionInterface $conn) { $this->clients[$conn->resourceId] = $conn; } + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param string $msg + */ public function onMessage(ConnectionInterface $from, $msg) { $commands = explode(';', $msg); @@ -101,6 +122,11 @@ class Broadcast_Server implements MessageComponentInterface } } + /** + * + * @param int $song_id + * @return string + */ protected function getSongJS($song_id) { $media = array(); @@ -113,13 +139,18 @@ class Broadcast_Server implements MessageComponentInterface return WebPlayer::get_media_js_param($item[0]); } - protected function notifySong($from, $song_id) + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param int $song_id + */ + protected function notifySong(ConnectionInterface $from, $song_id) { if ($this->isBroadcaster($from)) { $broadcast = $this->broadcasters[$from->resourceId]; $clients = $this->getListeners($broadcast); - Session::extend(Stream::$session, 'stream'); + Session::extend(Stream::get_session(), 'stream'); $broadcast->update_song($song_id); $this->broadcastMessage($clients, self::BROADCAST_SONG, base64_encode($this->getSongJS($song_id))); @@ -132,7 +163,12 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function notifySongPosition($from, $song_position) + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param int $song_position + */ + protected function notifySongPosition(ConnectionInterface $from, $song_position) { if ($this->isBroadcaster($from)) { $broadcast = $this->broadcasters[$from->resourceId]; @@ -151,12 +187,17 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function notifyPlayerPlay($from, $play) + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param boolean $play + */ + protected function notifyPlayerPlay(ConnectionInterface $from, $play) { if ($this->isBroadcaster($from)) { $broadcast = $this->broadcasters[$from->resourceId]; $clients = $this->getListeners($broadcast); - $this->broadcastMessage($clients, self::BROADCAST_PLAYER_PLAY, $play); + $this->broadcastMessage($clients, self::BROADCAST_PLAYER_PLAY, $play ? 'true' : 'false'); if ($this->verbose) { echo "[" . time() ."][info]Broadcast " . $broadcast->id . " player state: " . $play . "." . "\r\n"; @@ -166,7 +207,31 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function registerBroadcast($from, $broadcast_key) + /** + * + * @param \Ratchet\ConnectionInterface $from + */ + protected function notifyEnded(ConnectionInterface $from) + { + if ($this->isBroadcaster($from)) { + $broadcast = $this->broadcasters[$from->resourceId]; + $clients = $this->getListeners($broadcast); + $this->broadcastMessage($clients, self::BROADCAST_ENDED); + + if ($this->verbose) { + echo "[" . time() ."][info]Broadcast " . $broadcast->id . " ended." . "\r\n"; + } + } else { + debug_event('broadcast', 'Action unauthorized.', '3'); + } + } + + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param string $broadcast_key + */ + protected function registerBroadcast(ConnectionInterface $from, $broadcast_key) { $broadcast = Broadcast::get_broadcast($broadcast_key); if ($broadcast) { @@ -179,7 +244,11 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function unregisterBroadcast($conn) + /** + * + * @param \Ratchet\ConnectionInterface $conn + */ + protected function unregisterBroadcast(ConnectionInterface $conn) { $broadcast = $this->broadcasters[$conn->resourceId]; $clients = $this->getListeners($broadcast); @@ -194,6 +263,11 @@ class Broadcast_Server implements MessageComponentInterface } } + /** + * + * @param int $broadcast_id + * @return Broadcast + */ protected function getRunningBroadcast($broadcast_id) { $broadcast = null; @@ -206,7 +280,12 @@ class Broadcast_Server implements MessageComponentInterface return $broadcast; } - protected function registerListener($from, $broadcast_id) + /** + * + * @param \Ratchet\ConnectionInterface $from + * @param int $broadcast_id + */ + protected function registerListener(ConnectionInterface $from, $broadcast_id) { $broadcast = $this->getRunningBroadcast($broadcast_id); @@ -226,7 +305,12 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function authSid($conn, $sid) + /** + * + * @param \Ratchet\ConnectionInterface $conn + * @param string $sid + */ + protected function authSid(ConnectionInterface $conn, $sid) { if (Session::exists('stream', $sid)) { $this->sids[$conn->resourceId] = $sid; @@ -237,7 +321,11 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function unregisterListener($conn) + /** + * + * @param \Ratchet\ConnectionInterface $conn + */ + protected function unregisterListener(ConnectionInterface $conn) { foreach ($this->listeners as $broadcast_id => $brlisteners) { $lindex = array_search($conn, $brlisteners); @@ -257,7 +345,11 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function notifyNbListeners($broadcast) + /** + * + * @param Broadcast $broadcast + */ + protected function notifyNbListeners(Broadcast $broadcast) { $broadcaster_id = array_search($broadcast, $this->broadcasters); if ($broadcaster_id) { @@ -269,16 +361,32 @@ class Broadcast_Server implements MessageComponentInterface } } - protected function getListeners($broadcast) + /** + * + * @param Broadcast $broadcast + * @return \Ratchet\ConnectionInterface + */ + protected function getListeners(Broadcast $broadcast) { return $this->listeners[$broadcast->id]; } - protected function isBroadcaster($conn) + /** + * + * @param \Ratchet\ConnectionInterface $conn + * @return boolean + */ + protected function isBroadcaster(ConnectionInterface $conn) { return array_key_exists($conn->resourceId, $this->broadcasters); } + /** + * + * @param \Ratchet\ConnectionInterface[] $clients + * @param string $cmd + * @param string $value + */ protected function broadcastMessage($clients, $cmd, $value='') { $msg = $cmd . ':' . $value . ';'; @@ -291,6 +399,10 @@ class Broadcast_Server implements MessageComponentInterface } } + /** + * + * @param \Ratchet\ConnectionInterface $conn + */ public function onClose(ConnectionInterface $conn) { if ($this->isBroadcaster($conn)) { @@ -303,11 +415,20 @@ class Broadcast_Server implements MessageComponentInterface unset($this->sids[$conn->resourceId]); } + /** + * + * @param \Ratchet\ConnectionInterface $conn + * @param \Exception $e + */ public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } + /** + * + * @return string + */ public static function get_address() { $websocket_address = AmpConfig::get('websocket_address'); diff --git a/sources/lib/class/browse.class.php b/sources/lib/class/browse.class.php index 2df265e..459873c 100644 --- a/sources/lib/class/browse.class.php +++ b/sources/lib/class/browse.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -31,8 +31,17 @@ */ class Browse extends Query { + /** + * @var boolean $show_header + */ public $show_header; + /** + * Constructor. + * + * @param int|null $id + * @param boolean $cached + */ public function __construct($id = null, $cached = true) { parent::__construct($id, $cached); @@ -48,6 +57,8 @@ class Browse extends Query * set_simple_browse * This sets the current browse object to a 'simple' browse method * which means use the base query provided and expand from there + * + * @param boolean $value */ public function set_simple_browse($value) { @@ -58,6 +69,9 @@ class Browse extends Query /** * add_supplemental_object * Legacy function, need to find a better way to do that + * + * @param string $class + * @param int $uid */ public function add_supplemental_object($class, $uid) { @@ -71,6 +85,8 @@ class Browse extends Query * get_supplemental_objects * This returns an array of 'class','id' for additional objects that * need to be created before we start this whole browsing thing. + * + * @return array */ public function get_supplemental_objects() { @@ -84,11 +100,42 @@ class Browse extends Query } // get_supplemental_objects + /** + * update_browse_from_session + * Restore the previous start index from something saved into the current session. + */ + public function update_browse_from_session() + { + if ($this->is_simple() && $this->get_start() == 0) { + $name = 'browse_current_' . $this->get_type(); + if (isset($_SESSION[$name]) && isset($_SESSION[$name]['start']) && $_SESSION[$name]['start'] > 0) { + + // Checking if value is suitable + $start = $_SESSION[$name]['start']; + if ($this->get_offset() > 0) { + + $set_page = floor($start / $this->get_offset()); + if ($this->get_total() > $this->get_offset()) { + $total_pages = ceil($this->get_total() / $this->get_offset()); + } else { + $total_pages = 0; + } + + if ($set_page >= 0 && $set_page <= $total_pages) { + $this->set_start($start); + } + } + } + } + } + /** * show_objects * This takes an array of objects * and requires the correct template based on the * type that we are currently browsing + * + * @param int[] $object_ids */ public function show_objects($object_ids = null, $argument = null) { @@ -100,9 +147,8 @@ class Browse extends Query // Limit is based on the user's preferences if this is not a // simple browse because we've got too much here - if ((count($object_ids) > $this->get_start()) && - ! $this->is_simple() && - ! $this->is_static_content()) { + if ($this->get_start() >= 0 && (count($object_ids) > $this->get_start()) && + ! $this->is_simple()) { $object_ids = array_slice( $object_ids, $this->get_start(), @@ -133,37 +179,53 @@ class Browse extends Query $match = ' (' . $filter_value . ')';*/ } elseif ($filter_value = $this->get_filter('catalog')) { // Get the catalog title - $catalog = Catalog::create_from_id($filter_value); + $catalog = Catalog::create_from_id(intval($filter_value)); $match = ' (' . $catalog->name . ')'; } $type = $this->get_type(); + // Update the session value only if it's allowed on the current browser + if ($this->get_update_session()) { + $_SESSION['browse_current_' . $type]['start'] = $browse->get_start(); + } + // Set the correct classes based on type $class = "box browse_" . $type; - debug_event('browse', 'Called for type {'.$type.'}', '5'); + $argument_param = ($argument ? '&argument=' . scrub_in($argument) : ''); + + debug_event('browse', 'Show objects called for type {'.$type.'}', '5'); + + $limit_threshold = $this->get_threshold(); // Switch on the type of browsing we're doing switch ($type) { case 'song': $box_title = T_('Songs') . $match; - Song::build_cache($object_ids); + Song::build_cache($object_ids, $limit_threshold); $box_req = AmpConfig::get('prefix') . '/templates/show_songs.inc.php'; break; case 'album': - $box_title = T_('Albums') . $match; Album::build_cache($object_ids); - $allow_group_disks = $argument; + $box_title = T_('Albums') . $match; + if (is_array($argument)) { + $allow_group_disks = $argument['group_disks']; + if ($argument['title']) { + $box_title = $argument['title']; + } + } else { + $allow_group_disks = false; + } $box_req = AmpConfig::get('prefix') . '/templates/show_albums.inc.php'; break; case 'user': - $box_title = T_('Manage Users') . $match; + $box_title = T_('Users') . $match; $box_req = AmpConfig::get('prefix') . '/templates/show_users.inc.php'; break; case 'artist': $box_title = T_('Artists') . $match; - Artist::build_cache($object_ids, 'extra'); + Artist::build_cache($object_ids, true, $limit_threshold); $box_req = AmpConfig::get('prefix') . '/templates/show_artists.inc.php'; break; case 'live_stream': @@ -187,7 +249,7 @@ class Browse extends Query break; case 'smartplaylist': $box_title = T_('Smart Playlists') . $match; - $box_req = AmpConfig::get('prefix') . '/templates/show_smartplaylists.inc.php'; + $box_req = AmpConfig::get('prefix') . '/templates/show_searches.inc.php'; break; case 'catalog': $box_title = T_('Catalogs'); @@ -204,6 +266,7 @@ class Browse extends Query break; case 'video': Video::build_cache($object_ids); + $video_type = 'video'; $box_title = T_('Videos'); $box_req = AmpConfig::get('prefix') . '/templates/show_videos.inc.php'; break; @@ -231,12 +294,52 @@ class Browse extends Query $box_title = T_('Broadcasts'); $box_req = AmpConfig::get('prefix') . '/templates/show_broadcasts.inc.php'; break; + case 'license': + $box_title = T_('Media Licenses'); + $box_req = AmpConfig::get('prefix') . '/templates/show_manage_license.inc.php'; + break; + case 'tvshow': + $box_title = T_('TV Shows'); + $box_req = AmpConfig::get('prefix') . '/templates/show_tvshows.inc.php'; + break; + case 'tvshow_season': + $box_title = T_('Seasons'); + $box_req = AmpConfig::get('prefix') . '/templates/show_tvshow_seasons.inc.php'; + break; + case 'tvshow_episode': + $box_title = T_('Episodes'); + $video_type = $type; + $box_req = AmpConfig::get('prefix') . '/templates/show_videos.inc.php'; + break; + case 'movie': + $box_title = T_('Movies'); + $video_type = $type; + $box_req = AmpConfig::get('prefix') . '/templates/show_videos.inc.php'; + break; + case 'clip': + $box_title = T_('Clips'); + $video_type = $type; + $box_req = AmpConfig::get('prefix') . '/templates/show_videos.inc.php'; + break; + case 'personal_video': + $box_title = T_('Personal Videos'); + $video_type = $type; + $box_req = AmpConfig::get('prefix') . '/templates/show_videos.inc.php'; + break; + case 'label': + $box_title = T_('Labels'); + $box_req = AmpConfig::get('prefix') . '/templates/show_labels.inc.php'; + break; + case 'pvmsg': + $box_title = T_('Private Messages'); + $box_req = AmpConfig::get('prefix') . '/templates/show_pvmsgs.inc.php'; + break; default: // Rien a faire break; } // end switch on type - Ajax::start_container('browse_content_' . $type, 'browse_content'); + Ajax::start_container($this->get_content_div(), 'browse_content'); if ($this->get_show_header()) { if (isset($box_req) && isset($box_title)) { UI::show_box_top($box_title, $class); @@ -252,31 +355,32 @@ class Browse extends Query UI::show_box_bottom(); } echo ''; } else { if (!$this->get_use_pages()) { - $this->show_next_link(); + $this->show_next_link($argument); } } Ajax::end_container(); } // show_object - public function show_next_link() + public function show_next_link($argument = null) { - $limit = $this->get_offset(); - $start = $this->get_start(); - $total = $this->get_total(); + $limit = $this->get_offset(); + $start = $this->get_start(); + $total = $this->get_total(); $next_offset = $start + $limit; if ($next_offset <= $total) { - echo '' . T_('More') . ''; + echo '' . T_('More') . ''; } } /** * set_filter_from_request * //FIXME + * @param array $request */ public function set_filter_from_request($request) { @@ -300,6 +404,11 @@ class Browse extends Query } } // set_filter_from_request + /** + * + * @param string $type + * @param string $custom_base + */ public function set_type($type, $custom_base = '') { $cn = 'browse_' . $type . '_pages'; @@ -321,6 +430,11 @@ class Browse extends Query parent::set_type($type, $custom_base); } + /** + * + * @param string $option + * @param string $value + */ public function save_cookie_params($option, $value) { if ($this->get_type()) { @@ -328,36 +442,96 @@ class Browse extends Query } } + /** + * + * @param boolean $use_pages + */ public function set_use_pages($use_pages) { $this->save_cookie_params('pages', $use_pages ? 'true' : 'false'); $this->_state['use_pages'] = $use_pages; } + /** + * + * @return boolean + */ public function get_use_pages() { return $this->_state['use_pages']; } + /** + * + * @param boolean $use_alpha + */ public function set_use_alpha($use_alpha) { $this->save_cookie_params('alpha', $use_alpha ? 'true' : 'false'); $this->_state['use_alpha'] = $use_alpha; } + /** + * + * @return boolean + */ public function get_use_alpha() { return $this->_state['use_alpha']; } + /** + * + * @param boolean $show_header + */ public function set_show_header($show_header) { $this->show_header = $show_header; } + /** + * Allow the current page to be save into the current session + * @param boolean $update_session + */ + public function set_update_session($update_session) + { + $this->_state['update_session'] = $update_session; + } + + /** + * + * @return boolean + */ public function get_show_header() { return $this->show_header; } + /** + * + * @return boolean + */ + public function get_update_session() + { + return $this->_state['update_session']; + } + + /** + * + * @param string $threshold + */ + public function set_threshold($threshold) + { + $this->_state['threshold'] = $threshold; + } + + /** + * + * @return string + */ + public function get_threshold() + { + return $this->_state['threshold']; + } + } // browse diff --git a/sources/lib/class/catalog.class.php b/sources/lib/class/catalog.class.php index 478848d..0090bf8 100644 --- a/sources/lib/class/catalog.class.php +++ b/sources/lib/class/catalog.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -29,49 +29,167 @@ */ abstract class Catalog extends database_object { + /** + * @var int $id + */ public $id; + /** + * @var string $name + */ public $name; + /** + * @var int $last_update + */ public $last_update; + /** + * @var int $last_add + */ public $last_add; + /** + * @var int $last_clean + */ public $last_clean; + /** + * @var string $key + */ public $key; + /** + * @var string $rename_pattern + */ public $rename_pattern; + /** + * @var string $sort_pattern + */ public $sort_pattern; + /** + * @var string $catalog_type + */ public $catalog_type; + /** + * @var string $gather_types + */ + public $gather_types; + /** + * @var string $f_name + */ public $f_name; - public $f_name_link; + /** + * @var string $link + */ + public $link; + /** + * @var string $f_link + */ + public $f_link; + /** + * @var string $f_update + */ public $f_update; + /** + * @var string $f_add + */ public $f_add; + /** + * @var string $f_clean + */ public $f_clean; - /* This is a private var that's used during catalog builds */ + /* + * This is a private var that's used during catalog builds + * @var array $_playlists + */ protected $_playlists = array(); - // Cache all files in catalog for quick lookup during add + /* + * Cache all files in catalog for quick lookup during add + * @var array $_filecache + */ protected $_filecache = array(); // Used in functions + /** + * @var array $albums + */ protected static $albums = array(); + /** + * @var array $artists + */ protected static $artists = array(); + /** + * @var array $tags + */ protected static $tags = array(); + /** + * @return string + */ abstract public function get_type(); + /** + * @return string + */ abstract public function get_description(); + /** + * @return string + */ abstract public function get_version(); + /** + * @return string + */ abstract public function get_create_help(); + /** + * @return boolean + */ abstract public function is_installed(); + /** + * @return boolean + */ abstract public function install(); abstract public function add_to_catalog($options = null); abstract public function verify_catalog_proc(); abstract public function clean_catalog_proc(); + /** + * @return array + */ abstract public function catalog_fields(); + /** + * @return string + */ abstract public function get_rel_path($file_path); + /** + * @return media|null + */ abstract public function prepare_media($media); - /** + /** + * Check if the catalog is ready to perform actions (configuration completed, ...) + * @return boolean + */ + public function isReady() + { + return true; + } + + /** + * Show a message to make the catalog ready. + */ + public function show_ready_process() + { + // Do nothing. + } + + /** + * Perform the last step process to make the catalog ready. + */ + public function perform_ready() + { + // Do nothing. + } + + /** * uninstall * This removes the remote catalog + * @return boolean */ public function uninstall() { @@ -82,9 +200,13 @@ abstract class Catalog extends database_object Dba::query($sql); return true; - } // uninstall + /** + * Create a catalog from its id. + * @param int $id + * @return Catalog|null + */ public static function create_from_id($id) { $sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?'; @@ -100,10 +222,15 @@ abstract class Catalog extends database_object * create_catalog_type * This function attempts to create a catalog type * all Catalog modules should be located in /modules/catalog/.class.php + * @param string $type + * @param int $id + * @return Catalog|null */ public static function create_catalog_type($type, $id=0) { - if (!$type) { return false; } + if (!$type) { + return false; + } $filename = AmpConfig::get('prefix') . '/modules/catalog/' . $type . '.catalog.php'; $include = require_once $filename; @@ -126,9 +253,12 @@ abstract class Catalog extends database_object } return $catalog; } - } // create_catalog_type + /** + * Show dropdown catalog types. + * @param string $divback + */ public static function show_catalog_types($divback = 'catalog_type_fields') { echo ""; } // run_playlist_method @@ -347,23 +458,29 @@ class Stream * get_base_url * This returns the base requirements for a stream URL this does not include anything after the index.php?sid=???? */ - public static function get_base_url() + public static function get_base_url($local=false) { $session_string = ''; - if (AmpConfig::get('require_session')) { - $session_string = 'ssid=' . self::$session . '&'; + if (AmpConfig::get('use_auth') && AmpConfig::get('require_session')) { + $session_string = 'ssid=' . self::get_session() . '&'; } - $web_path = AmpConfig::get('web_path'); + if ($local) { + $web_path = AmpConfig::get('local_web_path'); + } else { + $web_path = AmpConfig::get('web_path'); + } - if (AmpConfig::get('force_http_play') OR !empty(self::$force_http)) { + if (AmpConfig::get('force_http_play')) { $web_path = str_replace("https://", "http://",$web_path); } - if (AmpConfig::get('http_port') != '80') { + + $http_port = AmpConfig::get('http_port'); + if (!empty($http_port) && $http_port != '80') { if (preg_match("/:(\d+)/",$web_path,$matches)) { - $web_path = str_replace(':' . $matches['1'],':' . AmpConfig::get('http_port'),$web_path); + $web_path = str_replace(':' . $matches['1'], ':' . $http_port, $web_path); } else { - $web_path = str_replace(AmpConfig::get('http_host'), AmpConfig::get('http_host') . ':' . AmpConfig::get('http_port'), $web_path); + $web_path = str_replace(AmpConfig::get('http_host'), AmpConfig::get('http_host') . ':' . $http_port, $web_path); } } diff --git a/sources/lib/class/stream_playlist.class.php b/sources/lib/class/stream_playlist.class.php index e2990ae..ae84e04 100644 --- a/sources/lib/class/stream_playlist.class.php +++ b/sources/lib/class/stream_playlist.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -45,7 +45,7 @@ class Stream_Playlist Stream::set_session($id); } - $this->id = Stream::$session; + $this->id = Stream::get_session(); if (!Session::exists('stream', $this->id)) { debug_event('stream_playlist', 'Session::exists failed', 2); @@ -70,7 +70,6 @@ class Stream_Playlist debug_event("stream_playlist.class.php", "Adding url {".json_encode($url)."}...", 5); $this->urls[] = $url; - $sql = 'INSERT INTO `stream_playlist` '; $fields = array(); @@ -115,19 +114,23 @@ class Stream_Playlist $additional_params .= "&custom_play_action=" . $medium['custom_play_action']; } + if ($_SESSION['iframe']['subtitle']) { + $additional_params .= "&subtitle=" . $_SESSION['iframe']['subtitle']; + } + $type = $medium['object_type']; - //$url['object_id'] = $medium['object_id']; + $object_id = $medium['object_id']; $url['type'] = $type; - $object = new $type($medium['object_id']); + $object = new $type($object_id); $object->format(); // Don't add disabled media objects to the stream playlist // Playing a disabled media return a 404 error that could make failed the player (mpd ...) - if (make_bool($object->enabled)) { + if (!isset($object->enabled) || make_bool($object->enabled)) { //FIXME: play_url shouldn't be static $url['url'] = $type::play_url($object->id, $additional_params); - $api_session = (AmpConfig::get('require_session')) ? Stream::$session : false; + $api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false; // Set a default which can be overridden $url['author'] = 'Ampache'; @@ -137,14 +140,15 @@ class Stream_Playlist $url['title'] = $object->title; $url['author'] = $object->f_artist_full; $url['info_url'] = $object->f_link; - $url['image_url'] = Art::url($object->album, 'album', $api_session); + $url['image_url'] = Art::url($object->album, 'album', $api_session, (AmpConfig::get('ajax_load') ? 3 : 4)); $url['album'] = $object->f_album_full; break; case 'video': $url['title'] = 'Video - ' . $object->title; $url['author'] = $object->f_artist_full; + $url['resolution'] = $object->f_resolution; break; - case 'radio': + case 'live_stream': $url['title'] = 'Radio - ' . $object->name; if (!empty($object->site_url)) { $url['title'] .= ' (' . $object->site_url . ')'; @@ -177,11 +181,17 @@ class Stream_Playlist public static function check_autoplay_append() { // For now, only iframed web player support media append in the currently played playlist - return ((AmpConfig::get('iframes') && AmpConfig::get('play_type') == 'web_player') || + return ((AmpConfig::get('ajax_load') && AmpConfig::get('play_type') == 'web_player') || AmpConfig::get('play_type') == 'localplay' ); } + public static function check_autoplay_next() + { + // Currently only supported for web player + return (AmpConfig::get('ajax_load') && AmpConfig::get('play_type') == 'web_player'); + } + public function generate_playlist($type, $redirect = false) { if (!count($this->urls)) { @@ -203,7 +213,7 @@ class Stream_Playlist unset($ext); break; case 'asx': - $ct = 'video/x-ms-wmv'; + $ct = 'video/x-ms-asf'; break; case 'pls': $ct = 'audio/x-scpls'; @@ -218,6 +228,10 @@ class Stream_Playlist case 'xspf': $ct = 'application/xspf+xml'; break; + case 'hls': + $ext = 'm3u8'; + $ct = 'application/vnd.apple.mpegurl'; + break; case 'm3u': default: // Assume M3U if the pooch is screwed @@ -294,9 +308,11 @@ class Stream_Playlist { echo "#EXTM3U\n"; + $i = 0; foreach ($this->urls as $url) { echo '#EXTINF:' . $url->time, ',' . $url->author . ' - ' . $url->title . "\n"; echo $url->url . "\n"; + $i++; } } // create_m3u @@ -391,6 +407,48 @@ class Stream_Playlist } // create_xspf + public function create_hls() + { + $ssize = 10; + echo "#EXTM3U\n"; + echo "#EXT-X-TARGETDURATION:" . $ssize . "\n"; + echo "#EXT-X-VERSION:1\n"; + echo "#EXT-X-ALLOW-CACHE:NO\n"; + echo "#EXT-X-MEDIA-SEQUENCE:0\n"; + echo "#EXT-X-PLAYLIST-TYPE:VOD\n"; // Static list of segments + + foreach ($this->urls as $url) { + $soffset = 0; + $segment = 0; + while ($soffset < $url->time) { + $type = $url->type; + $size = (($soffset + $ssize) <= $url->time) ? $ssize : ($url->time - $soffset); + $additional_params = '&transcode_to=ts&segment=' . $segment; + echo "#EXTINF:" . $size . ",\n"; + $purl = Stream_URL::parse($url->url); + $id = $purl['id']; + + unset($purl['id']); + unset($purl['ssid']); + unset($purl['type']); + unset($purl['base_url']); + unset($purl['uid']); + unset($purl['name']); + + foreach ($purl as $key => $value) { + $additional_params .= '&' . $key . '=' . $value; + } + + $hu = $type::play_url($id, $additional_params); + echo $hu . "\n"; + $soffset += $size; + $segment++; + } + } + + echo "#EXT-X-ENDLIST\n\n"; + } + /** * create_web_player * @@ -398,7 +456,7 @@ class Stream_Playlist */ public function create_web_player() { - if (AmpConfig::get("iframes")) { + if (AmpConfig::get("ajax_load")) { require AmpConfig::get('prefix') . '/templates/create_web_player_embedded.inc.php'; } else { require AmpConfig::get('prefix') . '/templates/create_web_player.inc.php'; @@ -422,6 +480,16 @@ class Stream_Playlist $localplay->add_url($url); } if (!$append) { + // We don't have metadata on Stream_URL to know its kind + // so we check the content to know if it is democratic + if (count($this->urls) == 1) { + $furl = $this->urls[0]; + if (strpos($furl->url, "&demo_id=1") !== false && $furl->time == -1) { + // If democratic, repeat the song to get the next voted one. + debug_event('stream_playlist', 'Playing democratic on localplay, enabling repeat...', 5); + $localplay->repeat(true); + } + } $localplay->play(); } @@ -445,6 +513,7 @@ class Stream_Playlist } $democratic->add_vote($items); + display_notification(T_('Vote added')); } /** @@ -460,7 +529,8 @@ class Stream_Playlist // Header redirect baby! $url = current($this->urls); - header('Location: ' . $url->url . '&action=download'); + $url = Stream_URL::add_options($url->url, '&action=download'); + header('Location: ' . $url); exit; } //create_download diff --git a/sources/lib/class/stream_url.class.php b/sources/lib/class/stream_url.class.php index dddf16f..019d8c1 100644 --- a/sources/lib/class/stream_url.class.php +++ b/sources/lib/class/stream_url.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -73,6 +73,36 @@ class Stream_URL extends memory_object return $results; } + /** + * add_options + * + * Add options to an existing stream url. + */ + public static function add_options($url, $options) + { + if (AmpConfig::get('stream_beautiful_url')) { + // We probably want beautiful url to have a real mp3 filename at the end. + // Add the new options before the filename + + $curel = explode('/', $url); + $newel = explode('&', $options); + + if (count($curel) > 2) { + foreach ($newel as $el) { + if (!empty($el)) { + $el = explode('=', $el); + array_splice($curel, count($curel) - 2, 0, $el); + } + } + $url = implode('/', $curel); + } + } else { + $url .= $options; + } + + return $url; + } + /** * format * This format the string url according to settings. diff --git a/sources/lib/class/subsonic_api.class.php b/sources/lib/class/subsonic_api.class.php index 22c972a..07584a0 100644 --- a/sources/lib/class/subsonic_api.class.php +++ b/sources/lib/class/subsonic_api.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -62,6 +62,19 @@ class Subsonic_Api return $input[$parameter]; } + public static function decrypt_password($password) + { + // Decode hex-encoded password + $encpwd = strpos($password, "enc:"); + if ($encpwd !== false) { + $hex = substr($password, 4); + $decpwd = ''; + for ($i=0; $i $reqheaders, CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => false, CURLOPT_FOLLOWLOCATION => true, @@ -122,6 +150,7 @@ class Subsonic_Api } else { header("Content-type: text/xml; charset=" . AmpConfig::get('site_charset')); } + header("access-control-allow-origin: *"); } public static function apiOutput($input, $xml) @@ -145,7 +174,6 @@ class Subsonic_Api $dom->formatOutput = true; echo $dom->saveXML(); } - } /** @@ -231,6 +259,9 @@ class Subsonic_Api $propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || ($plainText === '') ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText; + if (isset($propertiesArray['xmlns'])) { + unset($propertiesArray['xmlns']); + } //return node as array return array( $xml->getName() => $propertiesArray @@ -291,7 +322,7 @@ class Subsonic_Api $ifModifiedSince = $input['ifModifiedSince']; $catalogs = array(); - if (!empty($musicFolderId)) { + if (!empty($musicFolderId) && $musicFolderId != '-1') { $catalogs[] = $musicFolderId; } else { $catalogs = Catalog::get_catalogs(); @@ -309,7 +340,7 @@ class Subsonic_Api if ($catalog->last_clean > $clastmodified) $clastmodified = $catalog->last_clean; if ($clastmodified > $lastmodified) $lastmodified = $clastmodified; - if (!empty($ifModifiedSince) && $clastmodified > $ifModifiedSince) $fcatalogs[] = $id; + if (!empty($ifModifiedSince) && $clastmodified > ($ifModifiedSince / 1000)) $fcatalogs[] = $id; } if (empty($ifModifiedSince)) $fcatalogs = $catalogs; @@ -353,7 +384,7 @@ class Subsonic_Api self::check_version($input, "1.9.0"); $r = Subsonic_XML_Data::createSuccessResponse(); - Subsonic_XML_Data::addGenres($r, Tag::get_tags()); + Subsonic_XML_Data::addGenres($r, Tag::get_tags('song')); self::apiOutput($input, $r); } @@ -419,14 +450,14 @@ class Subsonic_Api * getVideos * Get all videos. * Takes no parameter. - * Not supported yet. */ public static function getvideos($input) { self::check_version($input, "1.7.0"); $r = Subsonic_XML_Data::createSuccessResponse(); - Subsonic_XML_Data::addVideos($r); + $videos = Catalog::get_videos(); + Subsonic_XML_Data::addVideos($r, $videos); self::apiOutput($input, $r); } @@ -443,12 +474,21 @@ class Subsonic_Api $size = $input['size']; $offset = $input['offset']; + $musicFolderId = $input['musicFolderId'] ?: 0; + + // Get albums from all catalogs by default + // Catalog filter is not supported for all request type for now. + $catalogs = null; + if ($musicFolderId > 0) { + $catalogs = array(); + $catalogs[] = $musicFolderId; + } $albums = array(); if ($type == "random") { $albums = Album::get_random($size); } else if ($type == "newest") { - $albums = Stats::get_newest("album", $size, $offset); + $albums = Stats::get_newest("album", $size, $offset, $musicFolderId); } else if ($type == "highest") { $albums = Rating::get_highest("album", $size, $offset); } else if ($type == "frequent") { @@ -458,9 +498,42 @@ class Subsonic_Api } else if ($type == "starred") { $albums = Userflag::get_latest('album'); } else if ($type == "alphabeticalByName") { - $albums = Catalog::get_albums($size, $offset); + $albums = Catalog::get_albums($size, $offset, $catalogs); } else if ($type == "alphabeticalByArtist") { - $albums = Catalog::get_albums_by_artist($size, $offset); + $albums = Catalog::get_albums_by_artist($size, $offset, $catalogs); + } else if ($type == "byYear") { + $fromYear = $input['fromYear']; + $toYear = $input['toYear']; + + if ($fromYear || $toYear) { + $search = array(); + $search['limit'] = $size; + $search['offset'] = $offset; + $search['type'] = "album"; + $i = 0; + if ($fromYear) { + $search['rule_'.$i.'_input'] = $fromYear; + $search['rule_'.$i.'_operator'] = 0; + $search['rule_'.$i.''] = "year"; + ++$i; + } + if ($toYear) { + $search['rule_'.$i.'_input'] = $toYear; + $search['rule_'.$i.'_operator'] = 1; + $search['rule_'.$i.''] = "year"; + ++$i; + } + + $query = new Search(null, 'album'); + $albums = $query->run($search); + } + } else if ($type == "byGenre") { + $genre = self::check_parameter($input, 'genre'); + + $tag_id = Tag::tag_exists($genre); + if ($tag_id) { + $albums = Tag::get_tag_objects('album', $tag_id, $size, $offset); + } } if (count($albums)) { @@ -526,17 +599,20 @@ class Subsonic_Api if (Subsonic_XML_Data::isArtist($musicFolderId)) { $artist = new Artist(Subsonic_XML_Data::getAmpacheId($musicFolderId)); $finput = $artist->name; + $operator = 4; $ftype = "artist"; } else if (Subsonic_XML_Data::isAlbum($musicFolderId)) { $album = new Album(Subsonic_XML_Data::getAmpacheId($musicFolderId)); $finput = $album->name; + $operator = 4; $ftype = "artist"; } else { - $finput = ""; - $ftype = ""; + $finput = intval($musicFolderId); + $operator = 0; + $ftype = "catalog"; } $search['rule_'.$i.'_input'] = $finput; - $search['rule_'.$i.'_operator'] = 4; + $search['rule_'.$i.'_operator'] = $operator; $search['rule_'.$i.''] = $ftype; ++$i; } @@ -617,6 +693,14 @@ class Subsonic_Api $query = self::check_parameter($input, 'query'); + $operator = 0; + if (strlen($query) > 1) { + if (substr($query, -1) == "*") { + $query = substr($query, 0, -1); + $operator = 2; // Start with + } + } + $artistCount = $input['artistCount']; $artistOffset = $input['artistOffset']; $albumCount = $input['albumCount']; @@ -628,7 +712,7 @@ class Subsonic_Api $sartist['limit'] = $artistCount; if ($artistOffset) $sartist['offset'] = $artistOffset; $sartist['rule_1_input'] = $query; - $sartist['rule_1_operator'] = 0; + $sartist['rule_1_operator'] = $operator; $sartist['rule_1'] = "name"; $sartist['type'] = "artist"; $artists = Search::run($sartist); @@ -637,7 +721,7 @@ class Subsonic_Api $salbum['limit'] = $albumCount; if ($albumOffset) $salbum['offset'] = $albumOffset; $salbum['rule_1_input'] = $query; - $salbum['rule_1_operator'] = 0; + $salbum['rule_1_operator'] = $operator; $salbum['rule_1'] = "title"; $salbum['type'] = "album"; $albums = Search::run($salbum); @@ -646,7 +730,7 @@ class Subsonic_Api $ssong['limit'] = $songCount; if ($songOffset) $ssong['offset'] = $songOffset; $ssong['rule_1_input'] = $query; - $ssong['rule_1_operator'] = 0; + $ssong['rule_1_operator'] = $operator; $ssong['rule_1'] = "anywhere"; $ssong['type'] = "song"; $songs = Search::run($ssong); @@ -680,7 +764,7 @@ class Subsonic_Api // Don't allow playlist listing for another user if (empty($username) || $username == $GLOBALS['user']->username) { - Subsonic_XML_Data::addPlaylists($r, Playlist::get_playlists()); + Subsonic_XML_Data::addPlaylists($r, Playlist::get_playlists(), Search::get_searches()); } else { $user = User::get_from_username($username); if ($user->id) { @@ -703,9 +787,14 @@ class Subsonic_Api $playlistid = self::check_parameter($input, 'id'); - $playlist = new Playlist($playlistid); $r = Subsonic_XML_Data::createSuccessResponse(); - Subsonic_XML_Data::addPlaylist($r, $playlist, true); + if (Subsonic_XML_Data::isSmartPlaylist($playlistid)) { + $playlist = new Search(Subsonic_XML_Data::getAmpacheId($playlistid), 'song'); + Subsonic_XML_Data::addSmartPlaylist($r, $playlist, true); + } else { + $playlist = new Playlist($playlistid); + Subsonic_XML_Data::addPlaylist($r, $playlist, true); + } self::apiOutput($input, $r); } @@ -726,7 +815,7 @@ class Subsonic_Api self::_updatePlaylist($playlistId, $name, $songId); $r = Subsonic_XML_Data::createSuccessResponse(); } else if (!empty($name)) { - $playlistId = Playlist::create($name, 'public'); + $playlistId = Playlist::create($name, 'private'); if (count($songId) > 0) { self::_updatePlaylist($playlistId, "", $songId); } @@ -746,20 +835,27 @@ class Subsonic_Api $newdata['pl_type'] = ($public) ? "public" : "private"; $playlist->update($newdata); - if (!is_array($songsIdToAdd)) { - $songsIdToAdd = array($songsIdToAdd); - } - if (count($songsIdToAdd) > 0) { - $playlist->add_songs(Subsonic_XML_Data::getAmpacheIds($songsIdToAdd)); + if ($songsIdToAdd) { + if (!is_array($songsIdToAdd)) { + $songsIdToAdd = array($songsIdToAdd); + } + if (count($songsIdToAdd) > 0) { + for ($i = 0; $i < count($songsIdToAdd); ++$i) { + $songsIdToAdd[$i] = Subsonic_XML_Data::getAmpacheId($songsIdToAdd[$i]); + } + $playlist->add_songs($songsIdToAdd); + } } - if (!is_array($songIndexToRemove)) { - $songIndexToRemove = array($songIndexToRemove); - } - if (is_array($songIndexToRemove) && count($songIndexToRemove) > 0) { - $tracks = Subsonic_XML_Data::getAmpacheIds($songIndexToRemove); - foreach ($tracks as $track) { - $playlist->delete_track_number($track); + if ($songIndexToRemove) { + if (!is_array($songIndexToRemove)) { + $songIndexToRemove = array($songIndexToRemove); + } + if (count($songIndexToRemove) > 0) { + foreach ($songIndexToRemove as $track) { + $playlist->delete_track_number($track); + } + $playlist->regenerate_track_numbers(); } } } @@ -777,12 +873,18 @@ class Subsonic_Api $name = $input['name']; $comment = $input['comment']; // Not supported. - $public = boolean($input['public']); - echo $public; - $songIdToAdd = $input['songIdToAdd']; - $songIndexToRemove = $input['songIndexToRemove']; + $public = ($input['public'] === "true"); - $r = Subsonic_XML_Data::createSuccessResponse(); + if (!Subsonic_XML_Data::isSmartPlaylist($playlistId)) { + $songIdToAdd = $input['songIdToAdd']; + $songIndexToRemove = $input['songIndexToRemove']; + + self::_updatePlaylist(Subsonic_XML_Data::getAmpacheId($playlistId), $name, $songIdToAdd, $songIndexToRemove, $public); + + $r = Subsonic_XML_Data::createSuccessResponse(); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, 'Cannot edit a smart playlist.'); + } self::apiOutput($input, $r); } @@ -795,10 +897,15 @@ class Subsonic_Api { self::check_version($input, "1.2.0"); - $playlistId = self::check_parameter($input, 'playlistId'); + $playlistId = self::check_parameter($input, 'id'); - $playlist = new Playlist($playlistId); - $playlist->delete(); + if (Subsonic_XML_Data::isSmartPlaylist($playlistId)) { + $playlist = new Search(Subsonic_XML_Data::getAmpacheId($playlistId), 'song'); + $playlist->delete(); + } else { + $playlist = new Playlist(Subsonic_XML_Data::getAmpacheId($playlistId)); + $playlist->delete(); + } $r = Subsonic_XML_Data::createSuccessResponse(); self::apiOutput($input, $r); @@ -815,19 +922,36 @@ class Subsonic_Api $fileid = self::check_parameter($input, 'id', true); - $maxBitRate = $input['maxBitRate']; // Not supported. - $format = $input['format']; // mp3, flv or raw. Not supported. - $timeOffset = $input['timeOffset']; // For video streaming. Not supported. + $maxBitRate = $input['maxBitRate']; + $format = $input['format']; // mp3, flv or raw + $timeOffset = $input['timeOffset']; $size = $input['size']; // For video streaming. Not supported. - $maxBitRate = $input['maxBitRate']; // For video streaming. Not supported. $estimateContentLength = $input['estimateContentLength']; // Force content-length guessing if transcode - $params = '&client=' . $input['c']; + $params = '&client=' . rawurlencode($input['c']) . '&noscrobble=1'; if ($estimateContentLength == 'true') { $params .= '&content_length=required'; } - $url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params); - self::follow_stream($url); + if ($format && $format != "raw") { + $params .= '&transcode_to=' . $format; + } + if ($maxBitRate) { + $params .= '&bitrate=' . $maxBitRate; + } + if ($timeOffset) { + $params .= '&frame=' . $timeOffset; + } + + $url = ''; + if (Subsonic_XML_Data::isVideo($fileid)) { + $url = Video::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params, 'api', function_exists('curl_version')); + } else if (Subsonic_XML_Data::isSong($fileid)) { + $url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params, 'api', function_exists('curl_version')); + } + + if (!empty($url)) { + self::follow_stream($url); + } } /** @@ -841,7 +965,7 @@ class Subsonic_Api $fileid = self::check_parameter($input, 'id', true); - $url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), '&action=download' . '&client=' . $input['c']); + $url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), '&action=download' . '&client=' . rawurlencode($input['c']) . '&noscrobble=1', 'api', function_exists('curl_version')); self::follow_stream($url); } @@ -856,7 +980,7 @@ class Subsonic_Api $fileid = self::check_parameter($input, 'id', true); - $bitRate = $input['bitRate']; // Not supported. + $bitRate = $input['bitRate']; $media = array(); $media['object_type'] = 'song'; @@ -865,7 +989,12 @@ class Subsonic_Api $medias = array(); $medias[] = $media; $stream = new Stream_Playlist(); - $stream->add($medias); + $additional_params = ''; + if ($bitRate) { + $additional_params .= '&bitrate=' . $bitRate; + } + //$additional_params .= '&transcode_to=ts'; + $stream->add($medias, $additional_params); header('Content-Type: application/vnd.apple.mpegurl;'); $stream->create_m3u(); @@ -894,19 +1023,22 @@ class Subsonic_Api if ($art != null) { $art->get_db(); - if (!$size) { - header('Content-type: ' . $art->raw_mime); - header('Content-Length: ' . strlen($art->raw)); - echo $art->raw; - } else { + if ($size) { $dim = array(); $dim['width'] = $size; $dim['height'] = $size; $thumb = $art->get_thumb($dim); - header('Content-type: ' . $thumb['thumb_mime']); - header('Content-Length: ' . strlen($thumb['thumb'])); - echo $thumb['thumb']; + if ($thumb) { + header('Content-type: ' . $thumb['thumb_mime']); + header('Content-Length: ' . strlen($thumb['thumb'])); + echo $thumb['thumb']; + return; + } } + + header('Content-type: ' . $art->raw_mime); + header('Content-Length: ' . strlen($art->raw)); + echo $art->raw; } } @@ -1093,6 +1225,41 @@ class Subsonic_Api self::apiOutput($input, $r); } + /** + * getAvatar + * Return the user avatar in bytes. + */ + public static function getavatar($input) + { + $username = self::check_parameter($input, 'username'); + + $r = null; + if ($GLOBALS['user']->access >= 100 || $GLOBALS['user']->username == $username) { + if ($GLOBALS['user']->username == $username) { + $user = $GLOBALS['user']; + } else { + $user = User::get_from_username($username); + } + + if ($user !== null) { + $avatar = $user->get_avatar(true); + if (isset($avatar['url']) && !empty($avatar['url'])) { + $request = Requests::get($avatar['url'], array(), Core::requests_options()); + header("Content-Type: " . $request->headers['Content-Type']); + echo $request->body; + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, $GLOBALS['user']->username . ' is not authorized to get avatar for other users.'); + } + + if ($r != null) { + self::apiOutput($input, $r); + } + } + /** * getInternetRadioStations * Get all internet radio stations @@ -1103,7 +1270,7 @@ class Subsonic_Api self::check_version($input, "1.9.0"); $r = Subsonic_XML_Data::createSuccessResponse(); - $radios = Radio::get_all_radios(); + $radios = Live_Stream::get_all_radios(); Subsonic_XML_Data::addRadios($r, $radios); self::apiOutput($input, $r); } @@ -1134,11 +1301,15 @@ class Subsonic_Api $id = self::check_parameter($input, 'id'); $description = $input['description']; - $expires = $input['expires']; if (AmpConfig::get('share')) { - if ($expires) { - $expire_days = round((($expires / 1000) - time()) / 86400, 0, PHP_ROUND_HALF_EVEN); + if (isset($input['expires'])) { + $expires = $input['expires']; + // Parse as a string to work on 32-bit computers + if (strlen($expires) > 3) { + $expires = intval(substr($expires, 0, - 3)); + } + $expire_days = round(($expires - time()) / 86400, 0, PHP_ROUND_HALF_EVEN); } else { $expire_days = AmpConfig::get('share_expire'); } @@ -1187,21 +1358,6 @@ class Subsonic_Api self::apiOutput($input, $r); } - /**** CURRENT UNSUPPORTED FUNCTIONS ****/ - - /** - * getLyrics - * Searches and returns lyrics for a given song. - * Takes the optional artist and title in parameters. - */ - public static function getlyrics($input) - { - self::check_version($input, "1.2.0"); - - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); - self::apiOutput($input, $r); - } - /** * updateShare * Update the description and/or expiration date for an existing share. @@ -1212,22 +1368,44 @@ class Subsonic_Api { self::check_version($input, "1.6.0"); - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); - self::apiOutput($input, $r); - } + $id = self::check_parameter($input, 'id'); + $description = $input['description']; - /** - * scrobble - * Scrobbles a given music file on last.fm. - * Takes the file id with optional time and submission parameters. - * Not supported. Already done by Ampache if plugin enabled. - */ - public static function scrobble($input) - { - self::check_version($input, "1.5.0"); + if (AmpConfig::get('share')) { + $share = new Share($id); + if ($share->id > 0) { + $expires = $share->expire_days; + if (isset($input['expires'])) { + // Parse as a string to work on 32-bit computers + $expires = $input['expires']; + if (strlen($expires) > 3) { + $expires = intval(substr($expires, 0, - 3)); + } + if ($expires > 0) { + $expires = ($expires - $share->creation_date) / 86400; + $expires = ceil($expires); + } + } + + $data = array( + 'max_counter' => $share->max_counter, + 'expire' => $expires, + 'allow_stream' => $share->allow_stream, + 'allow_download' => $share->allow_download, + 'description' => $description ?: $share->description, + ); + if ($share->update($data)) { + $r = Subsonic_XML_Data::createSuccessResponse(); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED); + } - // Ignore error to not break clients - $r = Subsonic_XML_Data::createSuccessResponse(); self::apiOutput($input, $r); } @@ -1235,13 +1413,54 @@ class Subsonic_Api * createUser * Create a new user. * Takes the username, password and email with optional roles in parameters. - * Not supported. */ public static function createuser($input) { self::check_version($input, "1.1.0"); - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + $username = self::check_parameter($input, 'username'); + $password = self::check_parameter($input, 'password'); + $email = self::check_parameter($input, 'email'); + $ldapAuthenticated = $input['ldapAuthenticated']; + $adminRole = ($input['adminRole'] == 'true'); + //$settingsRole = $input['settingsRole']; + //$streamRole = $input['streamRole']; + //$jukeboxRole = $input['jukeboxRole']; + $downloadRole = ($input['downloadRole'] == 'true'); + $uploadRole = ($input['uploadRole'] == 'true'); + //$playlistRole = $input['playlistRole']; + $coverArtRole = ($input['coverArtRole'] == 'true'); + //$commentRole = $input['commentRole']; + //$podcastRole = $input['podcastRole']; + $shareRole = ($input['shareRole'] == 'true'); + + if (Access::check('interface', 100)) { + $access = 25; + if ($adminRole) { + $access = 100; + } elseif ($coverArtRole) { + $access = 75; + } + $password = self::decrypt_password($password); + $user_id = User::create($username, $username, $email, null, $password, $access); + if ($user_id > 0) { + if ($downloadRole) { + Preference::update('download', $user_id, '1'); + } + if ($uploadRole) { + Preference::update('allow_upload', $user_id, '1'); + } + if ($shareRole) { + Preference::update('share', $user_id, '1'); + } + $r = Subsonic_XML_Data::createSuccessResponse(); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED); + } + self::apiOutput($input, $r); } @@ -1249,13 +1468,24 @@ class Subsonic_Api * deleteUser * Delete an existing user. * Takes the username in parameter. - * Not supported. */ public static function deleteuser($input) { self::check_version($input, "1.3.0"); - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + $username = self::check_parameter($input, 'username'); + if (Access::check('interface', 100)) { + $user = User::get_from_username($username); + if ($user->id) { + $user->delete(); + $r = Subsonic_XML_Data::createSuccessResponse(); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED); + } + self::apiOutput($input, $r); } @@ -1263,16 +1493,284 @@ class Subsonic_Api * changePassword * Change the password of an existing user. * Takes the username with new password in parameters. - * Not supported. */ public static function changepassword($input) { self::check_version($input, "1.1.0"); - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + $username = self::check_parameter($input, 'username'); + $password = self::check_parameter($input, 'password'); + $password = self::decrypt_password($password); + + if ($GLOBALS['user']->username == $username || Access::check('interface', 100)) { + $user = User::get_from_username($username); + if ($user->id) { + $user->update_password($password); + $r = Subsonic_XML_Data::createSuccessResponse(); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED); + } self::apiOutput($input, $r); } + /** + * jukeboxControl + * Control the jukebox. + * Takes the action with optional index, offset, song id and volume gain in parameters. + * Not supported. + */ + public static function jukeboxcontrol($input) + { + self::check_version($input, "1.2.0"); + $action = self::check_parameter($input, 'action'); + $id = $input['id']; + $gain = $input['gain']; + + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + debug_event('subsonic', 'Using Localplay controller: ' . AmpConfig::get('localplay_controller'), 5); + $localplay = new Localplay(AmpConfig::get('localplay_controller')); + + if ($localplay->connect()) { + $ret = false; + switch ($action) { + case 'get': + case 'status': + $ret = true; + break; + case 'start': + $ret = $localplay->play(); + break; + case 'stop': + $ret = $localplay->stop(); + break; + case 'skip': + if (isset($input['index'])) { + if ($localplay->skip($input['index'])) + $ret = $localplay->play(); + } elseif (isset($input['offset'])) { + debug_event('subsonic', 'Skip with offset is not supported on JukeboxControl.', 5); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM); + } + break; + case 'set': + $localplay->delete_all(); + case 'add': + if ($id) { + if (!is_array($id)) { + $rid = array(); + $rid[] = $id; + $id = $rid; + } + + foreach ($id as $i) { + $url = null; + if (Subsonic_XML_Data::isSong($i)) { + $url = Song::generic_play_url('song', Subsonic_XML_Data::getAmpacheId($i), '', 'api'); + } elseif (Subsonic_XML_Data::isVideo($i)) { + $url = Song::generic_play_url('video', Subsonic_XML_Data::getAmpacheId($i), '', 'api'); + } + + if ($url) { + debug_event('subsonic', 'Adding ' . $url, 5); + $stream = array(); + $stream['url'] = $url; + $ret = $localplay->add_url(new Stream_URL($stream)); + } + } + } + break; + case 'clear': + $ret = $localplay->delete_all(); + break; + case 'remove': + if (isset($input['index'])) { + $ret = $localplay->delete_track($input['index']); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM); + } + break; + case 'shuffle': + $ret = $localplay->random(true); + break; + case 'setGain': + $ret = $localplay->volume_set($gain * 100); + break; + } + + if ($ret) { + $r = Subsonic_XML_Data::createSuccessResponse(); + if ($action == 'get') { + Subsonic_XML_Data::addJukeboxPlaylist($r, $localplay); + } else { + Subsonic_XML_Data::createJukeboxStatus($r, $localplay); + } + } + } + + self::apiOutput($input, $r); + } + + /** + * scrobble + * Scrobbles a given music file on last.fm. + * Takes the file id with optional time and submission parameters. + */ + public static function scrobble($input) + { + self::check_version($input, "1.5.0"); + + $id = self::check_parameter($input, 'id'); + //$time = $input['time']; + //$submission = $input['submission']; + + if (!is_array($id)) { + $rid = array(); + $rid[] = $id; + $id = $rid; + } + + foreach ($id as $i) { + $aid = Subsonic_XML_Data::getAmpacheId($i); + if (Subsonic_XML_Data::isVideo($i)) { + $type = 'video'; + } else { + $type = 'song'; + } + + $media = new $type($aid); + $media->format(); + $GLOBALS['user']->save_mediaplay($GLOBALS['user'], $media); + } + + $r = Subsonic_XML_Data::createSuccessResponse(); + self::apiOutput($input, $r); + } + + /** + * getLyrics + * Searches and returns lyrics for a given song. + * Takes the optional artist and title in parameters. + */ + public static function getlyrics($input) + { + self::check_version($input, "1.2.0"); + + $artist = $input['artist']; + $title = $input['title']; + + if (!$artist && !$title) { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM); + } else { + $search = array(); + $search['limit'] = 1; + $search['offset'] = 0; + $search['type'] = "song"; + + $i = 0; + if ($artist) { + $search['rule_'.$i.'_input'] = $artist; + $search['rule_'.$i.'_operator'] = 5; + $search['rule_'.$i.''] = "artist"; + ++$i; + } + if ($title) { + $search['rule_'.$i.'_input'] = $title; + $search['rule_'.$i.'_operator'] = 5; + $search['rule_'.$i.''] = "title"; + ++$i; + } + + $query = new Search(null, 'song'); + $songs = $query->run($search); + + $r = Subsonic_XML_Data::createSuccessResponse(); + if (count($songs) > 0) { + Subsonic_XML_Data::addLyrics($r, $artist, $title, $songs[0]); + } + } + + self::apiOutput($input, $r); + } + + /** + * getArtistInfo + * Returns artist info with biography, image URLs and similar artists, using data from last.fm. + * Takes artist id in parameter with optional similar artist count and if not present similar artist should be returned. + */ + public static function getartistinfo($input) + { + $id = self::check_parameter($input, 'id'); + $count = $input['count'] ?: 20; + $includeNotPresent = ($input['includeNotPresent'] === "true"); + + if (Subsonic_XML_Data::isArtist($id)) { + $artist_id = Subsonic_XML_Data::getAmpacheId($id); + $info = Recommendation::get_artist_info($artist_id); + $similars = Recommendation::get_artists_like($artist_id, $count, !$includeNotPresent); + $r = Subsonic_XML_Data::createSuccessResponse(); + Subsonic_XML_Data::addArtistInfo($r, $info, $similars); + } else { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } + + self::apiOutput($input, $r); + } + + /** + * getArtistInfo2 + * See getArtistInfo. + */ + public static function getartistinfo2($input) + { + return self::getartistinfo($input); + } + + /** + * getSimilarSongs + * Returns a random collection of songs from the given artist and similar artists, using data from last.fm. Typically used for artist radio features. + * Takes song/album/artist id in parameter with optional similar songs count. + */ + public static function getsimilarsongs($input) + { + $id = self::check_parameter($input, 'id'); + $count = $input['count'] ?: 50; + + $songs = null; + if (Subsonic_XML_Data::isArtist($id)) { + // TODO: support similar songs for artists + } elseif (Subsonic_XML_Data::isAlbum($id)) { + // TODO: support similar songs for albums + } elseif (Subsonic_XML_Data::isSong($id)) { + if (AmpConfig::get('show_similar')) { + $songs = Recommendation::get_songs_like(Subsonic_XML_Data::getAmpacheId($id)); + } + } + + if ($songs === null) { + $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); + } else { + $r = Subsonic_XML_Data::createSuccessResponse(); + Subsonic_XML_Data::addSimilarSongs($r, $songs); + } + + self::apiOutput($input, $r); + } + + /** + * getSimilarSongs2 + * See getSimilarSongs. + */ + public static function getsimilarsongs2($input) + { + return self::getsimilarsongs($input); + } + + /**** CURRENT UNSUPPORTED FUNCTIONS ****/ + /** * getPodcasts * Get all podcast channels. @@ -1357,20 +1855,6 @@ class Subsonic_Api self::apiOutput($input, $r); } - /** - * jukeboxControl - * Control the jukebox. - * Takes the action with optional index, offset, song id and volume gain in parameters. - * Not supported. - */ - public static function jukeboxcontrol($input) - { - self::check_version($input, "1.2.0"); - - $r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND); - self::apiOutput($input, $r); - } - /** * getChatMessages * Get the current chat messages. diff --git a/sources/lib/class/subsonic_xml_data.class.php b/sources/lib/class/subsonic_xml_data.class.php index dddc885..675b8fd 100644 --- a/sources/lib/class/subsonic_xml_data.class.php +++ b/sources/lib/class/subsonic_xml_data.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,7 +30,7 @@ */ class Subsonic_XML_Data { - const API_VERSION = "1.10.1"; + const API_VERSION = "1.11.0"; const SSERROR_GENERIC = 0; const SSERROR_MISSINGPARAM = 10; @@ -45,6 +45,8 @@ class Subsonic_XML_Data const AMPACHEID_ARTIST = 100000000; const AMPACHEID_ALBUM = 200000000; const AMPACHEID_SONG = 300000000; + const AMPACHEID_SMARTPL = 400000000; + const AMPACHEID_VIDEO = 500000000; /** * constructor @@ -70,6 +72,16 @@ class Subsonic_XML_Data return $id + Subsonic_XML_Data::AMPACHEID_SONG; } + public static function getSmartPlId($id) + { + return $id + Subsonic_XML_Data::AMPACHEID_SMARTPL; + } + + public static function getVideoId($id) + { + return $id + Subsonic_XML_Data::AMPACHEID_VIDEO; + } + public static function getAmpacheId($id) { return ($id % Subsonic_XML_Data::AMPACHEID_ARTIST); @@ -99,6 +111,16 @@ class Subsonic_XML_Data return ($id >= Subsonic_XML_Data::AMPACHEID_SONG); } + public static function isSmartPlaylist($id) + { + return ($id >= Subsonic_XML_Data::AMPACHEID_SMARTPL); + } + + public static function isVideo($id) + { + return ($id >= Subsonic_XML_Data::AMPACHEID_VIDEO); + } + public static function createFailedResponse($version = "") { $response = self::createResponse($version); @@ -118,6 +140,7 @@ class Subsonic_XML_Data if (empty($version)) $version = Subsonic_XML_Data::API_VERSION; $response = new SimpleXMLElement(''); $response->addAttribute('xmlns', 'http://subsonic.org/restapi'); + $response->addAttribute('type', 'ampache'); $response->addAttribute('version', $version); return $response; } @@ -135,7 +158,7 @@ class Subsonic_XML_Data * * @param SimpleXMLElement $xml Parent node * @param integer $code Error code - * @param string $string Error message + * @param string $message Error message */ public static function setError($xml, $code, $message = "") { @@ -181,7 +204,7 @@ class Subsonic_XML_Data public static function addArtistsIndexes($xml, $artists, $lastModified) { $xindexes = $xml->addChild('indexes'); - $xindexes->addAttribute('lastModified', $lastModified * 1000); + $xindexes->addAttribute('lastModified', number_format($lastModified * 1000, 0, '.', '')); self::addArtists($xindexes, $artists); } @@ -217,7 +240,9 @@ class Subsonic_XML_Data } } - self::addArtist($xlastcat, $artist, $extra); + if ($xlastcat != null) { + self::addArtist($xlastcat, $artist, $extra); + } } } @@ -246,7 +271,7 @@ class Subsonic_XML_Data public static function addAlbumList($xml, $albums, $elementName="albumList") { - $xlist = $xml->addChild($elementName); + $xlist = $xml->addChild(htmlspecialchars($elementName)); foreach ($albums as $id) { $album = new Album($id); self::addAlbum($xlist, $album); @@ -255,10 +280,10 @@ class Subsonic_XML_Data public static function addAlbum($xml, $album, $songs=false, $elementName="album") { - $xalbum = $xml->addChild($elementName); + $xalbum = $xml->addChild(htmlspecialchars($elementName)); $xalbum->addAttribute('id', self::getAlbumId($album->id)); $xalbum->addAttribute('album', $album->name); - $xalbum->addAttribute('title', self::formatAlbum($album)); + $xalbum->addAttribute('title', self::formatAlbum($album, $elementName === "album")); $xalbum->addAttribute('name', $album->name); $xalbum->addAttribute('isDir', 'true'); $album->format(); @@ -270,10 +295,24 @@ class Subsonic_XML_Data $xalbum->addAttribute('artistId', self::getArtistId($album->artist_id)); $xalbum->addAttribute('parent', self::getArtistId($album->artist_id)); $xalbum->addAttribute('artist', $album->artist_name); + if ($album->year > 0) { + $xalbum->addAttribute('year', $album->year); + } + if (count($album->tags) > 0) { + $tag_values = array_values($album->tags); + $tag = array_shift($tag_values); + $xalbum->addAttribute('genre', $tag['name']); + } $rating = new Rating($album->id, "album"); - $rating_value = $rating->get_average_rating(); - $xalbum->addAttribute('averageRating', ($rating_value) ? $rating_value : 0); + $user_rating = $rating->get_user_rating(); + if ($user_rating > 0) { + $xalbum->addAttribute('userRating', ceil($user_rating)); + } + $avg_rating = $rating->get_average_rating(); + if ($avg_rating > 0) { + $xalbum->addAttribute('averageRating', ceil($avg_rating)); + } if ($songs) { $allsongs = $album->get_songs(); @@ -291,7 +330,7 @@ class Subsonic_XML_Data public static function createSong($xml, $song, $elementName='song') { - $xsong = $xml->addChild($elementName); + $xsong = $xml->addChild(htmlspecialchars($elementName)); $xsong->addAttribute('id', self::getSongId($song->id)); $xsong->addAttribute('parent', self::getAlbumId($song->album)); //$xsong->addAttribute('created', ); @@ -303,11 +342,20 @@ class Subsonic_XML_Data $xsong->addAttribute('albumId', self::getAlbumId($album->id)); $xsong->addAttribute('album', $album->name); $artist = new Artist($song->artist); - $xsong->addAttribute('artistId', self::getArtistId($album->id)); + $xsong->addAttribute('artistId', self::getArtistId($song->artist)); $xsong->addAttribute('artist', $artist->name); $xsong->addAttribute('coverArt', self::getAlbumId($album->id)); $xsong->addAttribute('duration', $song->time); $xsong->addAttribute('bitRate', intval($song->bitrate / 1000)); + $rating = new Rating($song->id, "song"); + $user_rating = $rating->get_user_rating(); + if ($user_rating > 0) { + $xsong->addAttribute('userRating', ceil($user_rating)); + } + $avg_rating = $rating->get_average_rating(); + if ($avg_rating > 0) { + $xsong->addAttribute('averageRating', ceil($avg_rating)); + } if ($song->track > 0) { $xsong->addAttribute('track', $song->track); } @@ -326,9 +374,9 @@ class Subsonic_XML_Data // Set transcoding information if required $transcode_cfg = AmpConfig::get('transcode'); - $transcode_mode = AmpConfig::get('transcode_' . $song->type); - if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && $transcode_mode == 'required')) { - $transcode_settings = $song->get_transcode_settings(null); + $valid_types = Song::get_stream_types_for_type($song->type, 'api'); + if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) { + $transcode_settings = $song->get_transcode_settings(null, 'api'); if ($transcode_settings) { $transcode_type = $transcode_settings['format']; $xsong->addAttribute('transcodedSuffix', $transcode_type); @@ -339,14 +387,14 @@ class Subsonic_XML_Data return $xsong; } - private static function formatAlbum($album) + private static function formatAlbum($album, $checkDisk = true) { $name = $album->name; if ($album->year > 0) { $name .= " [" . $album->year . "]"; } - if ($album->disk) { + if (($checkDisk || !AmpConfig::get('album_group')) && $album->disk) { $name .= " [" . T_('Disk') . " " . $album->disk . "]"; } @@ -359,7 +407,7 @@ class Subsonic_XML_Data $xdir->addAttribute('id', self::getArtistId($artist->id)); $xdir->addAttribute('name', $artist->name); - $allalbums = $artist->get_albums(null, true); + $allalbums = $artist->get_albums(); foreach ($allalbums as $id) { $album = new Album($id); self::addAlbum($xdir, $album, false, "child"); @@ -370,14 +418,20 @@ class Subsonic_XML_Data { $xdir = $xml->addChild('directory'); $xdir->addAttribute('id', self::getAlbumId($album->id)); - $xdir->addAttribute('name', self::formatAlbum($album)); + $xdir->addAttribute('name', self::formatAlbum($album, false)); $album->format(); - //$xdir->addAttribute('parent', self::getArtistId($album->artist_id)); + if ($album->artist_id) { + $xdir->addAttribute('parent', self::getArtistId($album->artist_id)); + } - $allsongs = $album->get_songs(); - foreach ($allsongs as $id) { - $song = new Song($id); - self::addSong($xdir, $song, "child"); + $disc_ids = $album->get_group_disks_ids(); + foreach ($disc_ids as $id) { + $disc = new Album($id); + $allsongs = $disc->get_songs(); + foreach ($allsongs as $id) { + $song = new Song($id); + self::addSong($xdir, $song, "child"); + } } } @@ -387,23 +441,65 @@ class Subsonic_XML_Data foreach ($tags as $tag) { $otag = new Tag($tag['id']); - $xgenres->addChild('genre', $otag->name); + $xgenres->addChild('genre', htmlspecialchars($otag->name)); } } - public static function addVideos($xml) + public static function addVideos($xml, $videos) { - // Not supported yet - $xml->addChild('videos'); + $xvideos = $xml->addChild('videos'); + foreach ($videos as $video) { + $video->format(); + self::addVideo($xvideos, $video); + } } - public static function addPlaylists($xml, $playlists) + public static function addVideo($xml, $video) + { + $xvideo = $xml->addChild('video'); + $xvideo->addAttribute('id', self::getVideoId($video->id)); + $xvideo->addAttribute('title', $video->f_full_title); + $xvideo->addAttribute('isDir', 'false'); + $xvideo->addAttribute('coverArt', self::getVideoId($video->id)); + $xvideo->addAttribute('isVideo', 'true'); + $xvideo->addAttribute('type', 'video'); + $xvideo->addAttribute('duration', $video->time); + if ($video->year > 0) { + $xvideo->addAttribute('year', $video->year); + } + $tags = Tag::get_object_tags('video', $video->id); + if (count($tags) > 0) $xvideo->addAttribute('genre', $tags[0]['name']); + $xvideo->addAttribute('size', $video->size); + $xvideo->addAttribute('suffix', $video->type); + $xvideo->addAttribute('contentType', $video->mime); + // Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients + $path = basename($video->file); + $xvideo->addAttribute('path', $path); + + // Set transcoding information if required + $transcode_cfg = AmpConfig::get('transcode'); + $valid_types = Song::get_stream_types_for_type($video->type, 'api'); + if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) { + $transcode_settings = $video->get_transcode_settings(null, 'api'); + if ($transcode_settings) { + $transcode_type = $transcode_settings['format']; + $xvideo->addAttribute('transcodedSuffix', $transcode_type); + $xvideo->addAttribute('transcodedContentType', Video::type_to_mime($transcode_type)); + } + } + } + + public static function addPlaylists($xml, $playlists, $smartplaylists = array()) { $xplaylists = $xml->addChild('playlists'); foreach ($playlists as $id) { $playlist = new Playlist($id); self::addPlaylist($xplaylists, $playlist); } + foreach ($smartplaylists as $id) { + $smartplaylist = new Search($id, 'song'); + self::addSmartPlaylist($xplaylists, $smartplaylist); + } } public static function addPlaylist($xml, $playlist, $songs=false) @@ -427,6 +523,24 @@ class Subsonic_XML_Data } } + public static function addSmartPlaylist($xml, $playlist, $songs=false) + { + $xplaylist = $xml->addChild('playlist'); + $xplaylist->addAttribute('id', self::getSmartPlId($playlist->id)); + $xplaylist->addAttribute('name', $playlist->name); + $user = new User($playlist->user); + $xplaylist->addAttribute('owner', $user->username); + $xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false"); + + if ($songs) { + $allitems = $playlist->get_items(); + foreach ($allitems as $item) { + $song = new Song($item['object_id']); + self::addSong($xplaylist, $song, "entry"); + } + } + } + public static function addRandomSongs($xml, $songs) { $xsongs = $xml->addChild('randomSongs'); @@ -458,7 +572,7 @@ class Subsonic_XML_Data public static function addSearchResult($xml, $artists, $albums, $songs, $elementName = "searchResult2") { - $xresult = $xml->addChild($elementName); + $xresult = $xml->addChild(htmlspecialchars($elementName)); foreach ($artists as $id) { $artist = new Artist($id); self::addArtist($xresult, $artist); @@ -475,7 +589,7 @@ class Subsonic_XML_Data public static function addStarred($xml, $artists, $albums, $songs, $elementName="starred") { - $xstarred = $xml->addChild($elementName); + $xstarred = $xml->addChild(htmlspecialchars($elementName)); foreach ($artists as $id) { $artist = new Artist($id); @@ -498,11 +612,11 @@ class Subsonic_XML_Data $xuser = $xml->addChild('user'); $xuser->addAttribute('username', $user->username); $xuser->addAttribute('email', $user->email); - $xuser->addAttribute('scrobblingEnabled', 'false'); + $xuser->addAttribute('scrobblingEnabled', 'true'); $isManager = ($user->access >= 75); $isAdmin = ($user->access >= 100); $xuser->addAttribute('adminRole', $isAdmin ? 'true' : 'false'); - $xuser->addAttribute('settingsRole', $isAdmin ? 'true' : 'false'); + $xuser->addAttribute('settingsRole', 'true'); $xuser->addAttribute('downloadRole', Preference::get_by_user($user->id, 'download') ? 'true' : 'false'); $xuser->addAttribute('playlistRole', 'true'); $xuser->addAttribute('coverArtRole', $isManager ? 'true' : 'false'); @@ -510,7 +624,7 @@ class Subsonic_XML_Data $xuser->addAttribute('podcastRole', 'false'); $xuser->addAttribute('streamRole', 'true'); $xuser->addAttribute('jukeboxRole', 'false'); - $xuser->addAttribute('shareRole', 'false'); + $xuser->addAttribute('shareRole', Preference::get_by_user($user->id, 'share') ? 'true' : 'false'); } public static function addUsers($xml, $users) @@ -535,14 +649,14 @@ class Subsonic_XML_Data { $xradios = $xml->addChild('internetRadioStations'); foreach ($radios as $id) { - $radio = new Radio($id); + $radio = new Live_Stream($id); self::addRadio($xradios, $radio); } } public static function addShare($xml, $share) { - $xshare = $xml->addChild('share '); + $xshare = $xml->addChild('share'); $xshare->addAttribute('id', $share->id); $xshare->addAttribute('url', $share->public_url); $xshare->addAttribute('description', $share->description); @@ -588,4 +702,79 @@ class Subsonic_XML_Data } } } + + public static function addJukeboxPlaylist($xml, Localplay $localplay) + { + $xjbox = self::createJukeboxStatus($xml, $localplay, 'jukeboxPlaylist'); + $tracks = $localplay->get(); + foreach ($tracks as $track) { + if ($track['oid']) { + $song = new Song($track['oid']); + self::createSong($xjbox, $song, 'entry'); + } + } + } + + public static function createJukeboxStatus($xml, Localplay $localplay, $elementName = 'jukeboxStatus') + { + $xjbox = $xml->addChild($elementName); + $status = $localplay->status(); + $xjbox->addAttribute('currentIndex', 0); // Not supported + $xjbox->addAttribute('playing', ($status['state'] == 'play') ? 'true' : 'false'); + $xjbox->addAttribute('gain', $status['volume']); + $xjbox->addAttribute('position', 0); // Not supported + + return $xjbox; + } + + public static function addLyrics($xml, $artist, $title, $song_id) + { + $song = new Song($song_id); + $song->format(); + $song->fill_ext_info(); + $lyrics = $song->get_lyrics(); + + if ($lyrics && $lyrics['text']) { + $text = preg_replace('/\/i', "\n", $lyrics['text']); + $text = str_replace("\r", '', $text); + $xlyrics = $xml->addChild("lyrics", $text); + if ($artist) { + $xlyrics->addAttribute("artist", $artist); + } + if ($title) { + $xlyrics->addAttribute("title", $title); + } + } + } + + public static function addArtistInfo($xml, $info, $similars) + { + $artist = new Artist($info['id']); + + $xartist = $xml->addChild("artistInfo"); + $xartist->addChild("biography", trim($info['summary'])); + $xartist->addChild("musicBrainzId", $artist->mbid); + //$xartist->addChild("lastFmUrl", ""); + $xartist->addChild("smallImageUrl", htmlentities($info['smallphoto'])); + $xartist->addChild("mediumImageUrl", htmlentities($info['mediumphoto'])); + $xartist->addChild("largeImageUrl", htmlentities($info['largephoto'])); + + foreach ($similars as $similar) { + $xsimilar = $xartist->addChild("similarArtist"); + $xsimilar->addAttribute("id", ($similar['id'] !== null ? self::getArtistId($similar['id']) : "-1")); + $xsimilar->addAttribute("name", $similar['name']); + } + } + + public static function addSimilarSongs($xml, $similar_songs) + { + $xsimilar = $xml->addChild("similarSongs"); + foreach ($similar_songs as $similar_song) { + $song = new Song($similar_song['id']); + $song->format(); + if ($song->id) { + self::addSong($xsimilar, $song); + } + } + } } diff --git a/sources/lib/class/tag.class.php b/sources/lib/class/tag.class.php index a318432..b83151c 100644 --- a/sources/lib/class/tag.class.php +++ b/sources/lib/class/tag.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -26,10 +26,11 @@ * This class hnadles all of the tag relation operations * */ -class Tag extends database_object +class Tag extends database_object implements library_item { public $id; public $name; + public $is_hidden; /** * constructor @@ -90,7 +91,9 @@ class Tag extends database_object { if (!is_array($ids) OR !count($ids)) { return false; } - $type = self::validate_type($type); + if (!Core::is_library_item($type)) + return false; + $idlist = '(' . implode(',',$ids) . ')'; $sql = "SELECT `tag_map`.`id`,`tag_map`.`tag_id`, `tag`.`name`,`tag_map`.`object_id`,`tag_map`.`user` FROM `tag` " . @@ -128,8 +131,8 @@ class Tag extends database_object */ public static function add($type, $id, $value, $user=false) { - // Validate the tag type - if (!self::validate_type($type)) { return false; } + if (!Core::is_library_item($type)) + return false; if (!is_numeric($id)) { return false; } @@ -166,10 +169,8 @@ class Tag extends database_object { if (!strlen($value)) { return false; } - $value = Dba::escape($value); - - $sql = "REPLACE INTO `tag` SET `name`='$value'"; - Dba::write($sql); + $sql = "REPLACE INTO `tag` SET `name` = ?"; + Dba::write($sql, array($value)); $insert_id = Dba::insert_id(); parent::add_to_cache('tag_name', $value, $insert_id); @@ -182,18 +183,88 @@ class Tag extends database_object * update * Update the name of the tag */ - public function update($name) + public function update(array $data) { //debug_event('tag.class', 'Updating tag {'.$this->id.'} with name {'.$name.'}...', '5'); - if (!strlen($name)) { return false; } - - $name = Dba::escape($name); + if (!strlen($data['name'])) { return false; } $sql = 'UPDATE `tag` SET `name` = ? WHERE `id` = ?'; - Dba::write($sql, array($name, $this->id)); + Dba::write($sql, array($data[name], $this->id)); + + if ($data['edit_tags']) { + $tag_names = explode(',', $data['edit_tags']); + foreach ($tag_names as $tag) { + $merge_to = Tag::construct_from_name($tag); + if ($merge_to->id == 0) { + Tag::add_tag($tag); + $merge_to = Tag::construct_from_name($tag); + } + $this->merge($merge_to->id, $data['merge_persist'] == '1'); + } + if ($data['keep_existing'] != '1') { + $sql = "DELETE FROM `tag_map` WHERE `tag_map`.`tag_id` = ? "; + Dba::write($sql, array($this->id)); + if ($data['merge_persist'] != '1') { + $this->delete(); + } else { + $sql = "UPDATE `tag` SET `is_hidden` = true WHERE `tag`.`tag` = ? "; + Dba::write($sql, array($this->id)); + } + } + } + return $this->id; } // add_tag + /** + * merge + * merges this tag to another one. + */ + public function merge($merge_to, $is_persistent) + { + if ($this->id != $merge_to) { + debug_event('tag', 'Merging tag ' . $this->id . ' into ' . $merge_to . ')...', '5'); + + $sql = "INSERT INTO `tag_map` (`tag_id`,`user`,`object_type`,`object_id`) " . + "SELECT ?,`user`,`object_type`,`object_id` " . + "FROM `tag_map` AS `tm`" . + "WHERE `tm`.`tag_id` = ? AND NOT EXISTS ( " . + "SELECT 1 FROM `tag_map` ". + "WHERE `tag_map`.`tag_id` = ? " . + "AND `tag_map`.`object_id` = `tm`.`object_id` " . + "AND `tag_map`.`object_type` = `tm`.`object_type` " . + "AND `tag_map`.`user` = `tm`.`user`" . + ")"; + Dba::write($sql, array($merge_to, $this->id, $merge_to)); + if ($is_persistent) { + $sql = 'INSERT INTO `tag_merge` (`tag_id`, `merged_to`) VALUES (?, ?)'; + Dba::write($sql, array($this->id, $merge_to)); + } + } + } + + /** + * get_merged_tags + * Get merged tags to this tag. + */ + public function get_merged_tags() + { + $sql = "SELECT `tag`.`id`, `tag`.`name`" . + "FROM `tag_merge` " . + "INNER JOIN `tag` ON `tag`.`id` = `tag_merge`.`merged_to` ". + "WHERE `tag_merge`.`tag_id` = ? " . + "ORDER BY `tag`.`name` "; + + $db_results = Dba::read($sql, array($this->id)); + + $results = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $results[$row['id']] = array('id'=>$row['id'], 'name'=>$row['name']); + } + + return $results; + } + /** * add_tag_map * This adds a specific tag to the map for specified object @@ -202,14 +273,23 @@ class Tag extends database_object { $uid = ($user == '') ? intval($GLOBALS['user']->id) : intval($user); $tag_id = intval($tag_id); - if (!self::validate_type($type)) { return false; } + if (!Core::is_library_item($type)) + return false; $id = intval($object_id); if (!$tag_id || !$id) { return false; } - $sql = "INSERT INTO `tag_map` (`tag_id`,`user`,`object_type`,`object_id`) " . - "VALUES ('$tag_id','$uid','$type','$id')"; - Dba::write($sql); + // If tag merged to another one, add reference to the merge destination + $parent = new Tag($tag_id); + $merges = $parent->get_merged_tags(); + if ($parent->is_hidden == false) { + $merges[] = array('id' => $parent->id, 'name' => $parent->name); + } + foreach ($merges as $tag) { + $sql = "INSERT INTO `tag_map` (`tag_id`,`user`,`object_type`,`object_id`) " . + "VALUES (?, ?, ?, ?)"; + Dba::write($sql, array($tag['id'], $uid, $type, $id)); + } $insert_id = Dba::insert_id(); parent::add_to_cache('tag_map_' . $type,$insert_id,array('tag_id'=>$tag_id,'user'=>$uid,'object_type'=>$type,'object_id'=>$id)); @@ -242,9 +322,18 @@ class Tag extends database_object "WHERE `tag_map`.`object_type`='video' AND `video`.`id` IS NULL"; Dba::write($sql); + $sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `tvshow` ON `tvshow`.`id`=`tag_map`.`object_id` " . + "WHERE `tag_map`.`object_type`='tvshow' AND `tvshow`.`id` IS NULL"; + Dba::write($sql); + + $sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `tvshow_season` ON `tvshow_season`.`id`=`tag_map`.`object_id` " . + "WHERE `tag_map`.`object_type`='tvshow_season' AND `tvshow_season`.`id` IS NULL"; + Dba::write($sql); + // Now nuke the tags themselves $sql = "DELETE FROM `tag` USING `tag` LEFT JOIN `tag_map` ON `tag`.`id`=`tag_map`.`tag_id` " . - "WHERE `tag_map`.`id` IS NULL"; + "WHERE `tag_map`.`id` IS NULL " . + "AND NOT EXISTS (SELECT 1 FROM `tag_merge` where `tag_merge`.`tag_id` = `tag`.`id`)"; Dba::write($sql); } @@ -255,11 +344,15 @@ class Tag extends database_object */ public function delete() { - $sql = "DELETE FROM `tag_map` WHERE `tag_map`.`tag_id`='".$this->id."'"; - Dba::write($sql); + $sql = "DELETE FROM `tag_map` WHERE `tag_map`.`tag_id` = ?"; + Dba::write($sql, array($this->id)); - $sql = "DELETE FROM `tag` WHERE `tag`.`id`='".$this->id."'"; - Dba::write($sql); + $sql = "DELETE FROM `tag_merge` " . + "WHERE `tag_merge`.`tag_id` = ?"; + Dba::write($sql, array($this->id)); + + $sql = "DELETE FROM `tag` WHERE `tag`.`id` = ? "; + Dba::write($sql, array($this->id, $this->id)); // Call the garbage collector to clean everything Tag::gc(); @@ -277,9 +370,8 @@ class Tag extends database_object return parent::get_from_cache('tag_name',$value); } - $value = Dba::escape($value); - $sql = "SELECT * FROM `tag` WHERE `name`='$value'"; - $db_results = Dba::read($sql); + $sql = "SELECT * FROM `tag` WHERE `name` = ?"; + $db_results = Dba::read($sql, array($value)); $results = Dba::fetch_assoc($db_results); @@ -296,15 +388,12 @@ class Tag extends database_object */ public static function tag_map_exists($type,$object_id,$tag_id,$user) { - if (!self::validate_type($type)) { return false; } + if (!Core::is_library_item($type)) + return false; - $object_id = Dba::escape($object_id); - $tag_id = Dba::escape($tag_id); - $user = Dba::escape($user); - $type = Dba::escape($type); - - $sql = "SELECT * FROM `tag_map` WHERE `tag_id`='$tag_id' AND `user`='$user' AND `object_id`='$object_id' AND `object_type`='$type'"; - $db_results = Dba::read($sql); + $sql = "SELECT * FROM `tag_map` LEFT JOIN `tag` ON `tag`.`id` = `tag_map`.`tag_id` LEFT JOIN `tag_merge` ON `tag`.`id`=`tag_merge`.`tag_id` " . + "WHERE (`tag_map`.`tag_id` = ? OR `tag_map`.`tag_id` = `tag_merge`.`merged_to`) AND `tag_map`.`user` = ? AND `tag_map`.`object_id` = ? AND `tag_map`.`object_type` = ?"; + $db_results = Dba::read($sql, array($tag_id, $user, $object_id, $type)); $results = Dba::fetch_assoc($db_results); @@ -318,8 +407,8 @@ class Tag extends database_object */ public static function get_top_tags($type, $object_id, $limit = 10) { - //debug_event('tag.class', 'Getting tags for type {'.$type.'} object_id {'.$object_id.'}...', '5'); - if (!self::validate_type($type)) { return false; } + if (!Core::is_library_item($type)) + return array(); $object_id = intval($object_id); @@ -348,16 +437,15 @@ class Tag extends database_object */ public static function get_object_tags($type, $id) { - if (!self::validate_type($type)) { return array(); } - - $id = Dba::escape($id); + if (!Core::is_library_item($type)) + return false; $sql = "SELECT `tag_map`.`id`, `tag`.`name`, `tag_map`.`user` FROM `tag` " . "LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` " . - "WHERE `tag_map`.`object_type`='$type' AND `tag_map`.`object_id`='$id'"; + "WHERE `tag_map`.`object_type` = ? AND `tag_map`.`object_id` = ?"; $results = array(); - $db_results = Dba::read($sql); + $db_results = Dba::read($sql, array($type, $id)); while ($row = Dba::fetch_assoc($db_results)) { $results[] = $row; @@ -372,7 +460,8 @@ class Tag extends database_object */ public static function get_tag_objects($type,$tag_id,$count='',$offset='') { - if (!self::validate_type($type)) { return array(); } + if (!Core::is_library_item($type)) + return false; $limit_sql = ""; if ($count) { @@ -402,7 +491,7 @@ class Tag extends database_object * This is a non-object non type dependent function that just returns tags * we've got, it can take filters (this is used by the tag cloud) */ - public static function get_tags($limit = 0) + public static function get_tags($type = '', $limit = 0, $order = 'count') { //debug_event('tag.class.php', 'Get tags list called...', '5'); if (parent::is_cached('tags_list', 'no_name')) { @@ -412,10 +501,18 @@ class Tag extends database_object $results = array(); - $sql = "SELECT `tag_map`.`tag_id`, `tag`.`name`, COUNT(`tag_map`.`object_id`) AS `count` " . + $sql = "SELECT `tag_map`.`tag_id`, `tag`.`name`, `tag`.`is_hidden`, COUNT(`tag_map`.`object_id`) AS `count` " . "FROM `tag_map` " . "LEFT JOIN `tag` ON `tag`.`id`=`tag_map`.`tag_id` " . - "GROUP BY `tag`.`name` ORDER BY `count` DESC "; + "WHERE `tag`.`is_hidden` = false "; + if (!empty($type)) { + $sql .= "AND `tag_map`.`object_type` = '" . scrub_in($type) . "' "; + } + $order = "`" . $order . "`"; + if ($order == 'count') { + $order .= " DESC"; + } + $sql .="GROUP BY `tag`.`name` ORDER BY " . $order; if ($limit > 0) { $sql .= " LIMIT $limit"; @@ -424,7 +521,7 @@ class Tag extends database_object $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { - $results[$row['tag_id']] = array('id'=>$row['tag_id'], 'name'=>$row['name'], 'count'=>$row['count']); + $results[$row['tag_id']] = array('id'=>$row['tag_id'], 'name'=>$row['name'], 'is_hidden'=>$row['is_hidden'], 'count'=>$row['count']); } parent::add_to_cache('tags_list', 'no_name', $results); @@ -434,11 +531,11 @@ class Tag extends database_object /** * get_display - * This returns a human formated version of the tags that we are given + * This returns a csv formated version of the tags that we are given * it also takes a type so that it knows how to return it, this is used * by the formating functions of the different objects */ - public static function get_display($tags) + public static function get_display($tags, $link=false, $filter_type='') { //debug_event('tag.class.php', 'Get display tags called...', '5'); if (!is_array($tags)) { return ''; } @@ -451,7 +548,14 @@ class Tag extends database_object foreach ($value as $vid=>$v) { debug_event('tag.class.php', $vid.' = {'.$v.'}', '5'); }*/ - $results .= $value['name'] . ', '; + if ($link) { + $results .= ''; + } + $results .= $value['name']; + if ($link) { + $results .= ''; + } + $results .= ', '; } $results = rtrim($results, ', '); @@ -464,7 +568,7 @@ class Tag extends database_object * update_tag_list * Update the tags list based on commated list (ex. tag1,tag2,tag3,..) */ - public static function update_tag_list($tags_comma, $type, $object_id) + public static function update_tag_list($tags_comma, $type, $object_id, $overwrite) { debug_event('tag.class', 'Updating tags for values {'.$tags_comma.'} type {'.$type.'} object_id {'.$object_id.'}', '5'); @@ -488,7 +592,7 @@ class Tag extends database_object if ($found) { debug_event('tag.class', 'Already found. Do nothing.', '5'); unset($editedTags[$tk]); - } else { + } else if ($overwrite) { debug_event('tag.class', 'Not found in the new list. Delete it.', '5'); $ctag->remove_map($type, $object_id); } @@ -498,13 +602,40 @@ class Tag extends database_object // Look if we need to add some new tags foreach ($editedTags as $tk => $tv) { - debug_event('tag.class', 'Adding new tag {'.$tv.'}', '5'); if ($tv != '') { + debug_event('tag.class', 'Adding new tag {'.$tv.'}', '5'); Tag::add($type, $object_id, $tv, false); } } } // update_tag_list + /** + * clean_to_existing + * Clean tag list to existing tag list only + * @param array|string $tags + * @return array|string + */ + public static function clean_to_existing($tags) + { + if (is_array($tags)) { + $ar = $tags; + } else { + $ar = explode(",", $tags); + } + + $ret = array(); + foreach ($ar as $tag) { + $tag = trim($tag); + if (!empty($tag)) { + if (Tag::tag_exists($tag)) { + $ret[] = $tag; + } + } + } + + return (is_array($tags) ? $ret : implode(",", $ret)); + } + /** * count * This returns the count for the all objects associated with this tag @@ -534,30 +665,103 @@ class Tag extends database_object * remove_map * This will only remove tag maps for the current user */ - public function remove_map($type,$object_id) + public function remove_map($type, $object_id) { - if (!self::validate_type($type)) { return false; } + if (!Core::is_library_item($type)) + return false; - $sql = "DELETE FROM `tag_map` WHERE `tag_id` = ? AND `object_type` = ? AND `object_id` = ? AND `user` = ?"; - Dba::write($sql, array($this->id, $type, $object_id, $GLOBALS['user']->id)); + // TODO: Review the tag edition per user. + + $sql = "DELETE FROM `tag_map` WHERE `tag_id` = ? AND `object_type` = ? AND `object_id` = ? "; //AND `user` = ?"; + Dba::write($sql, array($this->id, $type, $object_id));//, $GLOBALS['user']->id)); return true; } // remove_map - /** - * validate_type - * This validates the type of the object the user wants to tag, we limit this to types - * we currently support - */ - public static function validate_type($type) + public function format($details = true) { - $valid_array = array('song','artist','album','video','playlist','live_stream','channel','broadcast'); - if (in_array($type,$valid_array)) { return $type; } + } - return false; + public function get_keywords() + { + $keywords = array(); + $keywords['tag'] = array('important' => true, + 'label' => T_('Tag'), + 'value' => $this->name); - } // validate_type + return $keywords; + } + + public function get_fullname() + { + return $this->name; + } + + public function get_parent() + { + return null; + } + + public function get_childrens() + { + return array(); + } + + public function search_childrens($name) + { + return array(); + } + + public function get_medias($filter_type = null) + { + $medias = array(); + if ($filter_type) { + $ids = Tag::get_tag_objects($filter_type, $this->id); + if ($ids) { + foreach ($ids as $id) { + $medias[] = array( + 'object_type' => $filter_type, + 'object_id' => $id + ); + } + } + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array(); + } + + public function get_user_owner() + { + return null; + } + + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + return null; + } + + public function display_art($thumb = 2) + { + if (Art::has_db($this->id, 'tag')) { + Art::display('tag', $this->id, $this->get_fullname(), $thumb, $this->link); + } + } } // end of Tag class diff --git a/sources/lib/class/tmp_playlist.class.php b/sources/lib/class/tmp_playlist.class.php index d0127fa..85896bc 100644 --- a/sources/lib/class/tmp_playlist.class.php +++ b/sources/lib/class/tmp_playlist.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -137,22 +137,22 @@ class Tmp_Playlist extends database_object */ public function get_items() { - $id = Dba::escape($this->id); - /* Select all objects from this playlist */ $sql = "SELECT `object_type`, `id`, `object_id` " . "FROM `tmp_playlist_data` " . - "WHERE `tmp_playlist`='$id' ORDER BY `id` ASC"; - $db_results = Dba::read($sql); + "WHERE `tmp_playlist` = ? ORDER BY `id` ASC"; + $db_results = Dba::read($sql, array($this->id)); /* Define the array */ $items = array(); + $i = 1; while ($results = Dba::fetch_assoc($db_results)) { - $key = $results['id']; - $items[$key] = array( + $items[] = array( 'object_type' => $results['object_type'], - 'object_id' => $results['object_id'] + 'object_id' => $results['object_id'], + 'track_id' => $results['id'], + 'track' => $i++, ); } @@ -271,7 +271,7 @@ class Tmp_Playlist extends database_object { self::prune_playlists(); self::prune_tracks(); - Dba::write("DELETE FROM `tmp_playlist_data` USING `tmp_playlist_data` LEFT JOIN `song` ON `tmp_playlist_data`.`object_id` = `song`.`id` WHERE `song`.`id` IS NULL"); + //Dba::write("DELETE FROM `tmp_playlist_data` USING `tmp_playlist_data` LEFT JOIN `song` ON `tmp_playlist_data`.`object_id` = `song`.`id` WHERE `song`.`id` IS NULL"); } /** @@ -324,6 +324,13 @@ class Tmp_Playlist extends database_object } // add_object + public function add_medias($medias) + { + foreach ($medias as $media) { + $this->add_object($media['object_id'], $media['object_type']); + } + } + /** * vote_active * This checks to see if this playlist is a voting playlist diff --git a/sources/lib/class/tvshow.class.php b/sources/lib/class/tvshow.class.php new file mode 100644 index 0000000..9cc5dfa --- /dev/null +++ b/sources/lib/class/tvshow.class.php @@ -0,0 +1,440 @@ +get_info($id); + + foreach ($info as $key=>$value) { + $this->$key = $value; + } // foreach info + + return true; + + } //constructor + + /** + * gc + * + * This cleans out unused tv shows + */ + public static function gc() + { + $sql = "DELETE FROM `tvshow` USING `tvshow` LEFT JOIN `tvshow_season` ON `tvshow_season`.`tvshow` = `tvshow`.`id` " . + "WHERE `tvshow_season`.`id` IS NULL"; + Dba::write($sql); + } + + /** + * get_from_name + * This gets a tv show object based on the tv show name + */ + public static function get_from_name($name) + { + $sql = "SELECT `id` FROM `tvshow` WHERE `name` = ?'"; + $db_results = Dba::read($sql, array($name)); + + $row = Dba::fetch_assoc($db_results); + + $object = new TVShow($row['id']); + return $object; + + } // get_from_name + + /** + * get_seasons + * gets the tv show seasons + * of + */ + public function get_seasons() + { + $sql = "SELECT `id` FROM `tvshow_season` WHERE `tvshow` = ? ORDER BY `season_number`"; + $db_results = Dba::read($sql, array($this->id)); + $results = array(); + while ($r = Dba::fetch_assoc($db_results)) { + $results[] = $r['id']; + } + + return $results; + + } // get_seasons + + /** + * get_songs + * gets all episodes for this tv show + */ + public function get_episodes() + { + $sql = "SELECT `tvshow_episode`.`id` FROM `tvshow_episode` "; + if (AmpConfig::get('catalog_disable')) { + $sql .= "LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` "; + $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `video`.`catalog` "; + } + $sql .= "LEFT JOIN `tvshow_season` ON `tvshow_season`.`id` = `tvshow_episode`.`season` "; + $sql .= "WHERE `tvshow_season`.`tvshow`='" . Dba::escape($this->id) . "' "; + if (AmpConfig::get('catalog_disable')) { + $sql .= "AND `catalog`.`enabled` = '1' "; + } + $sql .= "ORDER BY `tvshow_season`.`season_number`, `tvshow_episode`.`episode_number`"; + $db_results = Dba::read($sql); + + $results = array(); + while ($r = Dba::fetch_assoc($db_results)) { + $results[] = $r['id']; + } + + return $results; + + } // get_episodes + + /** + * _get_extra info + * This returns the extra information for the tv show, this means totals etc + */ + private function _get_extra_info() + { + // Try to find it in the cache and save ourselves the trouble + if (parent::is_cached('tvshow_extra', $this->id) ) { + $row = parent::get_from_cache('tvshow_extra', $this->id); + } else { + $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` as `catalog_id` FROM `tvshow_season` " . + "LEFT JOIN `tvshow_episode` ON `tvshow_episode`.`season` = `tvshow_season`.`id` " . + "LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` " . + "WHERE `tvshow_season`.`tvshow` = ?"; + $db_results = Dba::read($sql, array($this->id)); + $row = Dba::fetch_assoc($db_results); + + $sql = "SELECT COUNT(`tvshow_season`.`id`) AS `season_count` FROM `tvshow_season` " . + "WHERE `tvshow_season`.`tvshow` = ?"; + $db_results = Dba::read($sql, array($this->id)); + $row2 = Dba::fetch_assoc($db_results); + $row['season_count'] = $row2['season_count']; + + parent::add_to_cache('tvshow_extra',$this->id,$row); + } + + /* Set Object Vars */ + $this->episodes = $row['episode_count']; + $this->seasons = $row['season_count']; + $this->catalog_id = $row['catalog_id']; + + return $row; + + } // _get_extra_info + + /** + * format + * this function takes the object and reformats some values + */ + public function format($details = true) + { + $this->f_name = trim($this->prefix . " " . $this->name); + $this->link = AmpConfig::get('web_path') . '/tvshows.php?action=show&tvshow=' . $this->id; + $this->f_link = '' . $this->f_name . ''; + + if ($details) { + $this->_get_extra_info(); + $this->tags = Tag::get_top_tags('tvshow', $this->id); + $this->f_tags = Tag::get_display($this->tags, true, 'tvshow'); + } + + return true; + } + + public function get_keywords() + { + $keywords = array(); + $keywords['tvshow'] = array('important' => true, + 'label' => T_('TV Show'), + 'value' => $this->f_name); + $keywords['type'] = array('important' => false, + 'label' => null, + 'value' => 'tvshow' + ); + + return $keywords; + } + + public function get_fullname() + { + return $this->f_name; + } + + public function get_parent() + { + return null; + } + + public function get_childrens() + { + return array('tvshow_season' => $this->get_seasons()); + } + + public function search_childrens($name) + { + return array(); + } + + public function get_medias($filter_type = null) + { + $medias = array(); + if (!$filter_type || $filter_type == 'video') { + $episodes = $this->get_episodes(); + foreach ($episodes as $episode_id) { + $medias[] = array( + 'object_type' => 'video', + 'object_id' => $episode_id + ); + } + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array($this->catalog_id); + } + + public function get_user_owner() + { + return null; + } + + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + return $this->summary; + } + + public function display_art($thumb = 2) + { + if (Art::has_db($this->id, 'tvshow')) { + Art::display('tvshow', $this->id, $this->get_fullname(), $thumb, $this->link); + } + } + + /** + * check + * + * Checks for an existing tv show; if none exists, insert one. + */ + public static function check($name, $year, $readonly = false) + { + // null because we don't have any unique id like mbid for now + if (isset(self::$_mapcache[$name]['null'])) { + return self::$_mapcache[$name]['null']; + } + + $id = 0; + $exists = false; + + $trimmed = Catalog::trim_prefix(trim($name)); + $name = $trimmed['string']; + $prefix = $trimmed['prefix']; + + if (!$exists) { + $sql = 'SELECT `id` FROM `tvshow` WHERE `name` LIKE ? AND `year` = ?'; + $db_results = Dba::read($sql, array($name, $year)); + + $id_array = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $key = 'null'; + $id_array[$key] = $row['id']; + } + + if (count($id_array)) { + $id = array_shift($id_array); + $exists = true; + } + } + + if ($exists) { + self::$_mapcache[$name]['null'] = $id; + return $id; + } + + if ($readonly) { + return null; + } + + $sql = 'INSERT INTO `tvshow` (`name`, `prefix`, `year`) ' . + 'VALUES(?, ?, ?)'; + + $db_results = Dba::write($sql, array($name, $prefix, $year)); + if (!$db_results) { + return null; + } + $id = Dba::insert_id(); + + self::$_mapcache[$name]['null'] = $id; + return $id; + + } + + /** + * update + * This takes a key'd array of data and updates the current tv show + */ + public function update(array $data) + { + // Save our current ID + $current_id = $this->id; + $name = isset($data['name']) ? $data['name'] : $this->name; + $year = isset($data['year']) ? $data['year'] : $this->year; + $summary = isset($data['summary']) ? $data['summary'] : $this->summary; + + // Check if name is different than current name + if ($this->name != $name || $this->year != $year) { + $tvshow_id = self::check($name, $year, true); + + // If it's changed we need to update + if ($tvshow_id != $this->id && $tvshow_id != null) { + $seasons = $this->get_seasons(); + foreach ($seasons as $season_id) { + Season::update_tvshow($tvshow_id, $season_id); + } + $current_id = $tvshow_id; + Stats::migrate('tvshow', $this->id, $tvshow_id); + Art::migrate('tvshow', $this->id, $tvshow_id); + self::gc(); + } // end if it changed + } + + $trimmed = Catalog::trim_prefix(trim($name)); + $name = $trimmed['string']; + $prefix = $trimmed['prefix']; + + $sql = 'UPDATE `tvshow` SET `name` = ?, `prefix` = ?, `year` = ?, `summary` = ? WHERE `id` = ?'; + Dba::write($sql, array($name, $prefix, $year, $summary, $current_id)); + + $this->name = $name; + $this->prefix = $prefix; + $this->year = $year; + $this->summary = $summary; + + $override_childs = false; + if ($data['overwrite_childs'] == 'checked') { + $override_childs = true; + } + + $add_to_childs = false; + if ($data['add_to_childs'] == 'checked') { + $add_to_childs = true; + } + + if (isset($data['edit_tags'])) { + $this->update_tags($data['edit_tags'], $override_childs, $add_to_childs, $current_id, true); + } + + return $current_id; + + } // update + + /** + * update_tags + * + * Update tags of tv shows + */ + public function update_tags($tags_comma, $override_childs, $add_to_childs, $current_id = null, $force_update = false) + { + if ($current_id == null) { + $current_id = $this->id; + } + + Tag::update_tag_list($tags_comma, 'tvshow', $current_id, $force_update ? true : $override_childs); + + if ($override_childs || $add_to_childs) { + $episodes = $this->get_episodes(); + foreach ($episodes as $ep_id) { + Tag::update_tag_list($tags_comma, 'episode', $ep_id, $override_childs); + } + } + } + + public function remove_from_disk() + { + $deleted = true; + $season_ids = $this->get_seasons(); + foreach ($season_ids as $id) { + $season = new TVShow_Season($id); + $deleted = $season->remove_from_disk(); + if (!$deleted) { + debug_event('tvshow', 'Error when deleting the season `' . $id .'`.', 1); + break; + } + } + + if ($deleted) { + $sql = "DELETE FROM `tvshow` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + if ($deleted) { + Art::gc('tvshow', $this->id); + Userflag::gc('tvshow', $this->id); + Rating::gc('tvshow', $this->id); + Shoutbox::gc('tvshow', $this->id); + } + } + + return $deleted; + } + +} // end of tvshow class diff --git a/sources/lib/class/tvshow_episode.class.php b/sources/lib/class/tvshow_episode.class.php new file mode 100644 index 0000000..b89ec76 --- /dev/null +++ b/sources/lib/class/tvshow_episode.class.php @@ -0,0 +1,255 @@ +get_info($id); + foreach ($info as $key=>$value) { + $this->$key = $value; + } + + return true; + + } + + /** + * gc + * + * This cleans out unused tv shows episodes + */ + public static function gc() + { + $sql = "DELETE FROM `tvshow_episode` USING `tvshow_episode` LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` WHERE `video`.`id` IS NULL"; + Dba::write($sql); + } + + /** + * insert + * Insert a new tv show episode and related entities. + */ + public static function insert(array $data, $gtypes = array(), $options = array()) + { + if (empty($data['tvshow'])) { + $data['tvshow'] = T_('Unknown'); + } + $tags = $data['genre']; + + $tvshow = TVShow::check($data['tvshow'], $data['year']); + if ($options['gather_art'] && $tvshow && $data['tvshow_art'] && !Art::has_db($tvshow, 'tvshow')) { + $art = new Art($tvshow, 'tvshow'); + $art->insert_url($data['tvshow_art']); + } + $tvshow_season = TVShow_Season::check($tvshow, $data['tvshow_season']); + if ($options['gather_art'] && $tvshow_season && $data['tvshow_season_art'] && !Art::has_db($tvshow_season, 'tvshow_season')) { + $art = new Art($tvshow_season, 'tvshow_season'); + $art->insert_url($data['tvshow_season_art']); + } + + if (is_array($tags)) { + foreach ($tags as $tag) { + $tag = trim($tag); + if (!empty($tag)) { + Tag::add('tvshow_season', $tvshow_season, $tag, false); + Tag::add('tvshow', $tvshow, $tag, false); + } + } + } + + $sdata = $data; + // Replace relation name with db ids + $sdata['tvshow'] = $tvshow; + $sdata['tvshow_season'] = $tvshow_season; + return self::create($sdata); + } + + /** + * create + * This takes a key'd array of data as input and inserts a new tv show episode entry, it returns the record id + */ + public static function create($data) + { + $sql = "INSERT INTO `tvshow_episode` (`id`, `original_name`, `season`, `episode_number`, `summary`) " . + "VALUES (?, ?, ?, ?, ?)"; + Dba::write($sql, array($data['id'], $data['original_name'], $data['tvshow_season'], $data['tvshow_episode'], $data['summary'])); + + return $data['id']; + + } + + /** + * update + * This takes a key'd array of data as input and updates a tv show episode entry + */ + public function update(array $data) + { + parent::update($data); + + $original_name = isset($data['original_name']) ? $data['original_name'] : $this->original_name; + $tvshow_season = isset($data['tvshow_season']) ? $data['tvshow_season'] : $this->season; + $tvshow_episode = isset($data['tvshow_episode']) ? $data['tvshow_episode'] : $this->episode_number; + $summary = isset($data['summary']) ? $data['summary'] : $this->summary; + + $sql = "UPDATE `tvshow_episode` SET `original_name` = ?, `season` = ?, `episode_number` = ?, `summary` = ? WHERE `id` = ?"; + Dba::write($sql, array($original_name, $tvshow_season, $tvshow_episode, $summary, $this->id)); + + $this->original_name = $original_name; + $this->season = $tvshow_season; + $this->episode_number = $tvshow_episode; + $this->summary = $summary; + + return $this->id; + + } + + /** + * format + * this function takes the object and reformats some values + */ + public function format($details = true) + { + parent::format($details); + + $season = new TVShow_Season($this->season); + $season->format($details); + + $this->f_title = ($this->original_name ?: $this->f_title); + $this->f_link = '' . $this->f_title . ''; + $this->f_season = $season->f_name; + $this->f_season_link = $season->f_link; + $this->f_tvshow = $season->f_tvshow; + $this->f_tvshow_link = $season->f_tvshow_link; + + $this->f_file = $this->f_tvshow; + if ($this->episode_number) { + $this->f_file .= ' - S'. sprintf('%02d', $season->season_number) . 'E'. sprintf('%02d', $this->episode_number); + } + $this->f_file .= ' - ' . $this->f_title; + $this->f_full_title = $this->f_file; + + return true; + } + + /** + * Get item keywords for metadata searches. + * @return array + */ + public function get_keywords() + { + $keywords = parent::get_keywords(); + $keywords['tvshow'] = array('important' => true, + 'label' => T_('TV Show'), + 'value' => $this->f_tvshow); + $keywords['tvshow_season'] = array('important' => false, + 'label' => T_('Season'), + 'value' => $this->f_season); + if ($this->episode_number) { + $keywords['tvshow_episode'] = array('important' => false, + 'label' => T_('Episode'), + 'value' => $this->episode_number); + } + $keywords['type'] = array('important' => false, + 'label' => null, + 'value' => 'tvshow' + ); + + return $keywords; + } + + public function get_parent() + { + return array('object_type' => 'tvshow_season', 'object_id' => $this->season); + } + + public function get_release_item_art() + { + return array('object_type' => 'tvshow_season', + 'object_id' => $this->season + ); + } + + public function get_description() + { + if (!empty($this->summary)) + return $this->summary; + + $season = new TVShow_Season($this->season); + return $season->get_description(); + } + + public function display_art($thumb = 2) + { + $id = null; + $type = null; + + if (Art::has_db($this->id, 'video')) { + $id = $this->id; + $type = 'video'; + } else if (Art::has_db($this->season, 'tvshow_season')) { + $id = $this->season; + $type = 'tvshow_season'; + } else { + $season = new TVShow_Season($this->season); + if (Art::has_db($season->tvshow, 'tvshow')) { + $id = $season->tvshow; + $type = 'tvshow'; + } + } + + if ($id !== null && $type !== null) { + Art::display($type, $id, $this->get_fullname(), $thumb, $this->link); + } + } + + /** + * Remove the video from disk. + */ + public function remove_from_disk() + { + $deleted = parent::remove_from_disk(); + if ($deleted) { + $sql = "DELETE FROM `tvshow_episode` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + } + + return $deleted; + } +} diff --git a/sources/lib/class/tvshow_season.class.php b/sources/lib/class/tvshow_season.class.php new file mode 100644 index 0000000..cd6e41a --- /dev/null +++ b/sources/lib/class/tvshow_season.class.php @@ -0,0 +1,349 @@ +get_info($id); + + foreach ($info as $key=>$value) { + $this->$key = $value; + } // foreach info + + return true; + + } //constructor + + /** + * gc + * + * This cleans out unused tv shows seasons + */ + public static function gc() + { + $sql = "DELETE FROM `tvshow_season` USING `tvshow_season` LEFT JOIN `tvshow_episode` ON `tvshow_episode`.`season` = `tvshow_season`.`id` " . + "WHERE `tvshow_episode`.`id` IS NULL"; + Dba::write($sql); + } + + /** + * get_songs + * gets all episodes for this tv show season + */ + public function get_episodes() + { + $sql = "SELECT `tvshow_episode`.`id` FROM `tvshow_episode` "; + if (AmpConfig::get('catalog_disable')) { + $sql .= "LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` "; + $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `video`.`catalog` "; + } + $sql .= "WHERE `tvshow_episode`.`season`='" . Dba::escape($this->id) . "' "; + if (AmpConfig::get('catalog_disable')) { + $sql .= "AND `catalog`.`enabled` = '1' "; + } + $sql .= "ORDER BY `tvshow_episode`.`episode_number`"; + $db_results = Dba::read($sql); + + $results = array(); + while ($r = Dba::fetch_assoc($db_results)) { + $results[] = $r['id']; + } + + return $results; + + } // get_episodes + + /** + * _get_extra info + * This returns the extra information for the tv show season, this means totals etc + */ + private function _get_extra_info() + { + // Try to find it in the cache and save ourselves the trouble + if (parent::is_cached('tvshow_extra', $this->id) ) { + $row = parent::get_from_cache('tvshow_extra', $this->id); + } else { + $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` as `catalog_id` FROM `tvshow_episode` " . + "LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` " . + "WHERE `tvshow_episode`.`season` = ?"; + + $db_results = Dba::read($sql, array($this->id)); + $row = Dba::fetch_assoc($db_results); + parent::add_to_cache('tvshow_extra',$this->id,$row); + } + + /* Set Object Vars */ + $this->episodes = $row['episode_count']; + $this->catalog_id = $row['catalog_id']; + + return $row; + + } // _get_extra_info + + /** + * format + * this function takes the object and reformats some values + */ + public function format($details = true) + { + $this->f_name = T_('Season') . ' ' . $this->season_number; + + $tvshow = new TVShow($this->tvshow); + $tvshow->format($details); + $this->f_tvshow = $tvshow->f_name; + $this->f_tvshow_link = $tvshow->f_link; + + $this->link = AmpConfig::get('web_path') . '/tvshow_seasons.php?action=show&season=' . $this->id; + $this->f_link = '' . $this->f_name . ''; + + if ($details) { + $this->_get_extra_info(); + } + + return true; + } + + public function get_keywords() + { + $keywords = array(); + $keywords['tvshow'] = array('important' => true, + 'label' => T_('TV Show'), + 'value' => $this->f_tvshow); + $keywords['tvshow_season'] = array('important' => false, + 'label' => T_('Season'), + 'value' => $this->season_number); + $keywords['type'] = array('important' => false, + 'label' => null, + 'value' => 'tvshow' + ); + + return $keywords; + } + + public function get_fullname() + { + return $this->f_name; + } + + public function get_parent() + { + return array('object_type' => 'tvshow', 'object_id' => $this->tvshow); + } + + public function get_childrens() + { + return array('tvshow_episode' => $this->get_episodes()); + } + + public function search_childrens($name) + { + return array(); + } + + public function get_medias($filter_type = null) + { + $medias = array(); + if (!$filter_type || $filter_type == 'video') { + $episodes = $this->get_episodes(); + foreach ($episodes as $episode_id) { + $medias[] = array( + 'object_type' => 'video', + 'object_id' => $episode_id + ); + } + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array($this->catalog_id); + } + + public function get_user_owner() + { + return null; + } + + public function get_default_art_kind() + { + return 'default'; + } + + public function get_description() + { + // No season description for now, always return tvshow description + $tvshow = new TVShow($this->tvshow); + return $tvshow->get_description(); + } + + public function display_art($thumb = 2) + { + $id = null; + $type = null; + + if (Art::has_db($this->id, 'tvshow_season')) { + $id = $this->id; + $type = 'tvshow_season'; + } else if (Art::has_db($this->tvshow, 'tvshow')) { + $id = $this->tvshow; + $type = 'tvshow'; + } + + if ($id !== null && $type !== null) { + Art::display($type, $id, $this->get_fullname(), $thumb, $this->link); + } + } + + /** + * check + * + * Checks for an existing tv show season; if none exists, insert one. + */ + public static function check($tvshow, $season_number, $readonly = false) + { + $name = $tvshow . '_' . $season_number; + // null because we don't have any unique id like mbid for now + if (isset(self::$_mapcache[$name]['null'])) { + return self::$_mapcache[$name]['null']; + } + + $id = 0; + $exists = false; + + if (!$exists) { + $sql = 'SELECT `id` FROM `tvshow_season` WHERE `tvshow` = ? AND `season_number` = ?'; + $db_results = Dba::read($sql, array($tvshow, $season_number)); + + $id_array = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $key = 'null'; + $id_array[$key] = $row['id']; + } + + if (count($id_array)) { + $id = array_shift($id_array); + $exists = true; + } + } + + if ($exists) { + self::$_mapcache[$name]['null'] = $id; + return $id; + } + + if ($readonly) { + return null; + } + + $sql = 'INSERT INTO `tvshow_season` (`tvshow`, `season_number`) ' . + 'VALUES(?, ?)'; + + $db_results = Dba::write($sql, array($tvshow, $season_number)); + if (!$db_results) { + return null; + } + $id = Dba::insert_id(); + + self::$_mapcache[$name]['null'] = $id; + return $id; + + } + + /** + * update + * This takes a key'd array of data and updates the current tv show + */ + public function update(array $data) + { + $sql = 'UPDATE `tvshow_season` SET `season_number` = ?, `tvshow` = ? WHERE `id` = ?'; + Dba::write($sql, array($data['season_number'], $data['tvshow'], $this->id)); + + return $this->id; + } // update + + public function remove_from_disk() + { + $deleted = true; + $video_ids = $this->get_episodes(); + foreach ($video_ids as $id) { + $video = Video::create_from_id($id); + $deleted = $video->remove_from_disk(); + if (!$deleted) { + debug_event('tvshow_season', 'Error when deleting the video `' . $id .'`.', 1); + break; + } + } + + if ($deleted) { + $sql = "DELETE FROM `tvshow_season` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + if ($deleted) { + Art::gc('tvshow_season', $this->id); + Userflag::gc('tvshow_season', $this->id); + Rating::gc('tvshow_season', $this->id); + Shoutbox::gc('tvshow_season', $this->id); + } + } + + return $deleted; + } + + public static function update_tvshow($tvshow_id, $season_id) + { + $sql = "UPDATE `tvshow_season` SET `tvshow` = ? WHERE `id` = ?"; + return Dba::write($sql, array($tvshow_id, $season_id)); + } + +} // end of tvshow_season class diff --git a/sources/lib/class/ui.class.php b/sources/lib/class/ui.class.php index a58acec..563db1a 100644 --- a/sources/lib/class/ui.class.php +++ b/sources/lib/class/ui.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -277,16 +277,6 @@ END; require_once AmpConfig::get('prefix') . '/templates/header.inc.php'; } - /** - * show_mainframes - * - * For now this just shows the mainframes template - */ - public static function show_mainframes() - { - require_once AmpConfig::get('prefix') . '/templates/mainframes.inc.php'; - } - /** * show_footer * @@ -294,6 +284,18 @@ END; */ public static function show_footer() { + if (!defined("TABLE_RENDERED")) { + show_table_render(); + } + + $plugins = Plugin::get_plugins('display_on_footer'); + foreach ($plugins as $plugin_name) { + $plugin = new Plugin($plugin_name); + if ($plugin->load($GLOBALS['user'])) { + $plugin->_plugin->display_on_footer(); + } + } + require_once AmpConfig::get('prefix') . '/templates/footer.inc.php'; if (isset($_REQUEST['profiling'])) { Dba::show_profile(); @@ -320,6 +322,16 @@ END; require AmpConfig::get('prefix') . '/templates/show_box_bottom.inc.php'; } + public static function show_custom_style() + { + if (AmpConfig::get('custom_login_logo')) { + echo ""; + } + + $favicon = AmpConfig::get('custom_favicon') ?: AmpConfig::get('web_path') . "/favicon.ico"; + echo "\n"; + } + /** * update_text * @@ -333,10 +345,30 @@ END; return; } - echo '\n"; + static $id = 1; + + if (defined('SSE_OUTPUT')) { + echo "id: " . $id . "\n"; + echo "data: displayNotification('" . json_encode($value) . "', 5000)\n\n"; + } else { + if (!empty($field)) { + echo "\n"; + } else { + echo "
" . $value . "

\n"; + } + } + ob_flush(); flush(); + $id++; + } + + public static function get_logo_url() + { + if (AmpConfig::get('custom_logo')) { + return AmpConfig::get('custom_logo'); + } else { + return AmpConfig::get('web_path') . AmpConfig::get('theme_path') . '/images/ampache.png'; + } } } diff --git a/sources/lib/class/update.class.php b/sources/lib/class/update.class.php index b84e311..688939a 100644 --- a/sources/lib/class/update.class.php +++ b/sources/lib/class/update.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -341,7 +341,7 @@ class Update $update_string = '- New table to store song previews.
'; $version[] = array('version' => '360030','description' => $update_string); - $update_string = '- Add option to fix header/sidebars position on compatible themes.
'; + $update_string = '- Add option to fix header position on compatible themes.
'; $version[] = array('version' => '360031','description' => $update_string); $update_string = '- Add check update automatically option.
'; @@ -401,6 +401,111 @@ class Update $update_string = '- Copy default .htaccess configurations.
'; $version[] = array('version' => '360051','description' => $update_string); + $update_string = '- Drop unused dynamic_playlist tables and add session id to votes.
'; + $version[] = array('version' => '370001','description' => $update_string); + + $update_string = '- Add tag persistent merge reference.
'; + $version[] = array('version' => '370002','description' => $update_string); + + $update_string = '- Add show/hide donate button preference.
'; + $version[] = array('version' => '370003','description' => $update_string); + + $update_string = '- Add license information and user\'s artist association.
'; + $version[] = array('version' => '370004','description' => $update_string); + + $update_string = '- Add new column album_artist into table song.
'; + $version[] = array('version' => '370005','description' => $update_string); + + $update_string = '- Add random and limit options to smart playlists.
'; + $version[] = array('version' => '370006','description' => $update_string); + + $update_string = '- Add DAAP backend preference.
'; + $version[] = array('version' => '370007','description' => $update_string); + + $update_string = '- Add UPnP backend preference.
'; + $version[] = array('version' => '370008','description' => $update_string); + + $update_string = '- Enhance video support with TVShows and Movies.
'; + $version[] = array('version' => '370009','description' => $update_string); + + $update_string = '- Add MusicBrainz Album Release Group identifier.
'; + $version[] = array('version' => '370010','description' => $update_string); + + $update_string = '- Add Prefix to TVShows and Movies.
'; + $version[] = array('version' => '370011','description' => $update_string); + + $update_string = '- Add metadata information to albums / songs / videos.
'; + $version[] = array('version' => '370012','description' => $update_string); + + $update_string = '- Replace iframe with ajax page load.
'; + $version[] = array('version' => '370013','description' => $update_string); + + $update_string = '- Modified release_date in video table to signed int.
'; + $version[] = array('version' => '370014','description' => $update_string); + + $update_string = '- Add session_remember table to store remember tokens.
'; + $version[] = array('version' => '370015','description' => $update_string); + + $update_string = '- Add limit of media count for direct play preference.
'; + $version[] = array('version' => '370016','description' => $update_string); + + $update_string = '- Add home display settings.
'; + $version[] = array('version' => '370017','description' => $update_string); + + $update_string = '- Enhance tag persistent merge reference.
'; + $version[] = array('version' => '370018','description' => $update_string); + + $update_string = '- Add album group order setting.
'; + $version[] = array('version' => '370019','description' => $update_string); + + $update_string = '- Add webplayer browser notification settings.
'; + $version[] = array('version' => '370020','description' => $update_string); + + $update_string = '- Add rating to playlists, tvshows and tvshows seasons.
'; + $version[] = array('version' => '370021','description' => $update_string); + + $update_string = '- Add users geolocation.
'; + $version[] = array('version' => '370022','description' => $update_string); + + $update_string = " - Add Aurora.js webplayer option.
"; + $version[] = array('version' => '370023','description' => $update_string); + + $update_string = " - Add count_type column to object_count table.
"; + $version[] = array('version' => '370024','description' => $update_string); + + $update_string = " - Add state and city fields to user table.
"; + $version[] = array('version' => '370025','description' => $update_string); + + $update_string = " - Add replay gain fields to song_data table.
"; + $version[] = array('version' => '370026','description' => $update_string); + + $update_string = " - Move column album_artist from table song to table album.
"; + $version[] = array('version' => '370027','description' => $update_string); + + $update_string = " - Add width and height in table image.
"; + $version[] = array('version' => '370028','description' => $update_string); + + $update_string = " - Set image column from image table as nullable.
"; + $version[] = array('version' => '370029','description' => $update_string); + + $update_string = " - Add an option to allow users to remove uploaded songs.
"; + $version[] = array('version' => '370030','description' => $update_string); + + $update_string = " - Add an option to customize login art, favicon and text footer.
"; + $version[] = array('version' => '370031','description' => $update_string); + + $update_string = " - Add WebDAV backend preference.
"; + $version[] = array('version' => '370032','description' => $update_string); + + $update_string = " - Add Label tables.
"; + $version[] = array('version' => '370033','description' => $update_string); + + $update_string = " - Add User messages and user follow tables.
"; + $version[] = array('version' => '370034','description' => $update_string); + + $update_string = " - Add option on user fullname to show/hide it publicly.
"; + $version[] = array('version' => '370035','description' => $update_string); + return $version; } @@ -441,7 +546,7 @@ class Update if (!defined('CLI')) { echo '

'; } echo T_('No updates needed.'); if (!defined('CLI')) { - echo '[Return]

'; + echo ' [', T_('Return to main page'), ']

'; } else { echo "\n"; } @@ -1630,16 +1735,18 @@ class Update */ public static function update_360015() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('iframes','1','Iframes',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /* @@ -1649,16 +1756,18 @@ class Update */ public static function update_360016() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('now_playing_per_user','1','Now playing filtered per user',50,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1687,16 +1796,18 @@ class Update */ public static function update_360018() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('album_sort','0','Album Default Sort',25,'string','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1706,16 +1817,18 @@ class Update */ public static function update_360019() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('show_played_times','0','Show # played',25,'string','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1725,6 +1838,8 @@ class Update */ public static function update_360020() { + $retval = true; + $sql = "SELECT `id`, `catalog_type`, `path`, `remote_username`, `remote_password` FROM `catalog`"; $db_results = Dba::read($sql); @@ -1736,29 +1851,29 @@ class Update while ($results = Dba::fetch_assoc($db_results)) { if ($results['catalog_type'] == 'local') { $sql = "INSERT INTO `catalog_local` (`path`, `catalog_id`) VALUES (?, ?)"; - Dba::write($sql, array($results['path'], $results['id'])); + $retval = Dba::write($sql, array($results['path'], $results['id'])) ? $retval : false; } elseif ($results['catalog_type'] == 'remote') { $sql = "INSERT INTO `catalog_remote` (`uri`, `username`, `password`, `catalog_id`) VALUES (?, ?, ?, ?)"; - Dba::write($sql, array($results['path'], $results['remote_username'], $results['remote_password'], $results['id'])); + $retval = Dba::write($sql, array($results['path'], $results['remote_username'], $results['remote_password'], $results['id'])) ? $retval : false; } } $sql = "ALTER TABLE `catalog` DROP `path`, DROP `remote_username`, DROP `remote_password`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `catalog` MODIFY COLUMN `catalog_type` varchar(128)"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "UPDATE `artist` SET `mbid` = null WHERE `mbid` = ''"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "UPDATE `album` SET `mbid` = null WHERE `mbid` = ''"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "UPDATE `song` SET `mbid` = null WHERE `mbid` = ''"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -1768,19 +1883,21 @@ class Update */ public static function update_360021() { + $retval = true; + $sql = "ALTER TABLE `now_playing` ADD `insertion` INT (11) AFTER `expire`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('song_page_title','1','Show current song in Web player page title',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1790,13 +1907,15 @@ class Update */ public static function update_360022() { + $retval = true; + $sql = "ALTER TABLE `live_stream` ADD `codec` VARCHAR(32) NULL AFTER `catalog`, DROP `frequency`, DROP `call_sign`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `stream_playlist` ADD `codec` VARCHAR(32) NULL AFTER `time`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -1806,25 +1925,27 @@ class Update */ public static function update_360023() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('subsonic_backend','1','Use SubSonic backend',25,'boolean','system')"; - Dba::write($sql); + "VALUES ('subsonic_backend','1','Use SubSonic backend',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval: false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('plex_backend','0','Use Plex backend',25,'boolean','system')"; - Dba::write($sql); + "VALUES ('plex_backend','0','Use Plex backend',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1834,10 +1955,8 @@ class Update */ public static function update_360024() { - $sql = "DROP TABLE `flagged`"; - Dba::write($sql); - - return true; + $sql = "DROP TABLE IF EXISTS `flagged`"; + return Dba::write($sql); } /** @@ -1847,25 +1966,27 @@ class Update */ public static function update_360025() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('webplayer_flash','1','Authorize Flash Web Player(s)',25,'boolean','streaming')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('webplayer_html5','1','Authorize HTML5 Web Player(s)',25,'boolean','streaming')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1876,9 +1997,7 @@ class Update public static function update_360026() { $sql = "ALTER TABLE `object_count` ADD `agent` VARCHAR(255) NULL AFTER `user`"; - Dba::write($sql); - - return true; + return Dba::write($sql); } /** @@ -1888,16 +2007,18 @@ class Update */ public static function update_360027() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('allow_personal_info','1','Allow to show my personal info to other users (now playing, recently played)',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1909,35 +2030,37 @@ class Update */ public static function update_360028() { + $retval = true; + // Update previous update preference $sql = "UPDATE `preference` SET `name`='allow_personal_info_now', `description`='Personal information visibility - Now playing' WHERE `name`='allow_personal_info'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; // Insert new recently played preference $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('allow_personal_info_recent','1','Personal information visibility - Recently played',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; // Insert streaming time preference $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('allow_personal_info_time','1','Personal information visibility - Recently played - Allow to show streaming date/time',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; // Insert streaming agent preference $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('allow_personal_info_agent','1','Personal information visibility - Recently played - Allow to show streaming agent',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -1991,14 +2114,16 @@ class Update */ public static function update_360031() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('ui_fixed','0','Fix header/sidebars position on compatible themes',25,'boolean','interface')"; - Dba::write($sql); + "VALUES ('ui_fixed','0','Fix header position on compatible themes',25,'boolean','interface')"; + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2008,18 +2133,20 @@ class Update */ public static function update_360032() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('autoupdate','1','Check for Ampache updates automatically',25,'boolean','system')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; Preference::insert('autoupdate_lastcheck','AutoUpdate last check time','','25','string','internal'); Preference::insert('autoupdate_lastversion','AutoUpdate last version from last check','','25','string','internal'); Preference::insert('autoupdate_lastversion_new','AutoUpdate last version from last check is newer','','25','boolean','internal'); - return true; + return $retval; } /** @@ -2029,13 +2156,15 @@ class Update */ public static function update_360033() { + $retval = true; + $sql = "ALTER TABLE `song_data` ADD `waveform` MEDIUMBLOB NULL AFTER `language`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `user_shout` ADD `data` VARCHAR(256) NULL AFTER `object_type`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -2045,21 +2174,23 @@ class Update */ public static function update_360034() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('webplayer_confirmclose','0','Confirmation when closing current playing window',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('webplayer_pausetabs','1','Auto-pause betweens tabs',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2069,14 +2200,16 @@ class Update */ public static function update_360035() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('stream_beautiful_url','0','Use beautiful stream url',25,'boolean','streaming')"; - Dba::write($sql); + "VALUES ('stream_beautiful_url','0','Enable url rewriting',100,'boolean','streaming')"; + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2086,22 +2219,24 @@ class Update */ public static function update_360036() { + $retval = true; + $sql = "DELETE FROM `preference` WHERE `name` LIKE 'ellipse_threshold_%'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "DELETE FROM `preference` WHERE `name` = 'min_object_count'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "DELETE FROM `preference` WHERE `name` = 'bandwidth'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "DELETE FROM `preference` WHERE `name` = 'features'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "DELETE FROM `preference` WHERE `name` = 'tags_userlist'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -2111,6 +2246,8 @@ class Update */ public static function update_360037() { + $retval = true; + $sql = "CREATE TABLE `share` (" . "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . "`user` int(11) unsigned NOT NULL," . @@ -2127,23 +2264,23 @@ class Update "`public_url` varchar(255) CHARACTER SET utf8 NULL," . "`description` varchar(255) CHARACTER SET utf8 NULL," . "PRIMARY KEY (`id`)) ENGINE = MYISAM"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('share','0','Allow Share',25,'boolean','system')"; - Dba::write($sql); + "VALUES ('share','0','Allow Share',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . - "VALUES ('share_expire','7','Share links default expiration days (0=never)',25,'integer','system')"; - Dba::write($sql); + "VALUES ('share_expire','7','Share links default expiration days (0=never)',100,'integer','system')"; + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'7')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2153,19 +2290,21 @@ class Update */ public static function update_360038() { + $retval = true; + $sql = "ALTER TABLE `wanted` ADD `artist_mbid` varchar(1369) CHARACTER SET utf8 NULL AFTER `artist`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `wanted` MODIFY `artist` int(11) NULL"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `song_preview` ADD `artist_mbid` varchar(1369) CHARACTER SET utf8 NULL AFTER `artist`"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "ALTER TABLE `song_preview` MODIFY `artist` int(11) NULL"; - Dba::write($sql); + $retval= Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -2176,9 +2315,7 @@ class Update public static function update_360039() { $sql = "ALTER TABLE `user` ADD `website` varchar(255) CHARACTER SET utf8 NULL AFTER `email`"; - Dba::write($sql); - - return true; + return Dba::write($sql); } /** @@ -2225,6 +2362,8 @@ class Update */ public static function update_360042() { + $retval = true; + $sql = "CREATE TABLE `broadcast` (" . "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . "`user` int(11) unsigned NOT NULL," . @@ -2236,7 +2375,7 @@ class Update "`listeners` int(11) unsigned NOT NULL DEFAULT '0'," . "`key` varchar(32) CHARACTER SET utf8 NULL," . "PRIMARY KEY (`id`)) ENGINE = MYISAM"; - Dba::write($sql); + $retval= Dba::write($sql) ? $retval : false; $sql = "CREATE TABLE `player_control` (" . "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . @@ -2247,8 +2386,9 @@ class Update "`object_id` int(11) unsigned NOT NULL," . "`send_date` int(11) unsigned NOT NULL DEFAULT '0'," . "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; - return Dba::write($sql); + return $retval; } /** @@ -2258,14 +2398,16 @@ class Update */ public static function update_360043() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('slideshow_time','0','Artist slideshow inactivity time',25,'integer','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2275,11 +2417,13 @@ class Update */ public static function update_360044() { + $retval = true; + $sql = "ALTER TABLE `artist` ADD `summary` TEXT CHARACTER SET utf8 NULL," . "ADD `placeformed` varchar(64) NULL," . "ADD `yearformed` int(4) NULL," . "ADD `last_update` int(11) unsigned NOT NULL DEFAULT '0'"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "CREATE TABLE `recommendation` (" . "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . @@ -2287,7 +2431,7 @@ class Update "`object_id` int(11) unsigned NOT NULL," . "`last_update` int(11) unsigned NOT NULL DEFAULT '0'," . "PRIMARY KEY (`id`)) ENGINE = MYISAM"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $sql = "CREATE TABLE `recommendation_item` (" . "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . @@ -2297,9 +2441,9 @@ class Update "`rel` varchar(256) NULL," . "`mbid` varchar(1369) NULL," . "PRIMARY KEY (`id`)) ENGINE = MYISAM"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; - return true; + return $retval; } /** @@ -2310,9 +2454,7 @@ class Update public static function update_360045() { $sql = "ALTER TABLE `playlist` MODIFY `user` int(11) NULL"; - Dba::write($sql); - - return true; + return Dba::write($sql); } /** @@ -2322,14 +2464,16 @@ class Update */ public static function update_360046() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('broadcast_by_default','0','Broadcast web player by default',25,'boolean','streaming')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2340,9 +2484,7 @@ class Update public static function update_360047() { $sql = "ALTER TABLE `user` ADD `apikey` varchar(255) CHARACTER SET utf8 NULL AFTER `website`"; - Dba::write($sql); - - return true; + return Dba::write($sql); } /** @@ -2352,21 +2494,23 @@ class Update */ public static function update_360048() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('concerts_limit_future','0','Limit number of future events',25,'integer','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('concerts_limit_past','0','Limit number of past events',25,'integer','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2376,14 +2520,16 @@ class Update */ public static function update_360049() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('album_group','0','Album - Group multiple disks',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2393,14 +2539,16 @@ class Update */ public static function update_360050() { + $retval = true; + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . "VALUES ('topmenu','0','Top menu',25,'boolean','interface')"; - Dba::write($sql); + $retval = Dba::write($sql) ? $retval : false; $id = Dba::insert_id(); $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; - Dba::write($sql, array($id)); + $retval = Dba::write($sql, array($id)) ? $retval : false; - return true; + return $retval; } /** @@ -2419,6 +2567,7 @@ class Update $htaccess_play_file = AmpConfig::get('prefix') . '/play/.htaccess'; $htaccess_rest_file = AmpConfig::get('prefix') . '/rest/.htaccess'; + $htaccess_channel_file = AmpConfig::get('prefix') . '/channel/.htaccess'; $ret = true; if (!is_readable($htaccess_play_file)) { @@ -2453,6 +2602,906 @@ class Update } } + if (!is_readable($htaccess_channel_file)) { + $created = false; + if (check_htaccess_channel_writable()) { + if (!install_rewrite_rules($htaccess_channel_file, AmpConfig::get('raw_web_path'), false)) { + Error::add('general', T_('File copy error.')); + } else { + $created = true; + } + } + + if (!$created) { + Error::add('general', T_('Cannot copy default .htaccess file.') . ' Please copy ' . $htaccess_channel_file . '.dist to ' . $htaccess_channel_file . '.'); + $ret = false; + } + } + return $ret; } + + /** + * update_370001 + * + * Drop unused dynamic_playlist tables and add session id to votes + */ + public static function update_370001() + { + $retval = true; + + $sql = "DROP TABLE dynamic_playlist"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "DROP TABLE dynamic_playlist_data"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `user_vote` ADD `sid` varchar(256) CHARACTER SET utf8 NULL AFTER `date`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('demo_clear_sessions','0','Clear democratic votes of expired user sessions',25,'boolean','playlist')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370002 + * + * Add tag persistent merge reference + */ + public static function update_370002() + { + $sql = "ALTER TABLE `tag` ADD `merged_to` int(11) NULL AFTER `name`"; + return Dba::write($sql); + } + + /** + * update_370003 + * + * Add show/hide donate button preference + */ + public static function update_370003() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('show_donate','1','Show donate button in footer',25,'boolean','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370004 + * + * Add license information and user's artist association + */ + public static function update_370004() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_catalog','-1','Uploads catalog destination',75,'integer','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'-1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('allow_upload','0','Allow users to upload media',75,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_subdir','1','Upload: create a subdirectory per user (recommended)',75,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_user_artist','0','Upload: consider the user sender as the track\'s artist',75,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_script','','Upload: run the following script after upload (current directory = upload target directory)',75,'string','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_allow_edit','1','Upload: allow users to edit uploaded songs',75,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "ALTER TABLE `artist` ADD `user` int(11) NULL AFTER `last_update`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `license` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`name` varchar(80) NOT NULL," . + "`description` varchar(256) NULL," . + "`external_link` varchar(256) NOT NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('_default', '')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY', 'https://creativecommons.org/licenses/by/3.0/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY NC', 'https://creativecommons.org/licenses/by-nc/3.0/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY NC ND', 'https://creativecommons.org/licenses/by-nc-nd/3.0/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY NC SA', 'https://creativecommons.org/licenses/by-nc-sa/3.0/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY ND', 'https://creativecommons.org/licenses/by-nd/3.0/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('CC BY SA', 'https://creativecommons.org/licenses/by-sa/3.0/')"; + $retval= Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('Licence Art Libre', 'http://artlibre.org/licence/lal/')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('Yellow OpenMusic', 'http://openmusic.linuxtag.org/yellow.html')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('Green OpenMusic', 'http://openmusic.linuxtag.org/green.html')"; + $retval= Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('Gnu GPL Art', 'http://gnuart.org/english/gnugpl.html')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('WTFPL', 'https://en.wikipedia.org/wiki/WTFPL')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('FMPL', 'http://www.fmpl.org/fmpl.html')"; + $retval = Dba::write($sql) ? $retval : false; + $sql = "INSERT INTO `license`(`name`, `external_link`) VALUES ('C Reaction', 'http://morne.free.fr/Necktar7/creaction.htm')"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `song` ADD `user_upload` int(11) NULL AFTER `addition_time`, ADD `license` int(11) NULL AFTER `user_upload`"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370005 + * + * Add new column album_artist into table album + * + */ + public static function update_370005() + { + $sql = "ALTER TABLE `song` ADD `album_artist` int(11) unsigned DEFAULT NULL AFTER `artist`"; + return Dba::write($sql); + } + + /** + * update_370006 + * + * Add random and limit options to smart playlists + * + */ + public static function update_370006() + { + $sql = "ALTER TABLE `search` ADD `random` tinyint(1) unsigned NOT NULL DEFAULT '0' AFTER `logic_operator`, ADD `limit` int(11) unsigned NOT NULL DEFAULT '0' AFTER `random`"; + return Dba::write($sql); + } + + /** + * update_370007 + * + * Add DAAP backend preference + */ + public static function update_370007() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('daap_backend','0','Use DAAP backend',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('daap_pass','','DAAP backend password',100,'string','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "CREATE TABLE `daap_session` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`creationdate` int(11) unsigned NOT NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370008 + * + * Add UPnP backend preference + * + */ + public static function update_370008() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upnp_backend','0','Use UPnP backend',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370009 + * + * Enhance video support with TVShows and Movies + */ + public static function update_370009() + { + $retval = true; + + $sql = "ALTER TABLE `video` ADD `release_date` date NULL AFTER `enabled`, " . + "ADD `played` tinyint(1) unsigned DEFAULT '1' NOT NULL AFTER `enabled`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `tvshow` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`name` varchar(80) NOT NULL," . + "`summary` varchar(256) NULL," . + "`year` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `tvshow_season` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`season_number` int(11) unsigned NOT NULL," . + "`tvshow` int(11) unsigned NOT NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `tvshow_episode` (" . + "`id` int(11) unsigned NOT NULL," . + "`original_name` varchar(80) NULL," . + "`season` int(11) unsigned NOT NULL," . + "`episode_number` int(11) unsigned NOT NULL," . + "`summary` varchar(256) NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `movie` (" . + "`id` int(11) unsigned NOT NULL," . + "`original_name` varchar(80) NULL," . + "`summary` varchar(256) NULL," . + "`year` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval= Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `personal_video` (" . + "`id` int(11) unsigned NOT NULL," . + "`location` varchar(256) NULL," . + "`summary` varchar(256) NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `clip` (" . + "`id` int(11) unsigned NOT NULL," . + "`artist` int(11) NULL," . + "`song` int(11) NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('allow_video','1','Allow video features',25,'integer','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "ALTER TABLE `image` ADD `kind` VARCHAR( 32 ) NULL DEFAULT 'default' AFTER `object_id`"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370010 + * + * Add MusicBrainz Album Release Group identifier + */ + public static function update_370010() + { + $sql = "ALTER TABLE `album` ADD `mbid_group` varchar(36) CHARACTER SET utf8 NULL"; + return Dba::write($sql); + } + + /** + * update_370011 + * + * Add Prefix to TVShows and Movies + */ + public static function update_370011() + { + $retval = true; + + $sql = "ALTER TABLE `tvshow` ADD `prefix` varchar(32) CHARACTER SET utf8 NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `movie` ADD `prefix` varchar(32) CHARACTER SET utf8 NULL"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370012 + * + * Add metadata information to albums / songs / videos + */ + public static function update_370012() + { + $retval = true; + + $sql = "ALTER TABLE `album` ADD `release_type` varchar(32) CHARACTER SET utf8 NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `song` ADD `composer` varchar(256) CHARACTER SET utf8 NULL, ADD `channels` MEDIUMINT NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `video` ADD `channels` MEDIUMINT NULL, ADD `bitrate` MEDIUMINT(8) NULL, ADD `video_bitrate` MEDIUMINT(8) NULL, ADD `display_x` MEDIUMINT(8) NULL, ADD `display_y` MEDIUMINT(8) NULL, ADD `frame_rate` FLOAT NULL, ADD `mode` ENUM( 'abr', 'vbr', 'cbr' ) NULL DEFAULT 'cbr'"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('album_release_type','1','Album - Group per release type',25,'boolean','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370013 + * + * Replace iframe with ajax page load + */ + public static function update_370013() + { + $retval = true; + + $sql = "DELETE FROM `preference` WHERE `name` = 'iframes'"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('ajax_load','1','Ajax page load',25,'boolean','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370014 + * + * Modified release_date of table video to signed int(11) + */ + public static function update_370014() + { + $retval = true; + $sql="ALTER TABLE `video` CHANGE COLUMN `release_date` `release_date` INT NULL DEFAULT NULL" ; + $retval = Dba::write($sql) ? $retval : false; + return $retval; + } + + /** + * update 370015 + * + * Add session_remember table to store remember tokens + */ + public static function update_370015() + { + $retval = true; + $sql = "CREATE TABLE `session_remember` (" . + "`username` varchar(16) NOT NULL," . + "`token` varchar(32) NOT NULL," . + "`expire` int(11) NULL," . + "PRIMARY KEY (`username`, `token`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + return $retval; + } + + /** + * update 370016 + * + * Add limit of media count for direct play preference + */ + public static function update_370016() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('direct_play_limit','0','Limit direct play to maximum media count',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370017 + * + * Add home display settings + */ + public static function update_370017() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('home_moment_albums','1','Show Albums of the moment at home page',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('home_moment_videos','1','Show Videos of the moment at home page',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('home_recently_played','1','Show Recently Played at home page',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('home_now_playing','1','Show Now Playing at home page',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('custom_logo','','Custom logo url',25,'string','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /* + * update 370018 + * + * Enhance tag persistent merge reference. + */ + public static function update_370018() + { + $retval = true; + $sql = "CREATE TABLE IF NOT EXISTS `tag_merge` ( " . + "`tag_id` int(11) NOT NULL, " . + "`merged_to` int(11) NOT NULL, " . + "FOREIGN KEY (`tag_id`) REFERENCES `tag` (`tag_id`), " . + "FOREIGN KEY (`merged_to`) REFERENCES `tag` (`tag_id`), " . + "PRIMARY KEY (`tag_id`, `merged_to`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `tag_merge` (`tag_id`, `merged_to`) " . + "SELECT `tag`.`id`, `tag`.`merged_to` " . + "FROM `tag` " . + "WHERE `merged_to` IS NOT NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `tag` DROP COLUMN `merged_to`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `tag` ADD COLUMN `is_hidden` TINYINT(1) NOT NULL DEFAULT 0"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update 370019 + * + * Add album group order setting + */ + public static function update_370019() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('album_release_type_sort','album,ep,live,single','Album - Group per release type Sort',25,'string','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'album,ep,live,single')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370020 + * + * Add webplayer browser notification settings + */ + public static function update_370020() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('browser_notify','1','WebPlayer browser notifications',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('browser_notify_timeout','10','WebPlayer browser notifications timeout (seconds)',25,'integer','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'10')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370021 + * + * Add rating to playlists, tvshows and tvshows seasons + */ + public static function update_370021() + { + $retval = true; + + $sql = "ALTER TABLE `rating` CHANGE `object_type` `object_type` ENUM ('artist','album','song','stream','video','playlist','tvshow','tvshow_season') NULL"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update 370022 + * + * Add users geolocation + */ + public static function update_370022() + { + $retval = true; + + $sql = "ALTER TABLE `session` ADD COLUMN `geo_latitude` DECIMAL(10,6) NULL, ADD COLUMN `geo_longitude` DECIMAL(10,6) NULL, ADD COLUMN `geo_name` VARCHAR(255) NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `object_count` ADD COLUMN `geo_latitude` DECIMAL(10,6) NULL, ADD COLUMN `geo_longitude` DECIMAL(10,6) NULL, ADD COLUMN `geo_name` VARCHAR(255) NULL"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('geolocation','0','Allow geolocation',25,'integer','options')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370023 + * + * Add Aurora.js webplayer option + */ + public static function update_370023() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('webplayer_aurora','1','Authorize JavaScript decoder (Aurora.js) in Web Player(s)',25,'boolean','streaming')"; + $retval = Dba::write($sql) ? $retval : false; + + $id = Dba::insert_id(); + + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update 370024 + * + * Add count_type column to object_count table + */ + public static function update_370024() + { + $retval = true; + + $sql = "ALTER TABLE `object_count` ADD COLUMN `count_type` VARCHAR(16) NOT NULL DEFAULT 'stream'"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update 370025 + * + * Add state and city fields to user table + */ + public static function update_370025() + { + $retval = true; + + $sql = "ALTER TABLE `user` ADD COLUMN `state` VARCHAR(64) NULL, ADD COLUMN `city` VARCHAR(64) NULL"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update 370026 + * + * Add replay gain fields to song_data table + */ + public static function update_370026() + { + $retval = true; + + $sql = "ALTER TABLE `song_data` ADD COLUMN `replaygain_track_gain` DECIMAL(10,6) NULL, ADD COLUMN `replaygain_track_peak` DECIMAL(10,6) NULL, " . + "ADD COLUMN `replaygain_album_gain` DECIMAL(10,6) NULL, ADD COLUMN `replaygain_album_peak` DECIMAL(10,6) NULL"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370027 + * + * Move column album_artist from table song to table album + * + */ + public static function update_370027() + { + $retval = true; + + $sql = "ALTER TABLE `album` ADD `album_artist` int(11) unsigned DEFAULT NULL AFTER `release_type`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "UPDATE `album` INNER JOIN `song` ON `album`.`id` = `song`.`album` SET `album`.`album_artist` = `song`.`album_artist`"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "ALTER TABLE `song` DROP COLUMN `album_artist`"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + + + /** + * update_370028 + * + * Add width and height in table image + * + */ + public static function update_370028() + { + $retval = true; + + $sql = "select `width` from `image`"; + $db_results = Dba::read($sql); + if (!$db_results) { + $sql = "ALTER TABLE `image` ADD `width` int(4) unsigned DEFAULT 0 AFTER `image`"; + $retval = Dba::write($sql) ? $retval : false; + } + + $sql = "select `height` from `image`"; + $db_results = Dba::read($sql); + if (!$db_results) { + $sql = "ALTER TABLE `image` ADD `height` int(4) unsigned DEFAULT 0 AFTER `width`"; + $retval = Dba::write($sql) ? $retval : false; + } + + return $retval; + } + + /** + * update_370029 + * + * Set image column from image table as nullable. + * + */ + public static function update_370029() + { + $retval = true; + + $sql = "ALTER TABLE `image` CHANGE COLUMN `image` `image` MEDIUMBLOB NULL DEFAULT NULL" ; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370030 + * + * Add an option to allow users to remove uploaded songs. + */ + public static function update_370030() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('upload_allow_remove','1','Upload: allow users to remove uploaded songs',75,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'1')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370031 + * + * Add an option to customize login art, favicon and text footer. + */ + public static function update_370031() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('custom_login_logo','','Custom login page logo url',75,'string','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('custom_favicon','','Custom favicon url',75,'string','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('custom_text_footer','','Custom text footer',75,'string','interface')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370032 + * + * Add WebDAV backend preference. + */ + public static function update_370032() + { + $retval = true; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('webdav_backend','0','Use WebDAV backend',100,'boolean','system')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370033 + * + * Add Label tables. + */ + public static function update_370033() + { + $retval = true; + + $sql = "CREATE TABLE `label` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`name` varchar(80) NOT NULL," . + "`category` varchar(40) NULL," . + "`summary` TEXT CHARACTER SET utf8 NULL," . + "`address` varchar(256) NULL," . + "`email` varchar(128) NULL," . + "`website` varchar(256) NULL," . + "`user` int(11) unsigned NULL," . + "`creation_date` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `label_asso` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`label` int(11) unsigned NOT NULL," . + "`artist` int(11) unsigned NOT NULL," . + "`creation_date` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } + + /** + * update_370034 + * + * Add User messages and user follow tables. + */ + public static function update_370034() + { + $retval = true; + + $sql = "CREATE TABLE `user_pvmsg` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`subject` varchar(80) NOT NULL," . + "`message` TEXT CHARACTER SET utf8 NULL," . + "`from_user` int(11) unsigned NOT NULL," . + "`to_user` int(11) unsigned NOT NULL," . + "`is_read` tinyint(1) unsigned NOT NULL DEFAULT '0'," . + "`creation_date` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "CREATE TABLE `user_follower` (" . + "`id` int(11) unsigned NOT NULL AUTO_INCREMENT," . + "`user` int(11) unsigned NOT NULL," . + "`follow_user` int(11) unsigned NOT NULL," . + "`follow_date` int(11) unsigned NULL," . + "PRIMARY KEY (`id`)) ENGINE = MYISAM"; + $retval = Dba::write($sql) ? $retval : false; + + $sql = "INSERT INTO `preference` (`name`,`value`,`description`,`level`,`type`,`catagory`) " . + "VALUES ('notify_email','0','Receive notifications by email (shouts, private messages, ...)',25,'boolean','options')"; + $retval = Dba::write($sql) ? $retval : false; + $id = Dba::insert_id(); + $sql = "INSERT INTO `user_preference` VALUES (-1,?,'0')"; + $retval = Dba::write($sql, array($id)) ? $retval : false; + + return $retval; + } + + /** + * update_370035 + * + * Add option on user fullname to show/hide it publicly + */ + public static function update_370035() + { + $retval = true; + + $sql = "ALTER TABLE `user` ADD COLUMN `fullname_public` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0'"; + $retval = Dba::write($sql) ? $retval : false; + + return $retval; + } } diff --git a/sources/lib/class/upload.class.php b/sources/lib/class/upload.class.php new file mode 100644 index 0000000..5943c6a --- /dev/null +++ b/sources/lib/class/upload.class.php @@ -0,0 +1,221 @@ + 0) { + $catalog = Catalog::create_from_id($catalog_id); + if ($catalog->catalog_type == "local") { + $allowed = explode('|', AmpConfig::get('catalog_file_pattern')); + + if (isset($_FILES['upl']) && $_FILES['upl']['error'] == 0) { + $extension = pathinfo($_FILES['upl']['name'], PATHINFO_EXTENSION); + + if (!in_array(strtolower($extension), $allowed)) { + debug_event('upload', 'File extension `' . $extension . '` not allowed.', '2'); + return self::rerror(); + } + + $rootdir = self::get_root($catalog); + $targetdir = $rootdir; + $folder = $_POST['folder']; + if ($folder == '..') { + $folder = ''; + } + if (!empty($folder)) { + $targetdir .= DIRECTORY_SEPARATOR . $folder; + } + + $targetdir = realpath($targetdir); + if (strpos($targetdir, $rootdir) === FALSE) { + debug_event('upload', 'Something wrong with final upload path.', '1'); + return self::rerror(); + } + + $targetfile = $targetdir . DIRECTORY_SEPARATOR . time() . '_' . $_FILES['upl']['name']; + if (Core::is_readable($targetfile)) { + debug_event('upload', 'File `' . $targetfile . '` already exists.', '1'); + return self::rerror(); + } + + if (move_uploaded_file($_FILES['upl']['tmp_name'], $targetfile)) { + debug_event('upload', 'File uploaded to `' . $targetfile . '`.', '5'); + + if (AmpConfig::get('upload_script')) { + chdir($targetdir); + exec(AmpConfig::get('upload_script')); + } + + $options = array(); + $options['user_upload'] = $GLOBALS['user']->id; + if (isset($_POST['license'])) { + $options['license'] = $_POST['license']; + } + $artist_id = intval($_REQUEST['artist']); + $album_id = intval($_REQUEST['album']); + + // Override artist information with artist's user + if (AmpConfig::get('upload_user_artist')) { + $artists = $GLOBALS['user']->get_artists(); + $artist = null; + // No associated artist yet, we create a default one for the user sender + if (count($artists) == 0) { + $artists[] = Artist::check($GLOBALS['user']->f_name); + $artist = new Artist($artists[0]); + $artist->update_artist_user($GLOBALS['user']->id); + } else { + $artist = new Artist($artists[0]); + } + $artist_id = $artist->id; + } else { + // Try to create a new artist + if (isset($_REQUEST['artist_name'])) { + $artist_id = Artist::check($_REQUEST['artist_name'], null, true); + if ($artist_id && !Access::check('interface', 50)) { + debug_event('upload', 'An artist with the same name already exists, uploaded song skipped.', 3); + return self::rerror($targetfile); + } else { + $artist_id = Artist::check($_REQUEST['artist_name']); + $artist = new Artist($artist_id); + if (!$artist->get_user_owner()) { + $artist->update_artist_user($GLOBALS['user']->id); + } + } + } + if (!Access::check('interface', 50)) { + // If the user doesn't have privileges, check it is assigned to an artist he owns + if (!$artist_id) { + debug_event('upload', 'Artist information required, uploaded song skipped.', 3); + return self::rerror($targetfile); + } + $artist = new Artist($artist_id); + if ($artist->get_user_owner() != $GLOBALS['user']->id) { + debug_event('upload', 'Artist owner doesn\'t match the current user.', 3); + return self::rerror($targetfile); + } + } + } + // Try to create a new album + if (isset($_REQUEST['album_name'])) { + $album_id = Album::check($_REQUEST['album_name'], 0, 0, null, null, $artist_id); + } + + if (!Access::check('interface', 50)) { + // If the user doesn't have privileges, check it is assigned to an album he owns + if (!$album_id) { + debug_event('upload', 'Album information required, uploaded song skipped.', 3); + return self::rerror($targetfile); + } + $album = new Album($album_id); + if ($album->get_user_owner() != $GLOBALS['user']->id) { + debug_event('upload', 'Album owner doesn\'t match the current user.', 3); + return self::rerror($targetfile); + } + } + + if ($artist_id) { + $options['artist_id'] = $artist_id; + } + if ($album_id) { + $options['album_id'] = $album_id; + } + + $catalog->add_file($targetfile, $options); + + ob_get_contents(); + ob_end_clean(); + echo '{"status":"success"}'; + return true; + } else { + debug_event('upload', 'Cannot copy the file to target directory. Please check write access.', '1'); + } + } + } else { + debug_event('upload', 'The catalog must be local to upload files on it.', '1'); + } + } else { + debug_event('upload', 'No catalog target upload configured.', '1'); + } + + return self::rerror(); + } + + public static function rerror($file = null) + { + if ($file) { + @unlink($file); + } + @header($_SERVER['SERVER_PROTOCOL'] . ' 500 File Upload Error', true, 500); + ob_get_contents(); + ob_end_clean(); + echo '{"status":"error"}'; + return false; + } + + public static function get_root($catalog = null, $username = null) + { + if ($catalog == null) { + $catalog_id = AmpConfig::get('upload_catalog'); + if ($catalog_id > 0) { + $catalog = Catalog::create_from_id($catalog_id); + } + } + + if (is_null($username)) { + $username = $GLOBALS['user']->username; + } + + $rootdir = ""; + if ($catalog != null && $catalog->id) { + $rootdir = realpath($catalog->path); + if (!empty($rootdir)) { + if (AmpConfig::get('upload_subdir')) { + $rootdir .= DIRECTORY_SEPARATOR . $username; + if (!Core::is_readable($rootdir)) { + debug_event('upload', 'Target user directory `' . $rootdir . '` doesn\'t exists. Creating it...', '5'); + mkdir($rootdir); + } + } + } + } + + return $rootdir; + } + +} // Upload class diff --git a/sources/lib/class/upnp_api.class.php b/sources/lib/class/upnp_api.class.php new file mode 100644 index 0000000..fe87275 --- /dev/null +++ b/sources/lib/class/upnp_api.class.php @@ -0,0 +1,1220 @@ +XML($prmRequest); + while ($reader->read()) { + if (($reader->nodeType == XMLReader::ELEMENT) && !$reader->isEmptyElement) { + switch ($reader->localName) { + case 'Browse': + $retArr['action'] = 'browse'; + break; + case 'Search': + $retArr['action'] = 'search'; + break; + case 'ObjectID': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['objectid'] = $reader->value; + } # end if + break; + case 'BrowseFlag': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['browseflag'] = $reader->value; + } # end if + break; + case 'Filter': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['filter'] = $reader->value; + } # end if + break; + case 'StartingIndex': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['startingindex'] = $reader->value; + } # end if + break; + case 'RequestedCount': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['requestedcount'] = $reader->value; + } # end if + break; + case 'SearchCriteria': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['searchcriteria'] = $reader->value; + } # end if + break; + case 'SortCriteria': + $reader->read(); + if ($reader->nodeType == XMLReader::TEXT) { + $retArr['sortcriteria'] = $reader->value; + } # end if + break; + } # end switch + } # end if + } #end while + return $retArr; + } #end function + + + public static function createDIDL($prmItems) + { + $xmlDoc = new DOMDocument('1.0', 'utf-8'); + $xmlDoc->formatOutput = true; + + # Create root element and add namespaces: + $ndDIDL = $xmlDoc->createElementNS('urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/', 'DIDL-Lite'); + $ndDIDL->setAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $ndDIDL->setAttribute('xmlns:upnp', 'urn:schemas-upnp-org:metadata-1-0/upnp/'); + $xmlDoc->appendChild($ndDIDL); + + # Return empty DIDL if no items present: + if ( (!isset($prmItems)) || (!is_array($prmItems)) ) { + return $xmlDoc; + } + + # sometimes here comes only one single item, not an array. Convert it to array. (TODO - UGLY) + if ( (count($prmItems) > 0) && (!is_array($prmItems[0])) ) { + $prmItems = array($prmItems); + } + + # Add each item in $prmItems array to $ndDIDL: + foreach ($prmItems as $item) { + if (!is_array($item)) { + debug_event('upnp_class', 'item is not array', 2); + debug_event('upnp_class', $item, '5'); + continue; + } + + if ($item['upnp:class'] == 'object.container') { + $ndItem = $xmlDoc->createElement('container'); + } else { + $ndItem = $xmlDoc->createElement('item'); + } + $useRes = false; + $ndRes = $xmlDoc->createElement('res'); + $ndRes_text = $xmlDoc->createTextNode($item['res']); + $ndRes->appendChild($ndRes_text); + + # Add each element / attribute in $item array to item node: + foreach ($item as $key => $value) { + # Handle attributes. Better solution? + switch ($key) { + case 'id': + $ndItem->setAttribute('id', $value); + break; + case 'parentID': + $ndItem->setAttribute('parentID', $value); + break; + case 'childCount': + $ndItem->setAttribute('childCount', $value); + break; + case 'restricted': + $ndItem->setAttribute('restricted', $value); + break; + case 'res': + break; + case 'duration': + $ndRes->setAttribute('duration', $value); + $useRes = true; + break; + case 'size': + $ndRes->setAttribute('size', $value); + $useRes = true; + break; + case 'bitrate': + $ndRes->setAttribute('bitrate', $value); + $useRes = true; + break; + case 'protocolInfo': + $ndRes->setAttribute('protocolInfo', $value); + $useRes = true; + break; + case 'resolution': + $ndRes->setAttribute('resolution', $value); + $useRes = true; + break; + case 'colorDepth': + $ndRes->setAttribute('colorDepth', $value); + $useRes = true; + break; + case 'sampleFrequency': + $ndRes->setAttribute('sampleFrequency', $value); + $useRes = true; + break; + case 'nrAudioChannels': + $ndRes->setAttribute('nrAudioChannels', $value); + $useRes = true; + break; + default: + $ndTag = $xmlDoc->createElement($key); + $ndItem->appendChild($ndTag); + # check if string is already utf-8 encoded + $ndTag_text = $xmlDoc->createTextNode((mb_detect_encoding($value,'auto')=='UTF-8')?$value:utf8_encode($value)); + $ndTag->appendChild($ndTag_text); + } + if ($useRes) { + $ndItem->appendChild($ndRes); + } + } + $ndDIDL->appendChild($ndItem); + } + return $xmlDoc; + } + + + public static function createSOAPEnvelope($prmDIDL, $prmNumRet, $prmTotMatches, $prmResponseType = 'u:BrowseResponse', $prmUpdateID = '0') + { + # $prmDIDL is DIDL XML string + # XML-Layout: + # + # -s:Envelope + # -s:Body + # -u:BrowseResponse + # Result (DIDL) + # NumberReturned + # TotalMatches + # UpdateID + # + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + $ndEnvelope = $doc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 's:Envelope'); + $ndEnvelope->setAttribute("s:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/"); + $doc->appendChild($ndEnvelope); + $ndBody = $doc->createElement('s:Body'); + $ndEnvelope->appendChild($ndBody); + $ndBrowseResp = $doc->createElementNS('urn:schemas-upnp-org:service:ContentDirectory:1', $prmResponseType); + $ndBody->appendChild($ndBrowseResp); + $ndResult = $doc->createElement('Result',$prmDIDL); + $ndBrowseResp->appendChild($ndResult); + $ndNumRet = $doc->createElement('NumberReturned', $prmNumRet); + $ndBrowseResp->appendChild($ndNumRet); + $ndTotMatches = $doc->createElement('TotalMatches', $prmTotMatches); + $ndBrowseResp->appendChild($ndTotMatches); + $ndUpdateID = $doc->createElement('UpdateID', $prmUpdateID); # seems to be ignored by the WDTVL + #$ndUpdateID = $doc->createElement('UpdateID', (string) mt_rand(); # seems to be ignored by the WDTVL + $ndBrowseResp->appendChild($ndUpdateID); + + Return $doc; + } + + public static function _musicMetadata($prmPath, $prmQuery = '') + { + $root = 'amp://music'; + $pathreq = explode('/', $prmPath); + if ($pathreq[0] == '' && count($pathreq) > 0) { + array_shift($pathreq); + } + + $meta = null; + switch ($pathreq[0]) { + case 'artists': + switch (count($pathreq)) { + case 1: + $counts = Catalog::count_medias(); + $meta = array( + 'id' => $root . '/artists', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts['artists'], + 'dc:title' => T_('Artists'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $artist = new Artist($pathreq[1]); + if ($artist->id) { + $artist->format(); + $meta = self::_itemArtist($artist, $root . '/artists'); + } + break; + } + break; + + case 'albums': + switch (count($pathreq)) { + case 1: + $counts = Catalog::count_medias(); + $meta = array( + 'id' => $root . '/albums', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts['albums'], + 'dc:title' => T_('Albums'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $album = new Album($pathreq[1]); + if ($album->id) { + $album->format(); + $meta = self::_itemAlbum($album, $root . '/albums'); + } + break; + } + break; + + case 'songs': + switch (count($pathreq)) { + case 1: + $counts = Catalog::count_medias(); + $meta = array( + 'id' => $root . '/songs', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts['songs'], + 'dc:title' => T_('Songs'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $song = new Song($pathreq[1]); + if ($song->id) { + $song->format(); + $meta = self::_itemSong($song, $root . '/songs'); + } + break; + } + break; + + case 'playlists': + switch (count($pathreq)) { + case 1: + $counts = Catalog::count_medias(); + $meta = array( + 'id' => $root . '/playlists', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts['playlists'], + 'dc:title' => T_('Playlists'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $playlist = new Playlist($pathreq[1]); + if ($playlist->id) { + $playlist->format(); + $meta = self::_itemPlaylist($playlist, $root . '/playlists'); + } + break; + } + break; + + case 'smartplaylists': + switch (count($pathreq)) { + case 1: + $counts = Catalog::count_medias(); + $meta = array( + 'id' => $root . '/smartplaylists', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts['smartplaylists'], + 'dc:title' => T_('Smart Playlists'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $playlist = new Search($pathreq[1], 'song'); + if ($playlist->id) { + $playlist->format(); + $meta = self::_itemSmartPlaylist($playlist, $root . '/smartplaylists'); + } + break; + } + break; + + default: + $meta = array( + 'id' => $root, + 'parentID' => '0', + 'restricted' => '1', + 'childCount' => '5', + 'dc:title' => T_('Music'), + 'upnp:class' => 'object.container', + ); + break; + } + + return $meta; + } + + private static function _slice($items, $start, $count) + { + $maxCount = count($items); + debug_event('upnp-api', 'slice: ' . $maxCount . " " . $start . " " . $count, '5'); + return array($maxCount, array_slice($items, $start, ($count == 0 ? $maxCount - $start : $count))); + } + + public static function _musicChilds($prmPath, $prmQuery, $start, $count) + { + $mediaItems = array(); + $maxCount = 0; + $queryData = array(); + parse_str($prmQuery, $queryData); + + $parent = 'amp://music' . $prmPath; + $pathreq = explode('/', $prmPath); + if ($pathreq[0] == '' && count($pathreq) > 0) { + array_shift($pathreq); + } + + switch ($pathreq[0]) { + case 'artists': + switch (count($pathreq)) { + case 1: // Get artists list + //$artists = Catalog::get_artists(); + //list($maxCount, $artists) = self::_slice($artists, $start, $count); + $artists = Catalog::get_artists(null, $count, $start); + list($maxCount, $artists) = array(999999, $artists); + foreach ($artists as $artist) { + $artist->format(); + $mediaItems[] = self::_itemArtist($artist, $parent); + } + break; + case 2: // Get artist's albums list + $artist = new Artist($pathreq[1]); + if ($artist->id) { + $album_ids = $artist->get_albums(); + list($maxCount, $album_ids) = self::_slice($album_ids, $start, $count); + foreach ($album_ids as $album_id) { + $album = new Album($album_id); + $album->format(); + $mediaItems[] = self::_itemAlbum($album, $parent); + } + } + break; + } + break; + + case 'albums': + switch (count($pathreq)) { + case 1: // Get albums list + //!!$album_ids = Catalog::get_albums(); + //!!list($maxCount, $album_ids) = self::_slice($album_ids, $start, $count); + $album_ids = Catalog::get_albums($count, $start); + list($maxCount, $album_ids) = array(999999, $album_ids); + foreach ($album_ids as $album_id) { + $album = new Album($album_id); + $album->format(); + $mediaItems[] = self::_itemAlbum($album, $parent); + } + break; + case 2: // Get album's songs list + $album = new Album($pathreq[1]); + if ($album->id) { + $song_ids = $album->get_songs(); + list($maxCount, $song_ids) = self::_slice($song_ids, $start, $count); + foreach ($song_ids as $song_id) { + $song = new Song($song_id); + $song->format(); + $mediaItems[] = self::_itemSong($song, $parent); + } + } + break; + } + break; + + case 'songs': + switch (count($pathreq)) { + case 1: // Get songs list + $catalogs = Catalog::get_catalogs(); + foreach ($catalogs as $catalog_id) { + $catalog = Catalog::create_from_id($catalog_id); + $songs = $catalog->get_songs(); + list($maxCount, $songs) = self::_slice($songs, $start, $count); + foreach ($songs as $song) { + $song->format(); + $mediaItems[] = self::_itemSong($song, $parent); + } + } + break; + } + break; + + case 'playlists': + switch (count($pathreq)) { + case 1: // Get playlists list + $pl_ids = Playlist::get_playlists(); + list($maxCount, $pl_ids) = self::_slice($pl_ids, $start, $count); + foreach ($pl_ids as $pl_id) { + $playlist = new Playlist($pl_id); + $playlist->format(); + $mediaItems[] = self::_itemPlaylist($playlist, $parent); + } + break; + case 2: // Get playlist's songs list + $playlist = new Playlist($pathreq[1]); + if ($playlist->id) { + $items = $playlist->get_items(); + list($maxCount, $items) = self::_slice($items, $start, $count); + foreach ($items as $item) { + if ($item['object_type'] == 'song') { + $song = new Song($item['object_id']); + $song->format(); + $mediaItems[] = self::_itemSong($song, $parent); + } + } + } + break; + } + break; + + case 'smartplaylists': + switch (count($pathreq)) { + case 1: // Get playlists list + $pl_ids = Search::get_searches(); + list($maxCount, $pl_ids) = self::_slice($pl_ids, $start, $count); + foreach ($pl_ids as $pl_id) { + $playlist = new Search($pl_id, 'song'); + $playlist->format(); + $mediaItems[] = self::_itemPlaylist($playlist, $parent); + } + break; + case 2: // Get playlist's songs list + $playlist = new Search($pathreq[1], 'song'); + if ($playlist->id) { + $items = $playlist->get_items(); + list($maxCount, $items) = self::_slice($items, $start, $count); + foreach ($items as $item) { + if ($item['object_type'] == 'song') { + $song = new Song($item['object_id']); + $song->format(); + $mediaItems[] = self::_itemSong($song, $parent); + } + } + } + break; + } + break; + + default: + $mediaItems[] = self::_musicMetadata('artists'); + $mediaItems[] = self::_musicMetadata('albums'); + $mediaItems[] = self::_musicMetadata('songs'); + $mediaItems[] = self::_musicMetadata('playlists'); + $mediaItems[] = self::_musicMetadata('smartplaylists'); + break; + } + + if ($maxCount == 0) + $maxCount = count($mediaItems); + return array($maxCount, $mediaItems); + } + + public static function _videoMetadata($prmPath, $prmQuery = '') + { + $root = 'amp://video'; + $pathreq = explode('/', $prmPath); + if ($pathreq[0] == '' && count($pathreq) > 0) { + array_shift($pathreq); + } + + $meta = null; + switch ($pathreq[0]) { + case 'tvshows': + switch (count($pathreq)) { + case 1: + $counts = count(Catalog::get_tvshows()); + $meta = array( + 'id' => $root . '/tvshows', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts, + 'dc:title' => T_('TV Shows'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $tvshow = new TVShow($pathreq[1]); + if ($tvshow->id) { + $tvshow->format(); + $meta = self::_itemTVShow($tvshow, $root . '/tvshows'); + } + break; + + case 3: + $season = new TVShow_Season($pathreq[2]); + if ($season->id) { + $season->format(); + $meta = self::_itemTVShowSeason($season, $root . '/tvshows/' . $pathreq[1]); + } + break; + + case 4: + $video = new TVShow_Episode($pathreq[3]); + if ($video->id) { + $video->format(); + $meta = self::_itemVideo($video, $root . '/tvshows/' . $pathreq[1] . '/' . $pathreq[2] ); + } + break; + } + break; + + case 'clips': + switch (count($pathreq)) { + case 1: + $counts = Catalog::get_videos_count(null, 'clip'); + $meta = array( + 'id' => $root . '/clips', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts, + 'dc:title' => T_('Clips'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $video = new Clip($pathreq[1]); + if ($video->id) { + $video->format(); + $meta = self::_itemVideo($video, $root . '/clips'); + } + break; + } + break; + + case 'movies': + switch (count($pathreq)) { + case 1: + $counts = Catalog::get_videos_count(null, 'movie'); + $meta = array( + 'id' => $root . '/movies', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts, + 'dc:title' => T_('Movies'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $video = new Movie($pathreq[1]); + if ($video->id) { + $video->format(); + $meta = self::_itemVideo($video, $root . '/movies'); + } + break; + } + break; + + case 'personal_videos': + switch (count($pathreq)) { + case 1: + $counts = Catalog::get_videos_count(null, 'personal_video'); + $meta = array( + 'id' => $root . '/personal_videos', + 'parentID' => $root, + 'restricted' => '1', + 'childCount' => $counts, + 'dc:title' => T_('Personal Videos'), + 'upnp:class' => 'object.container', + ); + break; + + case 2: + $video = new Personal_Video($pathreq[1]); + if ($video->id) { + $video->format(); + $meta = self::_itemVideo($video, $root . '/personal_videos'); + } + break; + } + break; + + default: + $meta = array( + 'id' => $root, + 'parentID' => '0', + 'restricted' => '1', + 'childCount' => '4', + 'dc:title' => T_('Video'), + 'upnp:class' => 'object.container', + ); + break; + } + + return $meta; + } + + public static function _videoChilds($prmPath, $prmQuery, $start, $count) + { + $mediaItems = array(); + $maxCount = 0; + $queryData = array(); + parse_str($prmQuery, $queryData); + + $parent = 'amp://video' . $prmPath; + $pathreq = explode('/', $prmPath); + if ($pathreq[0] == '' && count($pathreq) > 0) { + array_shift($pathreq); + } + + switch ($pathreq[0]) { + case 'tvshows': + switch (count($pathreq)) { + case 1: // Get tvshow list + $tvshows = Catalog::get_tvshows(); + list($maxCount, $tvshows) = self::_slice($tvshows, $start, $count); + foreach ($tvshows as $tvshow) { + $tvshow->format(); + $mediaItems[] = self::_itemTVShow($tvshow, $parent); + } + break; + case 2: // Get season list + $tvshow = new TVShow($pathreq[1]); + if ($tvshow->id) { + $season_ids = $tvshow->get_seasons(); + list($maxCount, $season_ids) = self::_slice($season_ids, $start, $count); + foreach ($season_ids as $season_id) { + $season = new TVShow_Season($season_id); + $season->format(); + $mediaItems[] = self::_itemTVShowSeason($season, $parent); + } + } + break; + case 3: // Get episode list + $season = new TVShow_Season($pathreq[2]); + if ($season->id) { + $episode_ids = $season->get_episodes(); + list($maxCount, $episode_ids) = self::_slice($episode_ids, $start, $count); + foreach ($episode_ids as $episode_id) { + $video = new Video($episode_id); + $video->format(); + $mediaItems[] = self::_itemVideo($video, $parent); + } + } + break; + } + break; + + case 'clips': + switch (count($pathreq)) { + case 1: // Get clips list + $videos = Catalog::get_videos(null, 'clip'); + list($maxCount, $videos) = self::_slice($videos, $start, $count); + foreach ($videos as $video) { + $video->format(); + $mediaItems[] = self::_itemVideo($video, $parent); + } + break; + } + break; + + case 'movies': + switch (count($pathreq)) { + case 1: // Get clips list + $videos = Catalog::get_videos(null, 'movie'); + list($maxCount, $videos) = self::_slice($videos, $start, $count); + foreach ($videos as $video) { + $video->format(); + $mediaItems[] = self::_itemVideo($video, $parent); + } + break; + } + break; + + case 'personal_videos': + switch (count($pathreq)) { + case 1: // Get clips list + $videos = Catalog::get_videos(null, 'personal_video'); + list($maxCount, $videos) = self::_slice($videos, $start, $count); + foreach ($videos as $video) { + $video->format(); + $mediaItems[] = self::_itemVideo($video, $parent); + } + break; + } + break; + + default: + $mediaItems[] = self::_videoMetadata('clips'); + $mediaItems[] = self::_videoMetadata('tvshows'); + $mediaItems[] = self::_videoMetadata('movies'); + $mediaItems[] = self::_videoMetadata('personal_videos'); + break; + } + + if ($maxCount == 0) + $maxCount = count($mediaItems); + return array($maxCount, $mediaItems); + } + + public static function _callSearch($criteria) + { + // Not supported yet + return array(); + } + + private static function _replaceSpecialSymbols($title) + { + ///debug_event('upnp_class', 'replace <<< ' . $title, 5); + // replace non letter or digits + $title = preg_replace('~[^\\pL\d\.\s\(\)\.\,\'\"]+~u', '-', $title); + ///debug_event('upnp_class', 'replace >>> ' . $title, 5); + + if ($title == "") + $title = '(no title)'; + + return $title; + } + + private static function _itemArtist($artist, $parent) + { + return array( + 'id' => 'amp://music/artists/' . $artist->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => $artist->albums, + 'dc:title' => self::_replaceSpecialSymbols($artist->f_name), + 'upnp:class' => 'object.container', // object.container.person.musicArtist + ); + } + + private static function _itemAlbum($album, $parent) + { + $api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false; + $art_url = Art::url($album->id, 'album', $api_session); + + return array( + 'id' => 'amp://music/albums/' . $album->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => $album->song_count, + 'dc:title' => self::_replaceSpecialSymbols($album->f_title), + 'upnp:class' => 'object.container', // object.container.album.musicAlbum + 'upnp:albumArtURI' => $art_url, + ); + } + + private static function _itemPlaylist($playlist, $parent) + { + return array( + 'id' => 'amp://music/playlists/' . $playlist->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => count($playlist->get_items()), + 'dc:title' => self::_replaceSpecialSymbols($playlist->f_name), + 'upnp:class' => 'object.container', // object.container.playlistContainer + ); + } + + private static function _itemSmartPlaylist($playlist, $parent) + { + return array( + 'id' => 'amp://music/smartplaylists/' . $playlist->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => count($playlist->get_items()), + 'dc:title' => self::_replaceSpecialSymbols($playlist->f_name), + 'upnp:class' => 'object.container', + ); + } + + public static function _itemSong($song, $parent) + { + $api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false; + $art_url = Art::url($song->album, 'album', $api_session); + + $fileTypesByExt = self::_getFileTypes(); + $arrFileType = $fileTypesByExt[$song->type]; + + return array( + 'id' => 'amp://music/songs/' . $song->id, + 'parentID' => $parent, + 'restricted' => '1', + 'dc:title' => self::_replaceSpecialSymbols($song->f_title), + 'upnp:class' => (isset($arrFileType['class'])) ? $arrFileType['class'] : 'object.item.unknownItem', + 'upnp:albumArtURI' => $art_url, + 'upnp:artist' => self::_replaceSpecialSymbols($song->f_artist), + 'upnp:album' => self::_replaceSpecialSymbols($song->f_album), + 'upnp:genre' => Tag::get_display($song->tags, false, 'song'), + //'dc:date' => date("c", $song->addition_time), + 'upnp:originalTrackNumber' => $song->track, + + 'res' => Song::play_url($song->id, '', 'api'), + 'protocolInfo' => $arrFileType['mime'], + 'size' => $song->size, + 'duration' => $song->f_time_h . '.0', + 'bitrate' => $song->bitrate, + 'sampleFrequency' => $song->rate, + //'nrAudioChannels' => '1', + 'description' => self::_replaceSpecialSymbols($song->comment), + ); + } + + private static function _itemTVShow($tvshow, $parent) + { + return array( + 'id' => 'amp://video/tvshows/' . $tvshow->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => count($tvshow->get_seasons()), + 'dc:title' => self::_replaceSpecialSymbols($tvshow->f_name), + 'upnp:class' => 'object.container', + ); + } + + private static function _itemTVShowSeason($season, $parent) + { + return array( + 'id' => 'amp://video/tvshows/' . $season->tvshow . '/' . $season->id, + 'parentID' => $parent, + 'restricted' => '1', + 'childCount' => count($season->get_episodes()), + 'dc:title' => self::_replaceSpecialSymbols($season->f_name), + 'upnp:class' => 'object.container', + ); + } + + private static function _itemVideo($video, $parent) + { + $api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false; + $art_url = Art::url($video->id, 'video', $api_session); + + $fileTypesByExt = self::_getFileTypes(); + $arrFileType = $fileTypesByExt[$video->type]; + + return array( + 'id' => $parent . '/' . $video->id, + 'parentID' => $parent, + 'restricted' => '1', + 'dc:title' => self::_replaceSpecialSymbols($video->f_title), + 'upnp:class' => (isset($arrFileType['class'])) ? $arrFileType['class'] : 'object.item.unknownItem', + 'upnp:albumArtURI' => $art_url, + 'upnp:genre' => Tag::get_display($video->tags, false, 'video'), + + 'res' => Video::play_url($video->id, '', 'api'), + 'protocolInfo' => $arrFileType['mime'], + 'size' => $video->size, + 'duration' => $video->f_time_h . '.0', + ); + } + + private static function _getFileTypes() + { + return array( + 'wav' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-wav:*', + ), + 'mpa' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/mpeg:*', + ), + '.mp1' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/mpeg:*', + ), + 'mp3' => array( + 'class' => 'object.item.audioItem.musicTrack', + 'mime' => 'http-get:*:audio/mpeg:*', + ), + 'aiff' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-aiff:*', + ), + 'aif' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-aiff:*', + ), + 'wma' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-ms-wma:*', + ), + 'lpcm' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/lpcm:*', + ), + 'aac' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-aac:*', + ), + 'm4a' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-m4a:*', + ), + 'ac3' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-ac3:*', + ), + 'pcm' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/lpcm:*', + ), + 'flac' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/flac:*', + ), + 'ogg' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:application/ogg:*', + ), + 'mka' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-matroska:*', + ), + 'mp4a' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/x-m4a:*', + ), + 'mp2' => array( + 'class' => 'object.item.audioItem', + 'mime' => 'http-get:*:audio/mpeg:*', + ), + 'gif' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/gif:*', + ), + 'jpg' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/jpeg:*', + ), + 'jpe' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/jpeg:*', + ), + 'png' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/png:*', + ), + 'tiff' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/tiff:*', + ), + 'tif' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/tiff:*', + ), + 'jpeg' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/jpeg:*', + ), + 'bmp' => array( + 'class' => 'object.item.imageItem', + 'mime' => 'http-get:*:image/bmp:*', + ), + 'asf' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-ms-asf:*', + ), + 'wmv' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-ms-wmv:*', + ), + 'mpeg2' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2:*', + ), + 'avi' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-msvideo:*', + ), + 'divx' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-msvideo:*', + ), + 'mpg' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'm1v' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'm2v' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'mp4' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mp4:*', + ), + 'mov' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/quicktime:*', + ), + 'vob' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/dvd:*', + ), + 'dvr-ms' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-ms-dvr:*', + ), + 'dat' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'mpeg' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'm1s' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg:*', + ), + 'm2p' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2:*', + ), + 'm2t' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'm2ts' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'mts' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'ts' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'tp' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'trp' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'm4t' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2ts:*', + ), + 'm4v' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/MP4V-ES:*', + ), + 'vbs' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2:*', + ), + 'mod' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mpeg2:*', + ), + 'mkv' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/x-matroska:*', + ), + '3g2' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mp4:*', + ), + '3gp' => array( + 'class' => 'object.item.videoItem', + 'mime' => 'http-get:*:video/mp4:*', + ), + ); + } +} diff --git a/sources/lib/class/user.class.php b/sources/lib/class/user.class.php index 33f4bfc..3149f0d 100644 --- a/sources/lib/class/user.class.php +++ b/sources/lib/class/user.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -31,30 +31,113 @@ class User extends database_object { //Basic Componets + /** + * @var int $id + */ public $id; + /** + * @var string $username + */ public $username; + /** + * @var string $fullname + */ public $fullname; + /** + * @var boolean $fullname_public + */ + public $fullname_public; + /** + * @var int $access + */ public $access; + /** + * @var boolean $disabled + */ public $disabled; + /** + * @var string $email + */ public $email; + /** + * @var int $last_seen + */ public $last_seen; + /** + * @var int $create_date + */ public $create_date; + /** + * @var string $validation + */ public $validation; + /** + * @var string $website + */ public $website; + /** + * @var string $state + */ + public $state; + /** + * @var string city + */ + public $city; + /** + * @var string $apikey + */ public $apikey; // Constructed variables + /** + * @var array $prefs + */ public $prefs = array(); + + /** + * @var Tmp_Playlist $playlist + */ public $playlist; + /** + * @var string $f_name + */ + public $f_name; + /** + * @var string $f_last_seen + */ public $f_last_seen; + /** + * @var string $f_create_date + */ public $f_create_date; + /** + * @var string $link + */ public $link; + /** + * @var string $f_link + */ public $f_link; + /** + * @var string $f_useage + */ public $f_useage; + /** + * @var string $ip_history + */ public $ip_history; + /** + * @var string $f_avatar + */ public $f_avatar; + /** + * @var string $f_avatar_mini + */ public $f_avatar_mini; + /** + * @var string $f_avatar_medium + */ public $f_avatar_medium; /** @@ -472,7 +555,7 @@ class User extends database_object */ public function has_access($needed_level) { - if (!AmpConfig::get('use_auth') || AmpConfig::get('demo_mode')) { return true; } + if (AmpConfig::get('demo_mode')) { return true; } if ($this->access >= $needed_level) { return true; } @@ -480,13 +563,27 @@ class User extends database_object } // has_access + /** + * is_registered + * Check if the user is registered + * @return boolean + */ + public static function is_registered() + { + if (!$GLOBALS['user']->id) return false; + + if (!AmpConfig::get('use_auth') && $GLOBALS['user']->access <= 5) return false; + + return true; + } + /** * update * This function is an all encompasing update function that * calls the mini ones does all the error checking and all that * good stuff */ - public function update($data) + public function update(array $data) { if (empty($data['username'])) { Error::add('username', T_('Error Username Required')); @@ -500,6 +597,10 @@ class User extends database_object return false; } + if (!isset($data['fullname_public'])) { + $data['fullname_public'] = false; + } + foreach ($data as $name => $value) { if ($name == 'password1') { $name = 'password'; @@ -513,19 +614,25 @@ class User extends database_object case 'email': case 'username': case 'fullname': + case 'fullname_public': case 'website': + case 'state': + case 'city': if ($this->$name != $value) { $function = 'update_' . $name; $this->$function($value); } break; + case 'clear_stats': + Stats::clear($this->id); + break; default: // Rien a faire break; } } - return true; + return $this->id; } /** @@ -567,6 +674,17 @@ class User extends database_object } // update_fullname + /** + * update_fullname_public + * updates their fullname public + */ + public function update_fullname_public($new_fullname_public) + { + $sql = "UPDATE `user` SET `fullname_public` = ? WHERE `id` = ?"; + Dba::write($sql, array($new_fullname_public ? '1' : '0', $this->id)); + + } // update_fullname_public + /** * update_email * updates their email address @@ -590,6 +708,26 @@ class User extends database_object } // update_website + /** + * update_state + * updates their state + */ + public function update_state($new_state) + { + $sql = "UPDATE `user` SET `state` = ? WHERE `id` = ?"; + Dba::write($sql, array($new_state, $this->id)); + } // update_state + + /** + * update_city + * updates their city + */ + public function update_city($new_city) + { + $sql = "UPDATE `user` SET `city` = ? WHERE `id` = ?"; + Dba::write($sql, array($new_city, $this->id)); + } // update_city + /** * update_apikey * Updates their api key @@ -694,46 +832,47 @@ class User extends database_object * update_user_stats * updates the playcount mojo for this specific user */ - public function update_stats($song_id, $agent = '') + public function update_stats($media_type, $media_id, $agent = '', $location = array(), $noscrobble = false) { - debug_event('user.class.php', 'Updating stats for {'.$song_id.'} {'.$agent.'}...', '5'); - $song_info = new Song($song_id); - $song_info->format(); + debug_event('user.class.php', 'Updating stats for {'.$media_type.'/'.$media_id.'} {'.$agent.'}...', 5); + $media = new $media_type($media_id); + $media->format(); $user = $this->id; - if (!strlen($song_info->file)) { return false; } + // We shouldn't test on file only + if (!strlen($media->file)) { return false; } - $this->set_preferences(); - - // If pthreads available, we call save_songplay in a new thread to quickly return - if (class_exists("Thread", false)) { - debug_event('user.class.php', 'Calling save_songplay plugins in a new thread...', '5'); - $thread = new scrobbler_async($GLOBALS['user'], $song_info); - if ($thread->start()) { - //$thread->join(); + if (!$noscrobble) { + $this->set_preferences(); + // If pthreads available, we call save_songplay in a new thread to quickly return + if (class_exists("Thread", false)) { + debug_event('user.class.php', 'Calling save_mediaplay plugins in a new thread...', 5); + $thread = new scrobbler_async($GLOBALS['user'], $media); + if ($thread->start()) { + //$thread->join(); + } else { + debug_event('user.class.php', 'Error when starting the thread.', 1); + } } else { - debug_event('user.class.php', 'Error when starting the thread.', '1'); + User::save_mediaplay($GLOBALS['user'], $media); } } else { - User::save_songplay($GLOBALS['user'], $song_info); + debug_event('user.class.php', 'Scrobbling explicitly skipped', 5); } - // Do this last so the 'last played checks are correct' - Stats::insert('song', $song_id, $user, $agent); - Stats::insert('album', $song_info->album, $user, $agent); - Stats::insert('artist', $song_info->artist, $user, $agent); + $media->set_played($user, $agent, $location); return true; } // update_stats - public static function save_songplay($user, $song_info) + public static function save_mediaplay($user, $media) { - foreach (Plugin::get_plugins('save_songplay') as $plugin_name) { + foreach (Plugin::get_plugins('save_mediaplay') as $plugin_name) { try { $plugin = new Plugin($plugin_name); if ($plugin->load($user)) { - $plugin->_plugin->save_songplay($song_info); + $plugin->_plugin->save_mediaplay($media); } } catch (Exception $e) { debug_event('user.class.php', 'Stats plugin error: ' . $e->getMessage(), '1'); @@ -779,7 +918,7 @@ class User extends database_object * create * inserts a new user into ampache */ - public static function create($username, $fullname, $email, $website, $password, $access, $disabled = false) + public static function create($username, $fullname, $email, $website, $password, $access, $state = '', $city = '', $disabled = false) { $website = rtrim($website, "/"); $password = hash('sha256', $password); @@ -789,14 +928,32 @@ class User extends database_object $sql = "INSERT INTO `user` (`username`, `disabled`, " . "`fullname`, `email`, `password`, `access`, `create_date`"; $params = array($username, $disabled, $fullname, $email, $password, $access, time()); + if (!empty($website)) { $sql .= ", `website`"; $params[] = $website; } + if (!empty($state)) { + $sql .= ", `state`"; + $params[] = $state; + } + if (!empty($city)) { + $sql .= ", `city`"; + $params[] = $city; + } + $sql .= ") VALUES(?, ?, ?, ?, ?, ?, ?"; + if (!empty($website)) { $sql .= ", ?"; } + if (!empty($state)) { + $sql .= ", ?"; + } + if (!empty($city)) { + $sql .= ", ?"; + } + $sql .= ")"; $db_results = Dba::write($sql, $params); @@ -835,7 +992,7 @@ class User extends database_object * user for an admin, these should not be normally called when creating a * user object */ - public function format() + public function format($details = true) { /* If they have a last seen date */ if (!$this->last_seen) { $this->f_last_seen = T_('Never'); } else { $this->f_last_seen = date("m\/d\/Y - H:i",$this->last_seen); } @@ -843,26 +1000,31 @@ class User extends database_object /* If they have a create date */ if (!$this->create_date) { $this->f_create_date = T_('Unknown'); } else { $this->f_create_date = date("m\/d\/Y - H:i",$this->create_date); } + $this->f_name = ($this->fullname_public ? $this->fullname : $this->username); + // Base link - $this->f_link = '' . $this->fullname . ''; + $this->link = AmpConfig::get('web_path') . '/stats.php?action=show_user&user_id=' . $this->id; + $this->f_link = '' . $this->f_name . ''; - /* Calculate their total Bandwidth Usage */ - $sql = "SELECT `song`.`size` FROM `song` LEFT JOIN `object_count` ON `song`.`id`=`object_count`.`object_id` " . - "WHERE `object_count`.`user`='$this->id' AND `object_count`.`object_type`='song'"; - $db_results = Dba::read($sql); + if ($details) { + /* Calculate their total Bandwidth Usage */ + $sql = "SELECT `song`.`size` FROM `song` LEFT JOIN `object_count` ON `song`.`id`=`object_count`.`object_id` " . + "WHERE `object_count`.`user`='$this->id' AND `object_count`.`object_type`='song'"; + $db_results = Dba::read($sql); - $total = 0; - while ($r = Dba::fetch_assoc($db_results)) { - $total = $total + $r['size']; - } + $total = 0; + while ($r = Dba::fetch_assoc($db_results)) { + $total = $total + $r['size']; + } - $this->f_useage = UI::format_bytes($total); + $this->f_useage = UI::format_bytes($total); - /* Get Users Last ip */ - if (count($data = $this->get_ip_history(1))) { - $this->ip_history = inet_ntop($data['0']['ip']); - } else { - $this->ip_history = T_('Not Enough Data'); + /* Get Users Last ip */ + if (count($data = $this->get_ip_history(1))) { + $this->ip_history = inet_ntop($data['0']['ip']); + } else { + $this->ip_history = T_('Not Enough Data'); + } } $avatar = $this->get_avatar(); @@ -967,23 +1129,6 @@ class User extends database_object } } // while preferences - /* Let's also clean out any preferences garbage left over */ - $sql = "SELECT DISTINCT(user_preference.user) FROM user_preference " . - "LEFT JOIN user ON user_preference.user = user.id " . - "WHERE user_preference.user!='-1' AND user.id IS NULL"; - $db_results = Dba::read($sql); - - $results = array(); - - while ($r = Dba::fetch_assoc($db_results)) { - $results[] = $r['user']; - } - - foreach ($results as $data) { - $sql = "DELETE FROM user_preference WHERE user='$data'"; - Dba::write($sql); - } - } // fix_preferences /** @@ -998,16 +1143,16 @@ class User extends database_object admin */ if ($this->has_access(100)) { - $sql = "SELECT `id` FROM `user` WHERE `access`='100' AND id !='" . Dba::escape($this->id) . "'"; - $db_results = Dba::read($sql); + $sql = "SELECT `id` FROM `user` WHERE `access`='100' AND id != ?"; + $db_results = Dba::read($sql, array($this->id)); if (!Dba::num_rows($db_results)) { return false; } } // if this is an admin check for others // Delete their playlists - $sql = "DELETE FROM `playlist` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `playlist` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Clean up the playlist data table $sql = "DELETE FROM `playlist_data` USING `playlist_data` " . @@ -1016,47 +1161,55 @@ class User extends database_object Dba::write($sql); // Delete any stats they have - $sql = "DELETE FROM `object_count` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `object_count` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Clear the IP history for this user - $sql = "DELETE FROM `ip_history` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `ip_history` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Nuke any access lists that are specific to this user - $sql = "DELETE FROM `access_list` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `access_list` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Delete their ratings - $sql = "DELETE FROM `rating` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `rating` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Delete their tags - $sql = "DELETE FROM `tag_map` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `tag_map` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Clean out the tags $sql = "DELETE FROM `tags` USING `tag_map` LEFT JOIN `tag_map` ON tag_map.id=tags.map_id AND tag_map.id IS NULL"; Dba::write($sql); // Delete their preferences - $sql = "DELETE FROM `user_preference` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `user_preference` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Delete their voted stuff in democratic play - $sql = "DELETE FROM `user_vote` WHERE `user`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `user_vote` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); // Delete their shoutbox posts - $sql = "DELETE FROM `user_shout` WHERE `user='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `user_shout` WHERE `user` = ?"; + Dba::write($sql, array($this->id)); + + // Delete their private messages posts + $sql = "DELETE FROM `user_pvmsg` WHERE `from_user` = ? OR `to_user` = ?"; + Dba::write($sql, array($this->id, $this->id)); + + // Delete their following/followers + $sql = "DELETE FROM `user_follow` WHERE `user` = ? OR `follow_user` = ?"; + Dba::write($sql, array($this->id, $this->id)); // Delete the user itself - $sql = "DELETE FROM `user` WHERE `id`='$this->id'"; - Dba::write($sql); + $sql = "DELETE FROM `user` WHERE `id` = ?"; + Dba::write($sql, array($this->id)); - $sql = "DELETE FROM `session` WHERE `username`='" . Dba::escape($this->username) . "'"; - Dba::write($sql); + $sql = "DELETE FROM `session` WHERE `username` = ?"; + Dba::write($sql, array($this->username)); return true; @@ -1098,9 +1251,9 @@ class User extends database_object { if (!$type) { $type = 'song'; } - $sql = "SELECT * FROM `object_count` WHERE `object_type`='$type' AND `user`='$this->id' " . - "ORDER BY `date` DESC LIMIT $limit"; - $db_results = Dba::read($sql); + $sql = "SELECT * FROM `object_count` WHERE `object_type` = ? AND `user` = ? " . + "ORDER BY `date` DESC LIMIT " . $limit; + $db_results = Dba::read($sql, array($type, $this->id)); $results = array(); while ($row = Dba::fetch_assoc($db_results)) { @@ -1148,17 +1301,17 @@ class User extends database_object * get_avatar * Get the user avatar */ - public function get_avatar() + public function get_avatar($local = false) { $avatar = array(); $avatar['title'] = T_('User avatar'); $upavatar = new Art($this->id, 'user'); if ($upavatar->get_db()) { - $avatar['url'] = AmpConfig::get('web_path') . '/image.php?object_type=user&id=' . $this->id; + $avatar['url'] = ($local ? AmpConfig::get('local_web_path') : AmpConfig::get('web_path')) . '/image.php?object_type=user&object_id=' . $this->id; $avatar['url_mini'] = $avatar['url']; $avatar['url_medium'] = $avatar['url']; - $avatar['url'] .= '&thumb=3'; + $avatar['url'] .= '&thumb=4'; $avatar['url_mini'] .= '&thumb=5'; $avatar['url_medium'] .= '&thumb=3'; } else { @@ -1182,7 +1335,7 @@ class User extends database_object public function upload_avatar() { $upload = array(); - if (!empty($_FILES['avatar']['tmp_name'])) { + if (!empty($_FILES['avatar']['tmp_name']) && $_FILES['avatar']['size'] <= AmpConfig::get('max_upload_size')) { $path_info = pathinfo($_FILES['avatar']['name']); $upload['file'] = $_FILES['avatar']['tmp_name']; $upload['mime'] = 'image/' . $path_info['extension']; @@ -1214,6 +1367,23 @@ class User extends database_object } // activate_user + /** + * get_artists + * Get artists associated with the user + */ + public function get_artists() + { + $sql = "SELECT `id` FROM `artist` WHERE `user` = ?"; + $db_results = Dba::read($sql, array($this->id)); + + $results = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['id']; + } + + return $results; + } + /** * is_xmlrpc * checks to see if this is a valid xmlrpc user @@ -1232,6 +1402,108 @@ class User extends database_object } // is_xmlrpc + /** + * get_followers + * Get users following this user + * @return int[] + */ + public function get_followers() + { + $sql = "SELECT `user` FROM `user_follower` WHERE `follow_user` = ?"; + $db_results = Dba::read($sql, array($this->id)); + $results = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['user']; + } + return $results; + } + + /** + * get_following + * Get users followed by this user + * @return int[] + */ + public function get_following() + { + $sql = "SELECT `follow_user` FROM `user_follower` WHERE `user` = ?"; + $db_results = Dba::read($sql, array($this->id)); + $results = array(); + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['follow_user']; + } + return $results; + } + + /** + * is_followed_by + * Get if an user is followed by this user + * @param integer $user_id + * @return boolean + */ + public function is_followed_by($user_id) + { + $sql = "SELECT `id` FROM `user_follower` WHERE `user` = ? AND `follow_user` = ?"; + $db_results = Dba::read($sql, array($user_id, $this->id)); + return (Dba::num_rows($db_results) > 0); + } + + /** + * is_following + * Get if this user is following an user + * @param integer $user_id + * @return boolean + */ + public function is_following($user_id) + { + $sql = "SELECT `id` FROM `user_follower` WHERE `user` = ? AND `follow_user` = ?"; + $db_results = Dba::read($sql, array($this->id, $user_id)); + return (Dba::num_rows($db_results) > 0); + } + + /** + * toggle_follow + * @param integer $user_id + * @return boolean + */ + public function toggle_follow($user_id) + { + if (!$user_id || $user_id === $this->id) + return false; + + $params = array($this->id, $user_id); + if ($this->is_following($user_id)) { + $sql = "DELETE FROM `user_follower` WHERE `user` = ? AND `follow_user` = ?"; + } else { + $sql = "INSERT INTO `user_follower` (`user`, `follow_user`, `follow_date`) VALUES (?, ?, ?)"; + $params[] = time(); + } + + return Dba::write($sql, $params); + } + + /** + * get_display_follow + * Get html code to display the follow/unfollow link + * @param $display_user_id int|null + * @return string + */ + public function get_display_follow($user_id = null) + { + if (!$user_id) { + $user_id = $GLOBALS['user']->id; + } + + if ($user_id === $this->id) + return ""; + + $followed = $this->is_followed_by($user_id); + + $html = ""; + return $html; + } + /** * check_username * This checks to make sure the username passed doesn't already @@ -1258,17 +1530,49 @@ class User extends database_object */ public static function rebuild_all_preferences() { - $sql = "SELECT * FROM `user`"; + // Clean out any preferences garbage left over + $sql = "DELETE `user_preference`.* FROM `user_preference` " . + "LEFT JOIN `user` ON `user_preference`.`user` = `user`.`id` " . + "WHERE `user_preference`.`user` != -1 AND `user`.`id` IS NULL"; + Dba::write($sql); + + // Get only users who has less preferences than excepted + // otherwise it would have significant performance issue with large user database + $sql = "SELECT `user` FROM `user_preference` " . + "GROUP BY `user` HAVING COUNT(*) < (" . + "SELECT COUNT(`id`) FROM `preference` WHERE `catagory` != 'system')"; $db_results = Dba::read($sql); - - User::fix_preferences('-1'); - while ($row = Dba::fetch_assoc($db_results)) { - User::fix_preferences($row['id']); + User::fix_preferences($row['user']); } return true; } // rebuild_all_preferences + /** + * stream_control + * Check all stream control plugins + * @param array $media_ids + * @param User $user + * @return boolean + */ + public static function stream_control($media_ids, User $user = null) + { + if ($user == null) { + $user = $GLOBALS['user']; + } + + foreach (Plugin::get_plugins('stream_control') as $plugin_name) { + $plugin = new Plugin($plugin_name); + if ($plugin->load($user)) { + if (!$plugin->_plugin->stream_control($media_ids)) { + return false; + } + } + } + + return true; + } + } //end user class diff --git a/sources/lib/class/userflag.class.php b/sources/lib/class/userflag.class.php index fae0869..bb1535f 100644 --- a/sources/lib/class/userflag.class.php +++ b/sources/lib/class/userflag.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,7 +23,7 @@ /** * Userflag class * - * This user flag/unflag songs, albums and artists as favorite. + * This user flag/unflag songs, albums, artists, videos, tvshows, movies ... as favorite. * */ class Userflag extends database_object @@ -89,10 +89,21 @@ class Userflag extends database_object * * Remove userflag for items that no longer exist. */ - public static function gc() + public static function gc($object_type = null, $object_id = null) { - foreach (array('song', 'album', 'artist', 'video') as $object_type) { - Dba::write("DELETE FROM `user_flag` USING `user_flag` LEFT JOIN `$object_type` ON `$object_type`.`id` = `user_flag`.`object_id` WHERE `object_type` = '$object_type' AND `$object_type`.`id` IS NULL"); + $types = array('song', 'album', 'artist', 'video', 'tvshow', 'tvshow_season'); + + if ($object_type != null) { + if (in_array($object_type, $types)) { + $sql = "DELETE FROM `user_flag` WHERE `object_type` = ? AND `object_id` = ?"; + Dba::write($sql, array($object_type, $object_id)); + } else { + debug_event('userflag', 'Garbage collect on type `' . $object_type . '` is not supported.', 1); + } + } else { + foreach ($types as $type) { + Dba::write("DELETE FROM `user_flag` USING `user_flag` LEFT JOIN `$type` ON `$type`.`id` = `user_flag`.`object_id` WHERE `object_type` = '$type' AND `$type`.`id` IS NULL"); + } } } @@ -165,21 +176,36 @@ class Userflag extends database_object $user_id = $GLOBALS['user']->id; } $user_id = intval($user_id); - $type = Stats::validate_type($type); - $sql = "SELECT `object_id` as `id` FROM user_flag" . - " WHERE object_type = '" . $type . "' AND `user` = '" . $user_id . "'"; - if (AmpConfig::get('catalog_disable')) { - $sql .= " AND " . Catalog::get_enable_filter($type, '`object_id`'); + $sql = "SELECT `user_flag`.`object_id` as `id`, `user_flag`.`object_type` as `type`, `user_flag`.`user` as `user` FROM `user_flag`"; + if ($user_id <= 0) { + // Get latest only from user rights >= content manager + $sql .= " LEFT JOIN `user` ON `user`.`id` = `user_flag`.`user`" . + " WHERE `user`.`access` >= 50"; } - $sql .= " ORDER BY `date` DESC "; + if (!is_null($type)) { + if ($user_id <= 0) { + $sql .= " AND"; + } else { + $sql .= " WHERE"; + } + $type = Stats::validate_type($type); + $sql .= " `user_flag`.`object_type` = '" . $type . "'"; + if ($user_id > 0) { + $sql .= " AND `user_flag`.`user` = '" . $user_id . "'"; + } + if (AmpConfig::get('catalog_disable')) { + $sql .= " AND " . Catalog::get_enable_filter($type, '`object_id`'); + } + } + $sql .= " ORDER BY `user_flag`.`date` DESC "; return $sql; } /** * get_latest * Get the latest user flagged objects */ - public static function get_latest($type, $user_id=null, $count='', $offset='') + public static function get_latest($type=null, $user_id=null, $count='', $offset='') { if (!$count) { $count = AmpConfig::get('popular_threshold'); @@ -194,12 +220,16 @@ class Userflag extends database_object /* Select Top objects counting by # of rows */ $sql = self::get_latest_sql($type, $user_id); $sql .= "LIMIT $limit"; - $db_results = Dba::read($sql, array($type, $user_id)); + $db_results = Dba::read($sql); $results = array(); while ($row = Dba::fetch_assoc($db_results)) { - $results[] = $row['id']; + if (is_null($type)) { + $results[] = $row; + } else { + $results[] = $row['id']; + } } return $results; diff --git a/sources/lib/class/vainfo.class.php b/sources/lib/class/vainfo.class.php index c5c5ab3..2f5d361 100644 --- a/sources/lib/class/vainfo.class.php +++ b/sources/lib/class/vainfo.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -36,9 +36,10 @@ class vainfo public $type = ''; public $tags = array(); public $islocal; + public $gather_types = array(); protected $_raw = array(); - protected $_getID3 = ''; + protected $_getID3 = null; protected $_forcedSize = 0; protected $_file_encoding = ''; @@ -55,10 +56,11 @@ class vainfo * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function __construct($file, $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true) + public function __construct($file, $gather_types = array(), $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true) { $this->islocal = $islocal; $this->filename = $file; + $this->gather_types = $gather_types; $this->encoding = $encoding ?: AmpConfig::get('site_charset'); /* These are needed for the filename mojo */ @@ -84,6 +86,7 @@ class vainfo $this->_getID3->option_extra_info = true; $this->_getID3->option_tag_lyrics3 = true; $this->_getID3->option_tags_process = true; + $this->_getID3->option_tag_apetag = true; $this->_getID3->encoding = $this->encoding; // get id3tag encoding (try to work around off-spec id3v1 tags) @@ -92,6 +95,7 @@ class vainfo } catch (Exception $error) { debug_event('getID3', "Broken file detected: $file: " . $error->getMessage(), 1); $this->_broken = true; + return false; } @@ -128,7 +132,7 @@ class vainfo } $this->encoding_id3v2 = self::_detect_encoding($tags, $mb_order); - $this->_getID3->encoding_id3v2 = $this->encoding_id3v2; + $this->_getID3->encoding = $this->encoding_id3v2; } $this->_getID3->encoding_id3v1 = $this->encoding_id3v1; @@ -148,33 +152,40 @@ class vainfo */ private static function _detect_encoding($tags, $mb_order) { - if (function_exists('mb_detect_encoding')) { - $encodings = array(); - if (is_array($tags)) { - foreach ($tags as $tag) { - $encodings[mb_detect_encoding($tag, $mb_order, true)]++; - } - } + if (!function_exists('mb_detect_encoding')) + return 'ISO-8859-1'; - debug_event('vainfo', 'encoding detection: ' . json_encode($encodings), 5); - $high = 0; - $encoding = ''; - foreach ($encodings as $key => $value) { - if ($value > $high) { - $encoding = $key; - $high = $value; - } + $encodings = array(); + if (is_array($tags)) { + foreach ($tags as $tag) { + if (is_array($tag)) + $tag = implode(" ", $tag); + $enc = mb_detect_encoding($tag, $mb_order, true); + if ($enc != false) + $encodings[$enc]++; } + } else { + $enc = mb_detect_encoding($tags, $mb_order, true); + if ($enc != false) + $encodings[$enc]++; + } - if ($encoding != 'ASCII' && $encoding != '0') { - return $encoding; - } else { - return 'ISO-8859-1'; + //!!debug_event('vainfo', 'encoding detection: ' . json_encode($encodings), 5); + $high = 0; + $encoding = 'ISO-8859-1'; + foreach ($encodings as $key => $value) { + if ($value > $high) { + $encoding = $key; + $high = $value; } } - return 'ISO-8859-1'; - } + if ($encoding != 'ASCII') { + return (string) $encoding; + } else { + return 'ISO-8859-1'; + } + } /** * get_info @@ -187,6 +198,7 @@ class vainfo // time, just return their rotting carcass of a media file. if ($this->_broken) { $this->tags = $this->set_broken(); + return true; } @@ -201,7 +213,7 @@ class vainfo /* Figure out what type of file we are dealing with */ $this->type = $this->_get_type(); - $enabled_sources = (array) AmpConfig::get('metadata_order'); + $enabled_sources = (array) $this->get_metadata_order(); if (in_array('filename', $enabled_sources)) { $this->tags['filename'] = $this->_parse_filename($this->filename); @@ -212,9 +224,69 @@ class vainfo } $this->_get_plugin_tags(); - } // get_info + /* + * write_id3 + * This function runs the various steps to gathering the metadata + */ + public function write_id3($data) + { + // Get the Raw file information + $this->read_id3(); + if (isset($this->_raw['tags']['id3v2'])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'write.php', __FILE__, true); + $tagWriter = new getid3_writetags(); + $tagWriter->filename = $this->filename; + //'id3v2.4' doesn't saves the year; + $tagWriter->tagformats = array('id3v1', 'id3v2.3'); + $tagWriter->overwrite_tags = true; + $tagWriter->remove_other_tags = true; + $tagWriter->tag_encoding = 'UTF-8'; + $TagData = $this->_raw['tags']['id3v2']; + + // Foreach what we've got + foreach ($data as $key=>$value) { + if ($key != 'APIC') { + $TagData[$key][0] = $value; + } + } + + if (isset($data['APIC'])) { + $TagData['attached_picture'][0]['data'] = $data['APIC']['data']; + $TagData['attached_picture'][0]['picturetypeid'] = '3'; + $TagData['attached_picture'][0]['description'] = 'Cover'; + $TagData['attached_picture'][0]['mime'] = $data['APIC']['mime']; + } + + $tagWriter->tag_data = $TagData; + + if ($tagWriter->WriteTags()) { + if (!empty($tagWriter->warnings)) { + debug_event('vainfo' , 'FWarnings ' . implode("\n", $tagWriter->warnings), 5); + } + } else { + debug_event('vainfo' , 'Failed to write tags! ' . implode("\n", $tagWriter->errors), 5); + } + } + } // write_id3 + + /** + * read_id3 + * This function runs the various steps to gathering the metadata + */ + public function read_id3() + { + // Get the Raw file information + try { + $this->_raw = $this->_getID3->analyze($this->filename); + + return $this->_raw; + } catch (Exception $e) { + debug_event('vainfo', "Unable to read file:" . $e->getMessage(), '1'); + } + } // read_id3 + /** * get_tag_type * @@ -261,6 +333,7 @@ class vainfo public static function clean_tag_info($results, $keys, $filename = null) { $info = array(); + //debug_event('vainfo', 'Clean tag info: ' . print_r($results, true), '5'); $info['file'] = $filename; @@ -279,6 +352,8 @@ class vainfo $info['time'] = $info['time'] ?: intval($tags['time']); $info['channels'] = $info['channels'] ?: $tags['channels']; + // This because video title are almost always bad... + $info['original_name'] = $info['original_name'] ?: stripslashes(trim($tags['original_name'])); $info['title'] = $info['title'] ?: stripslashes(trim($tags['title'])); $info['year'] = $info['year'] ?: intval($tags['year']); @@ -288,39 +363,53 @@ class vainfo $info['totaldisks'] = $info['totaldisks'] ?: intval($tags['totaldisks']); $info['artist'] = $info['artist'] ?: trim($tags['artist']); + $info['albumartist'] = $info['albumartist'] ?: trim($tags['albumartist']); $info['album'] = $info['album'] ?: trim($tags['album']); - // multiple genre support - if ((!$info['genre']) && $tags['genre']) { - if (!is_array($tags['genre'])) { - // not all tag formats will return an array, but we need one - $info['genre'][] = trim($tags['genre']); - } else { - // if we trim the array we lose everything after 1st entry - foreach ($tags['genre'] as $genre) { - $info['genre'][] = trim($genre); - } - } - } + $info['band'] = $info['band'] ?: trim($tags['band']); + $info['composer'] = $info['composer'] ?: trim($tags['composer']); + $info['publisher'] = $info['publisher'] ?: trim($tags['publisher']); + + $info['genre'] = self::clean_array_tag('genre', $info, $tags); $info['mb_trackid'] = $info['mb_trackid'] ?: trim($tags['mb_trackid']); $info['mb_albumid'] = $info['mb_albumid'] ?: trim($tags['mb_albumid']); + $info['mb_albumid_group'] = $info['mb_albumid_group'] ?: trim($tags['mb_albumid_group']); $info['mb_artistid'] = $info['mb_artistid'] ?: trim($tags['mb_artistid']); + $info['mb_albumartistid'] = $info['mb_albumartistid'] ?: trim($tags['mb_albumartistid']); + $info['release_type'] = $info['release_type'] ?: trim($tags['release_type']); $info['language'] = $info['language'] ?: trim($tags['language']); + $info['comment'] = $info['comment'] ?: trim($tags['comment']); $info['lyrics'] = $info['lyrics'] - ?: str_replace( - array("\r\n","\r","\n"), - '
', - strip_tags($tags['lyrics'])); + ?: strip_tags(nl2br($tags['lyrics']), "
"); + $info['replaygain_track_gain'] = $info['replaygain_track_gain'] ?: floatval($tags['replaygain_track_gain']); + $info['replaygain_track_peak'] = $info['replaygain_track_peak'] ?: floatval($tags['replaygain_track_peak']); + $info['replaygain_album_gain'] = $info['replaygain_album_gain'] ?: floatval($tags['replaygain_album_gain']); + $info['replaygain_album_peak'] = $info['replaygain_album_peak'] ?: floatval($tags['replaygain_album_peak']); $info['track'] = $info['track'] ?: intval($tags['track']); $info['resolution_x'] = $info['resolution_x'] ?: intval($tags['resolution_x']); $info['resolution_y'] = $info['resolution_y'] ?: intval($tags['resolution_y']); + $info['display_x'] = $info['display_x'] ?: intval($tags['display_x']); + $info['display_y'] = $info['display_y'] ?: intval($tags['display_y']); + $info['frame_rate'] = $info['frame_rate'] ?: floatval($tags['frame_rate']); + $info['video_bitrate'] = $info['video_bitrate'] ?: intval($tags['video_bitrate']); $info['audio_codec'] = $info['audio_codec'] ?: trim($tags['audio_codec']); $info['video_codec'] = $info['video_codec'] ?: trim($tags['video_codec']); + $info['description'] = $info['description'] ?: trim($tags['description']); + + $info['tvshow'] = $info['tvshow'] ?: trim($tags['tvshow']); + $info['tvshow_year'] = $info['tvshow_year'] ?: trim($tags['tvshow_year']); + $info['tvshow_season'] = $info['tvshow_season'] ?: trim($tags['tvshow_season']); + $info['tvshow_episode'] = $info['tvshow_episode'] ?: trim($tags['tvshow_episode']); + $info['release_date'] = $info['release_date'] ?: trim($tags['release_date']); + + $info['tvshow_art'] = $info['tvshow_art'] ?: trim($tags['tvshow_art']); + $info['tvshow_season_art'] = $info['tvshow_season_art'] ?: trim($tags['tvshow_season_art']); + $info['art'] = $info['art'] ?: trim($tags['art']); } // Some things set the disk number even though there aren't multiple @@ -332,6 +421,25 @@ class vainfo return $info; } + private static function clean_array_tag($field, $info, $tags) + { + $arr = array(); + if ((!$info[$field] || count($info[$field]) == 0) && $tags[$field]) { + if (!is_array($tags[$field])) { + // not all tag formats will return an array, but we need one + $arr[] = trim($tags[$field]); + } else { + foreach ($tags[$field] as $genre) { + $arr[] = trim($genre); + } + } + } else { + $arr = $info[$field]; + } + + return $arr; + } + /** * _get_type * @@ -370,7 +478,6 @@ class vainfo // The tags can come in many different shapes and colors // depending on the encoding time of day and phase of the moon. - if (is_array($this->_raw['tags'])) { foreach ($this->_raw['tags'] as $key => $tag_array) { switch ($key) { @@ -435,6 +542,20 @@ class vainfo return $cleaned; } + private function get_metadata_order_key() + { + if (!in_array('music', $this->gather_types)) { + return 'metadata_order_video'; + } + + return 'metadata_order'; + } + + private function get_metadata_order() + { + return (array) AmpConfig::get($this->get_metadata_order_key()); + } + /** * _get_plugin_tags * @@ -442,7 +563,7 @@ class vainfo */ private function _get_plugin_tags() { - $tag_order = AmpConfig::get('metadata_order'); + $tag_order = $this->get_metadata_order(); if (!is_array($tag_order)) { $tag_order = array($tag_order); } @@ -451,8 +572,11 @@ class vainfo foreach ($tag_order as $tag_source) { if (in_array($tag_source, $plugin_names)) { $plugin = new Plugin($tag_source); - if ($plugin->load($GLOBALS['user'])) { - $this->tags[$tag_source] = $plugin->_plugin->get_metadata(self::clean_tag_info($this->tags, self::get_tag_type($this->tags), $this->filename)); + $installed_version = Plugin::get_plugin_version($plugin->_plugin->name); + if ($installed_version) { + if ($plugin->load($GLOBALS['user'])) { + $this->tags[$tag_source] = $plugin->_plugin->get_metadata($this->gather_types, self::clean_tag_info($this->tags, self::get_tag_type($this->tags, $this->get_metadata_order_key()), $this->filename)); + } } } } @@ -476,18 +600,42 @@ class vainfo $parsed['bitrate'] = $tags['audio']['bitrate']; $parsed['channels'] = intval($tags['audio']['channels']); $parsed['rate'] = intval($tags['audio']['sample_rate']); - $parsed['size'] = $this->_forcedSize ?: intval($tags['filesize']); + $parsed['size'] = $this->_forcedSize ?: $tags['filesize']; $parsed['encoding'] = $tags['encoding']; $parsed['mime'] = $tags['mime_type']; $parsed['time'] = ($this->_forcedSize ? ((($this->_forcedSize - $tags['avdataoffset']) * 8) / $tags['bitrate']) : $tags['playtime_seconds']); - $parsed['video_codec'] = $tags['video']['fourcc']; $parsed['audio_codec'] = $tags['audio']['dataformat']; + $parsed['video_codec'] = $tags['video']['dataformat']; $parsed['resolution_x'] = $tags['video']['resolution_x']; $parsed['resolution_y'] = $tags['video']['resolution_y']; + $parsed['display_x'] = $tags['video']['display_x']; + $parsed['display_y'] = $tags['video']['display_y']; + $parsed['frame_rate'] = $tags['video']['frame_rate']; + $parsed['video_bitrate'] = $tags['video']['bitrate']; + + if (isset($tags['ape'])) { + if (isset($tags['ape']['items'])) { + foreach ($tags['ape']['items'] as $key => $tag) { + switch (strtolower($key)) { + case 'replaygain_track_gain': + case 'replaygain_track_peak': + case 'replaygain_album_gain': + case 'replaygain_album_peak': + $parsed[$key] = floatval($tag['data'][0]); + break; + } + } + } + } return $parsed; } + private function trimAscii($string) + { + return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', trim($string)); + } + /** * _clean_type * This standardizes the type that we are given into a recognized type. @@ -513,6 +661,7 @@ class vainfo default: /* Log the fact that we couldn't figure it out */ debug_event('vainfo','Unable to determine file type from ' . $type . ' on file ' . $this->filename,'5'); + return $type; } } @@ -526,11 +675,29 @@ class vainfo { $parsed = array(); foreach ($tags as $tagname => $data) { - switch ($tagname) { + switch (strtolower($tagname)) { case 'genre': // Pass the array through - $parsed[$tagname] = $data; - break; + $parsed[$tagname] = $this->parseGenres($data); + break; + case 'musicbrainz_artistid': + $parsed['mb_artistid'] = $data[0]; + break; + case 'musicbrainz_albumid': + $parsed['mb_albumid'] = $data[0]; + break; + case 'musicbrainz_albumartistid': + $parsed['mb_albumartistid'] = $data[0]; + break; + case 'musicbrainz_releasegroupid': + $parsed['mb_albumid_group'] = $data[0]; + break; + case 'musicbrainz_trackid': + $parsed['mb_trackid'] = $data[0]; + break; + case 'musicbrainz_albumtype': + $parsed['release_type'] = $data[0]; + break; default: $parsed[$tagname] = $data[0]; break; @@ -550,11 +717,12 @@ class vainfo $parsed = array(); foreach ($tags as $tag => $data) { - if ($tag == 'unsynchedlyrics' || $tag == 'unsynchronised lyric') { + if ($tag == 'unsyncedlyrics' || $tag == 'unsynced lyrics' || $tag == 'unsynchronised lyric') { $tag = 'lyrics'; } $parsed[$tag] = $data[0]; } + return $parsed; } @@ -568,11 +736,11 @@ class vainfo $parsed = array(); foreach ($tags as $tag => $data) { - switch ($tag) { + switch (strtolower($tag)) { case 'genre': // Pass the array through - $parsed[$tag] = $data; - break; + $parsed[$tag] = $this->parseGenres($data); + break; case 'tracknumber': $parsed['track'] = $data[0]; break; @@ -584,6 +752,29 @@ class vainfo case 'date': $parsed['year'] = $data[0]; break; + case 'musicbrainz_artistid': + $parsed['mb_artistid'] = $data[0]; + break; + case 'musicbrainz_albumid': + $parsed['mb_albumid'] = $data[0]; + break; + case 'musicbrainz_albumartistid': + $parsed['mb_albumartistid'] = $data[0]; + break; + case 'musicbrainz_releasegroupid': + $parsed['mb_albumid_group'] = $data[0]; + break; + case 'musicbrainz_trackid': + $parsed['mb_trackid'] = $data[0]; + break; + case 'musicbrainz_albumtype': + $parsed['release_type'] = $data[0]; + break; + case 'unsyncedlyrics': + case 'unsynced lyrics': + case 'lyrics': + $parsed['lyrics'] = $data[0]; + break; default: $parsed[$tag] = $data[0]; break; @@ -621,12 +812,10 @@ class vainfo $parsed = array(); foreach ($tags as $tag => $data) { - switch ($tag) { case 'genre': - // Pass the array through - $parsed['genre'] = $data; - break; + $parsed['genre'] = $this->parseGenres($data); + break; case 'part_of_a_set': $elements = explode('/', $data[0]); $parsed['disk'] = $elements[0]; @@ -638,6 +827,9 @@ class vainfo case 'comments': $parsed['comment'] = $data[0]; break; + case 'unsynchronised_lyric': + $parsed['lyrics'] = $data[0]; + break; default: $parsed[$tag] = $data[0]; break; @@ -657,47 +849,44 @@ class vainfo if (!empty($id3v2['TXXX'])) { // Find the MBIDs for the album and artist + // Use trimAscii to remove noise (see #225 and #438 issues). Is this a GetID3 bug? foreach ($id3v2['TXXX'] as $txxx) { - switch ($txxx['description']) { - case 'MusicBrainz Album Id': - $parsed['mb_albumid'] = $txxx['data']; + switch (strtolower($this->trimAscii($txxx['description']))) { + case 'musicbrainz album id': + $parsed['mb_albumid'] = $this->trimAscii($txxx['data']); break; - case 'MusicBrainz Artist Id': - $parsed['mb_artistid'] = $txxx['data']; + case 'musicbrainz release group id': + $parsed['mb_albumid_group'] = $this->trimAscii($txxx['data']); + break; + case 'musicbrainz artist id': + $parsed['mb_artistid'] = $this->trimAscii($txxx['data']); + break; + case 'musicbrainz album artist id': + $parsed['mb_albumartistid'] = $this->trimAscii($txxx['data']); + break; + case 'musicbrainz album type': + $parsed['release_type'] = $this->trimAscii($txxx['data']); + break; + case 'catalognumber': + $parsed['catalog_number'] = $this->trimAscii($txxx['data']); + break; + case 'replaygain_track_gain': + $parsed['replaygain_track_gain'] = floatval($txxx['data']); + break; + case 'replaygain_track_peak': + $parsed['replaygain_track_peak'] = floatval($txxx['data']); + break; + case 'replaygain_album_gain': + $parsed['replaygain_album_gain'] = floatval($txxx['data']); + break; + case 'replaygain_album_peak': + $parsed['replaygain_album_peak'] = floatval($txxx['data']); break; } } } } - // Find all genre - if (!empty($id3v2['TCON'])) { - // Find the MBID for the track - foreach ($id3v2['TCON'] as $tcid) { - if ($tcid['framenameshort'] == "genre") { - // Removing unwanted UTF-8 charaters - $tcid['data'] = str_replace("\xFF", "", $tcid['data']); - $tcid['data'] = str_replace("\xFE", "", $tcid['data']); - - if (!empty($tcid['data'])) { - // Parsing string with the null character - $genres = explode("\0", $tcid['data']); - $parsed_genres = array(); - foreach ($genres as $g) { - if (strlen($g) > 2) { // Only allow tags with at least 3 characters - $parsed_genres[] = $g; - } - } - - if (count($parsed_genres)) { - $parsed['genre'] = $parsed_genres; - } - } - } - break; - } - } - // Find the rating if (is_array($id3v2['POPM'])) { foreach ($id3v2['POPM'] as $popm) { @@ -745,9 +934,9 @@ class vainfo foreach ($tags as $tag => $data) { switch ($tag) { case 'creation_date': + $parsed['release_date'] = strtotime(str_replace(" ", "", $data[0])); if (strlen($data['0']) > 4) { - // Weird date format, attempt to normalize it - $data[0] = date('Y', strtotime($data[0])); + $data[0] = date('Y', $parsed['release_date']); } $parsed['year'] = $data[0]; break; @@ -757,9 +946,36 @@ class vainfo case 'MusicBrainz Album Id': $parsed['mb_albumid'] = $data[0]; break; + case 'MusicBrainz Album Artist Id': + $parsed['mb_albumartistid'] = $data[0]; + break; + case 'MusicBrainz Release Group Id': + $parsed['mb_albumid_group'] = $data[0]; + break; case 'MusicBrainz Artist Id': $parsed['mb_artistid'] = $data[0]; break; + case 'MusicBrainz Album Type': + $parsed['release_type'] = $data[0]; + break; + case 'track_number': + $parsed['track'] = $data[0]; + break; + case 'disc_number': + $parsed['disk'] = $data[0]; + break; + case 'album_artist': + $parsed['albumartist'] = $data[0]; + break; + case 'tv_episode': + $parsed['tvshow_episode'] = $data[0]; + break; + case 'tv_season': + $parsed['tvshow_season'] = $data[0]; + break; + case 'tv_show_name': + $parsed['tvshow'] = $data[0]; + break; default: $parsed[$tag] = $data[0]; break; @@ -780,66 +996,214 @@ class vainfo $origin = $filename; $results = array(); - // Correctly detect the slash we need to use here - if (strpos($filename, '/') !== false) { - $slash_type = '/'; - $slash_type_preg = $slash_type; - } else { - $slash_type = '\\'; - $slash_type_preg = $slash_type . $slash_type; - } - - // Combine the patterns - $pattern = preg_quote($this->_dir_pattern) . $slash_type_preg . preg_quote($this->_file_pattern); - - // Remove first left directories from filename to match pattern - $cntslash = substr_count($pattern, $slash_type) + 1; - $filepart = explode($slash_type, $filename); - if (count($filepart) > $cntslash) { - $filename = implode($slash_type, array_slice($filepart, count($filepart) - $cntslash)); - } - - // Pull out the pattern codes into an array - preg_match_all('/\%\w/', $pattern, $elements); - - // Mangle the pattern by turning the codes into regex captures - $pattern = preg_replace('/\%[Ty]/', '([0-9]+?)', $pattern); - $pattern = preg_replace('/\%\w/', '(.+?)', $pattern); - $pattern = str_replace('/', '\/', $pattern); - $pattern = str_replace(' ', '\s', $pattern); - $pattern = '/' . $pattern . '\..+$/'; - - // Pull out our actual matches - preg_match($pattern, $filename, $matches); - - if ($matches != null) { - // The first element is the full match text - $matched = array_shift($matches); - debug_event('vainfo', $pattern . ' matched ' . $matched . ' on ' . $filename, 5); - - // Iterate over what we found - foreach ($matches as $key => $value) { - $new_key = translate_pattern_code($elements['0'][$key]); - if ($new_key) { - $results[$new_key] = $value; - } + if (in_array('music', $this->gather_types) || in_array('clip', $this->gather_types)) { + // Correctly detect the slash we need to use here + if (strpos($filename, '/') !== false) { + $slash_type = '/'; + $slash_type_preg = $slash_type; + } else { + $slash_type = '\\'; + $slash_type_preg = $slash_type . $slash_type; } - $results['title'] = $results['title'] ?: basename($filename); - if ($this->islocal) { - $results['size'] = filesize(Core::conv_lc_file($origin)); + // Combine the patterns + $pattern = preg_quote($this->_dir_pattern) . $slash_type_preg . preg_quote($this->_file_pattern); + + // Remove first left directories from filename to match pattern + $cntslash = substr_count($pattern, preg_quote($slash_type)) + 1; + $filepart = explode($slash_type, $filename); + if (count($filepart) > $cntslash) { + $filename = implode($slash_type, array_slice($filepart, count($filepart) - $cntslash)); + } + + // Pull out the pattern codes into an array + preg_match_all('/\%\w/', $pattern, $elements); + + // Mangle the pattern by turning the codes into regex captures + $pattern = preg_replace('/\%[Ty]/', '([0-9]+?)', $pattern); + $pattern = preg_replace('/\%\w/', '(.+?)', $pattern); + $pattern = str_replace('/', '\/', $pattern); + $pattern = str_replace(' ', '\s', $pattern); + $pattern = '/' . $pattern . '\..+$/'; + + // Pull out our actual matches + preg_match($pattern, $filename, $matches); + if ($matches != null) { + // The first element is the full match text + $matched = array_shift($matches); + debug_event('vainfo', $pattern . ' matched ' . $matched . ' on ' . $filename, 5); + + // Iterate over what we found + foreach ($matches as $key => $value) { + $new_key = translate_pattern_code($elements['0'][$key]); + if ($new_key) { + $results[$new_key] = $value; + } + } + + $results['title'] = $results['title'] ?: basename($filename); + if ($this->islocal) { + $results['size'] = Core::get_filesize(Core::conv_lc_file($origin)); + } + } + } + + if (in_array('tvshow', $this->gather_types)) { + $pathinfo = pathinfo($filename); + $filetitle = $pathinfo['filename']; + + $results = array_merge($results, $this->parseEpisodeName($filetitle)); + if (!$results['tvshow']) { + // Try to identify the show information from parent folder + $filetitle = basename($pathinfo['dirname']); + $results = array_merge($results, $this->parseEpisodeName($filetitle)); + + if (!$results['tvshow']) { + if ($results['tvshow_season'] && $results['tvshow_episode']) { + // We have season and episode, we assume parent folder is the tvshow name + $pathinfo = pathinfo($pathinfo['dirname']); + $filetitle = basename($pathinfo['dirname']); + $results['tvshow'] = $this->fixSerieName($filetitle); + } else { + // Or we assume each parent folder contains one missing information + if (preg_match('/[\/\\\\]([^\/\\\\]*)[\/\\\\]Season (\d{1,2})[\/\\\\]((E|Ep|Episode)\s?(\d{1,2})[\/\\\\])?/i', $filename, $matches)) { + if ($matches != null) { + $results['tvshow'] = $this->fixSerieName($matches[1]); + $results['tvshow_season'] = $matches[2]; + if (isset($matches[5])) { + $results['tvshow_episode'] = $matches[5]; + } + } + } + } + } + } + } + + if (in_array('movie', $this->gather_types)) { + $pathinfo = pathinfo($filename); + $filetitle = $pathinfo['filename']; + $results['title'] = $this->fixVideoReleaseName($filetitle); + if (!$results['title']) { + // Try to identify the movie information from parent folder + $filetitle = basename($pathinfo['dirname']); + $results['title'] = $this->fixVideoReleaseName($filetitle); } } return $results; } + private function parseEpisodeName($filetitle) + { + $patterns = array( + '/(.*)s(\d\d)e(\d\d)(\D.*)/i', + '/(.*)s(\d\d)(\D)(.*)/i', + '/(.*)\D(\d{1,2})x(\d\d)(\D)(.*)/i', + '/(.*)\D(\d{1,2})x(\d\d)$/i', + '/(\D*)[\.|\-|_](\d)(\d\d)([\.|\-|_]\D.*)/i', + '/(\D*)(\d)[^0-9](\d\d)(\D.*)/i' + ); + + $results = array(); + for ($i=0;$ifixSerieName($matches[1]); + if (empty($name)) { + continue; + } + + $season = floatval($matches[2]); + if ($season == 0) { + continue; + } + + $episode = floatval($matches[3]); + $leftover = $matches[4]; + + if ($episode == 0) { + // Some malformed string + $leftover = $filetitle; + } + + $results['tvshow'] = $name; + $results['tvshow_season'] = $season; + $results['tvshow_episode'] = $episode; + $results['title'] = $this->fixVideoReleaseName($leftover); + break; + } + } + + return $results; + } + + private function fixSerieName($name) + { + $name = str_replace('_', ' ', $name); + $name = str_replace('.', ' ', $name); + $name = str_replace(' ', ' ', $name); + $name = $this->removeStartingDashesAndSpaces($name); + $name = $this->removeEndingDashesAndSpaces($name); + + return ucwords($name); + } + + private function fixVideoReleaseName($name) + { + $commonabbr = array( + 'divx', 'xvid', 'dvdrip', 'hdtv', 'lol', 'axxo', 'repack', 'xor', + 'pdtv', 'real', 'vtv', 'caph', '2hd', 'proper', 'fqm', 'uncut', + 'topaz', 'tvt', 'notv', 'fpn', 'fov', 'orenji', '0tv', 'omicron', + 'dsr', 'ws', 'sys', 'crimson', 'wat', 'hiqt', 'internal', 'brrip', + 'boheme', 'vost', 'vostfr', 'fastsub', 'addiction' + ); + for ($i=0; $ifixSerieName($name); + } + + private function removeStartingDashesAndSpaces($name) + { + if (empty($name)) { + return $name; + } + + while (strpos($name, ' ') === 0 || strpos($name, '-') === 0) { + $name = preg_replace('/^ /', '', $name); + $name = preg_replace('/^-/', '', $name); + } + + return $name; + } + + private function removeEndingDashesAndSpaces($name) + { + if (empty($name)) { + return $name; + } + + while (strrpos($name, ' ') === strlen($name) - 1 || strrpos($name, '-') === strlen($name) - 1) { + $name = preg_replace('/ $/', '', $name); + $name = preg_replace('/-$/', '', $name); + } + + return $name; + } + /** * set_broken * * This fills all tag types with Unknown (Broken) * - * @return array Return broken title, album, artist + * @return array Return broken title, album, artist */ public function set_broken() { @@ -859,7 +1223,28 @@ class vainfo $broken[$key]['artist'] = 'Unknown (Broken)'; return $broken; + } + // set_broken - } // set_broken + /** + * + * @param array $data + * @return array + * @throws Exception + */ + private function parseGenres($data) + { + // read additional id3v2 delimiters from config + $delimiters = AmpConfig::get('additional_genre_delimiters'); + if (isset($data) && is_array($data) && count($data) === 1 && isset($delimiters)) { + $pattern = '~[\s]?(' . $delimiters . ')[\s]?~'; + $genres = preg_split($pattern, reset($data)); + if ($genres === false) { + throw new Exception('Pattern given in additional_genre_delimiters is not functional. Please ensure is it a valid regex (delimiter ~).'); + } + $data = $genres; + } + return $data; + } } // end class vainfo diff --git a/sources/lib/class/video.class.php b/sources/lib/class/video.class.php index 55f10fe..e7b1593 100644 --- a/sources/lib/class/video.class.php +++ b/sources/lib/class/video.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,48 +20,211 @@ * */ -class Video extends database_object implements media +class Video extends database_object implements media, library_item { + /** + * @var int $id + */ public $id; + /** + * @var string $title + */ public $title; + /** + * @var boolean $played + */ + public $played; + /** + * @var boolean $enabled + */ public $enabled; + /** + * @var string $file + */ public $file; + /** + * @var int $size + */ public $size; + /** + * @var string $video_codec + */ public $video_codec; + /** + * @var string $audio_codec + */ public $audio_codec; + /** + * @var int $resolution_x + */ public $resolution_x; + /** + * @var int $resolution_y + */ public $resolution_y; + /** + * @var int $time + */ public $time; + /** + * @var string $mime + */ public $mime; + /** + * @var int $release_date + */ + public $release_date; + /** + * @var int $catalog + */ + public $catalog; + /** + * @var int $bitrate + */ + public $bitrate; + /** + * @var string $mode + */ + public $mode; + /** + * @var int $channels + */ + public $channels; + /** + * @var int $display_x + */ + public $display_x; + /** + * @var int $display_x + */ + public $display_y; + /** + * @var float $frame_rate + */ + public $frame_rate; + /** + * @var int $video_bitrate + */ + public $video_bitrate; + /** + * @var string $type + */ + public $type; + /** + * @var array $tags + */ public $tags; + /** + * @var string $f_title + */ public $f_title; + /** + * @var string $f_full_title + */ + public $f_full_title; + /** + * @var string $f_time + */ + public $f_time; + /** + * @var string $f_time_h + */ + public $f_time_h; + /** + * @var string $link + */ + public $link; + /** + * @var string $f_link + */ public $f_link; + /** + * @var string $f_codec + */ public $f_codec; + /** + * @var string $f_resolution + */ public $f_resolution; + /** + * @var string $f_display + */ + public $f_display; + /** + * @var string $f_bitrate + */ + public $f_bitrate; + /** + * @var string $f_video_bitrate + */ + public $f_video_bitrate; + /** + * @var string $f_frame_rate + */ + public $f_frame_rate; + /** + * @var string $f_tags + */ public $f_tags; + /** + * @var string $f_length + */ public $f_length; + /** + * @var string $f_file + */ + public $f_file; + /** + * @var string $f_release_date + */ + public $f_release_date; /** * Constructor - * This pulls the shoutbox information from the database and returns - * a constructed object, uses user_shout table + * This pulls the information from the database and returns + * a constructed object + * @param int $id */ public function __construct($id) { // Load the data from the database - $info = $this->get_info($id); + $info = $this->get_info($id, 'video'); foreach ($info as $key=>$value) { $this->$key = $value; } + $data = pathinfo($this->file); + $this->type = strtolower($data['extension']); + return true; } // Constructor + /** + * Create a video strongly typed object from its id. + * @param int $video_id + * @return \Video + */ + public static function create_from_id($video_id) + { + $dtypes = self::get_derived_types(); + foreach ($dtypes as $dtype) { + $sql = "SELECT `id` FROM `" . strtolower($dtype) . "` WHERE `id` = ?"; + $db_results = Dba::read($sql, array($video_id)); + if ($results = Dba::fetch_assoc($db_results)) { + if ($results['id']) { + return new $dtype($video_id); + } + } + } + return new Video($video_id); + } + /** * build_cache * Build a cache based on the array of ids passed, saves lots of little queries + * @param int[] $ids */ public static function build_cache($ids=array()) { @@ -82,51 +245,790 @@ class Video extends database_object implements media * format * This formats a video object so that it is human readable */ - public function format() + public function format($details = true) { $this->f_title = scrub_out($this->title); - $this->f_link = scrub_out($this->title); + $this->f_full_title = $this->f_title; + $this->link = AmpConfig::get('web_path') . "/video.php?action=show_video&video_id=" . $this->id; + $this->f_link = "link . "\" title=\"" . scrub_out($this->f_title) . "\"> " . scrub_out($this->f_title) . ""; $this->f_codec = $this->video_codec . ' / ' . $this->audio_codec; - $this->f_resolution = $this->resolution_x . 'x' . $this->resolution_y; - $this->f_tags = ''; + if ($this->resolution_x || $this->resolution_y) { + $this->f_resolution = $this->resolution_x . 'x' . $this->resolution_y; + } + if ($this->display_x || $this->display_y) { + $this->f_display = $this->display_x . 'x' . $this->display_y; + } + + // Format the Bitrate + $this->f_bitrate = intval($this->bitrate/1000) . "-" . strtoupper($this->mode); + $this->f_video_bitrate = (string) intval($this->video_bitrate/1000); + if ($this->frame_rate) { + $this->f_frame_rate = $this->frame_rate . ' fps'; + } + + // Format the Time + $min = floor($this->time/60); + $sec = sprintf("%02d", ($this->time%60)); + $this->f_time = $min . ":" . $sec; + $hour = sprintf("%02d", floor($min/60)); + $min_h = sprintf("%02d", ($min%60)); + $this->f_time_h = $hour . ":" . $min_h . ":" . $sec; + + if ($details) { + // Get the top tags + $this->tags = Tag::get_top_tags('video', $this->id); + $this->f_tags = Tag::get_display($this->tags, true, 'video'); + } + $this->f_length = floor($this->time/60) . ' ' . T_('minutes'); + $this->f_file = $this->f_title . '.' . $this->type; + if ($this->release_date) { + $this->f_release_date = date('Y-m-d', $this->release_date); + } } // format - public function get_stream_types() + /** + * Get item keywords for metadata searches. + * @return array + */ + public function get_keywords() { - return array('native'); + $keywords = array(); + $keywords['title'] = array('important' => true, + 'label' => T_('Title'), + 'value' => $this->f_title); - } // native_stream + return $keywords; + } + + /** + * Get item fullname. + * @return string + */ + public function get_fullname() + { + return $this->f_title; + } + + /** + * Get parent item description. + * @return array|null + */ + public function get_parent() + { + return null; + } + + /** + * Get item childrens. + * @return array + */ + public function get_childrens() + { + return array(); + } + + /** + * Search for item childrens. + * @param string $name + * @return array + */ + public function search_childrens($name) + { + return array(); + } + + /** + * Get all childrens and sub-childrens medias. + * @param string $filter_type + * @return array + */ + public function get_medias($filter_type = null) + { + $medias = array(); + if (!$filter_type || $filter_type == 'video') { + $medias[] = array( + 'object_type' => 'video', + 'object_id' => $this->id + ); + } + return $medias; + } + + /** + * get_catalogs + * + * Get all catalog ids related to this item. + * @return int[] + */ + public function get_catalogs() + { + return array($this->catalog); + } + + /** + * Get item's owner. + * @return int|null + */ + public function get_user_owner() + { + return null; + } + + /** + * Get default art kind for this item. + * @return string + */ + public function get_default_art_kind() + { + return 'preview'; + } + + public function get_description() + { + return ''; + } + + public function display_art($thumb = 2) + { + if (Art::has_db($this->id, 'video')) { + Art::display('video', $this->id, $this->get_fullname(), $thumb, $this->link); + } + } + + /** + * gc + * + * Cleans up the inherited object tables + */ + public static function gc() + { + Movie::gc(); + TVShow_Episode::gc(); + TVShow_Season::gc(); + TVShow::gc(); + Personal_Video::gc(); + Clip::gc(); + } + + /** + * Get stream types. + * @return array + */ + public function get_stream_types($player = null) + { + return Song::get_stream_types_for_type($this->type, $player); + } /** * play_url * This returns a "PLAY" url for the video in question here, this currently feels a little * like a hack, might need to adjust it in the future + * @param int $oid + * @param string $additional_params + * @param string $player + * @param boolean $local + * @return string */ - public static function play_url($oid, $additional_params='',$sid='',$force_http='') + public static function play_url($oid, $additional_params='', $player=null, $local=false) { - $video = new Video($oid); + return Song::generic_play_url('video', $oid, $additional_params, $player, $local); + } - if (!$video->id) { return false; } - - $uid = intval($GLOBALS['user']->id); - $oid = intval($video->id); - - $url = Stream::get_base_url() . "type=video&uid=" . $uid . "&oid=" . $oid; - - return Stream_URL::format($url . $additional_params); - - } // play_url + /** + * Get stream name. + * @return string + */ + public function get_stream_name() + { + return $this->title; + } /** * get_transcode_settings - * - * FIXME: Video transcoding is not implemented + * @param string $target + * @param array $options + * @return array */ - public function get_transcode_settings($target = null) + public function get_transcode_settings($target = null, $player = null, $options=array()) { - return false; + return Song::get_transcode_settings_for_media($this->type, $target, $player, 'video', $options); } + /** + * Get derived video types. + * @return array + */ + private static function get_derived_types() + { + return array('TVShow_Episode', 'Movie', 'Clip', 'Personal_Video'); + } + + /** + * Validate video type. + * @param string $type + * @return string + */ + public static function validate_type($type) + { + $dtypes = self::get_derived_types(); + foreach ($dtypes as $dtype) { + if (strtolower($type) == strtolower($dtype)) + return $type; + } + + return 'Video'; + } + + /** + * type_to_mime + * + * Returns the mime type for the specified file extension/type + * @param string $type + * @return string + */ + public static function type_to_mime($type) + { + // FIXME: This should really be done the other way around. + // Store the mime type in the database, and provide a function + // to make it a human-friendly type. + switch ($type) { + case 'avi': + return 'video/avi'; + case 'ogg': + case 'ogv': + return 'application/ogg'; + case 'wmv': + return 'audio/x-ms-wmv'; + case 'mp4': + case 'm4v': + return 'video/mp4'; + case 'mkv': + return 'video/x-matroska'; + case 'mkv': + return 'video/x-matroska'; + case 'mov': + return 'video/quicktime'; + case 'divx': + return 'video/x-divx'; + case 'webm': + return 'video/webm'; + case 'flv': + return 'video/x-flv'; + case 'ts': + return 'video/mp2t'; + case 'mpg': + case 'mpeg': + case 'm2ts': + default: + return 'video/mpeg'; + } + } + + /** + * Insert new video. + * @param array $data + * @param array $gtypes + * @param array $options + * @return int + */ + public static function insert(array $data, $gtypes = array(), $options = array()) + { + $bitrate = intval($data['bitrate']); + $mode = $data['mode']; + $rezx = intval($data['resolution_x']); + $rezy = intval($data['resolution_y']); + $release_date = intval($data['release_date']); + // No release date, then release date = production year + if (!$release_date && $data['year']) { + $release_date = strtotime($data['year'] . '01-01'); + } + $tags = $data['genre']; + $channels = intval($data['channels']); + $disx = intval($data['display_x']); + $disy = intval($data['display_y']); + $frame_rate = floatval($data['frame_rate']); + $video_bitrate = intval($data['video_bitrate']); + + $sql = "INSERT INTO `video` (`file`,`catalog`,`title`,`video_codec`,`audio_codec`,`resolution_x`,`resolution_y`,`size`,`time`,`mime`,`release_date`,`addition_time`, `bitrate`, `mode`, `channels`, `display_x`, `display_y`, `frame_rate`, `video_bitrate`) " . + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + $params = array($data['file'], $data['catalog'], $data['title'], $data['video_codec'], $data['audio_codec'], $rezx, $rezy, $data['size'], $data['time'], $data['mime'], $release_date, time(), $bitrate, $mode, $channels, $disx, $disy, $frame_rate, $video_bitrate); + Dba::write($sql, $params); + $vid = Dba::insert_id(); + + if (is_array($tags)) { + foreach ($tags as $tag) { + $tag = trim($tag); + if (!empty($tag)) { + Tag::add('video', $vid, $tag, false); + } + } + } + + if ($data['art'] && $options['gather_art']) { + $art = new Art($vid, 'video'); + $art->insert_url($data['art']); + } + + $data['id'] = $vid; + return self::insert_video_type($data, $gtypes, $options); + } + + /** + * Insert video for derived type. + * @param array $data + * @param array $gtypes + * @param array $options + * @return int + */ + private static function insert_video_type(array $data, $gtypes, $options = array()) + { + if (count($gtypes) > 0) { + $gtype = $gtypes[0]; + switch ($gtype) { + case 'tvshow': + return TVShow_Episode::insert($data, $gtypes, $options); + case 'movie': + return Movie::insert($data, $gtypes, $options); + case 'clip': + return Clip::insert($data, $gtypes, $options); + case 'personal_video': + return Personal_Video::insert($data, $gtypes, $options); + default: + // Do nothing, video entry already created and no additional data for now + break; + } + } + + return $data['id']; + } + + /** + * update + * This takes a key'd array of data as input and updates a video entry + * @param array $data + * @return int + */ + public function update(array $data) + { + if (isset($data['release_date'])) { + $f_release_date = $data['release_date']; + $release_date = strtotime($f_release_date); + } else { + $release_date = $this->release_date; + } + $title = isset($data['title']) ? $data['title'] : $this->title; + + $sql = "UPDATE `video` SET `title` = ?, `release_date` = ? WHERE `id` = ?"; + Dba::write($sql, array($title, $release_date, $this->id)); + + if (isset($data['edit_tags'])) { + Tag::update_tag_list($data['edit_tags'], 'video', $this->id, true); + } + + $this->title = $title; + $this->release_date = $release_date; + + return $this->id; + + } // update + + /** + * Get release item art. + * @return array + */ + public function get_release_item_art() + { + return array('object_type' => 'video', + 'object_id' => $this->id + ); + } + + /* + * generate_preview + * Generate video preview image from a video file + * @param int $video_id + * @param boolean $overwrite + */ + public static function generate_preview($video_id, $overwrite = false) + { + if ($overwrite || !Art::has_db($video_id, 'video', 'preview')) { + $artp = new Art($video_id, 'video', 'preview'); + $video = new Video($video_id); + $image = Stream::get_image_preview($video); + $artp->insert($image, 'image/png'); + } + } + + /** + * get_random + * + * This returns a number of random videos. + * @param int $count + * @return int[] + */ + public static function get_random($count = 1) + { + $results = array(); + + if (!$count) { + $count = 1; + } + + $sql = "SELECT DISTINCT(`video`.`id`) FROM `video` "; + $where = "WHERE `video`.`enabled` = '1' "; + if (AmpConfig::get('catalog_disable')) { + $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `video`.`catalog` "; + $where .= "AND `catalog`.`enabled` = '1' "; + } + + $sql .= $where; + $sql .= "ORDER BY RAND() LIMIT " . intval($count); + $db_results = Dba::read($sql); + + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['id']; + } + + return $results; + } + + /** + * set_played + * this checks to see if the current object has been played + * if not then it sets it to played. In any case it updates stats. + * @param int $user + * @param string $agent + * @param array $location + * @return boolean + */ + public function set_played($user, $agent, $location) + { + Stats::insert('video', $this->id, $user, $agent, $location); + + if ($this->played) { + return true; + } + + /* If it hasn't been played, set it! */ + Video::update_played(true, $this->id); + + return true; + + } // set_played + + /** + * get_subtitles + * Get existing subtitles list for this video + * @return array + */ + public function get_subtitles() + { + $subtitles = array(); + $pinfo = pathinfo($this->file); + $filter = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $pinfo['filename'] . '*.srt'; + + foreach (glob($filter) as $srt) { + $psrt = explode('.', $srt); + $lang_code = '__'; + $lang_name = T_("Unknown"); + if (count($psrt) >= 2) { + $lang_code = $psrt[count($psrt) - 2]; + if (strlen($lang_code) == 2) { + $lang_name = $this->get_language_name($lang_code); + } + } + $subtitles[] = array( + 'file' => $pinfo['dirname'] . DIRECTORY_SEPARATOR . $srt, + 'lang_code' => $lang_code, + 'lang_name' => $lang_name + ); + } + + return $subtitles; + } + + /** + * Get language name from code. + * @param string $code + * @return string + */ + protected function get_language_name($code) + { + $languageCodes = array( + "aa" => T_("Afar"), + "ab" => T_("Abkhazian"), + "ae" => T_("Avestan"), + "af" => T_("Afrikaans"), + "ak" => T_("Akan"), + "am" => T_("Amharic"), + "an" => T_("Aragonese"), + "ar" => T_("Arabic"), + "as" => T_("Assamese"), + "av" => T_("Avaric"), + "ay" => T_("Aymara"), + "az" => T_("Azerbaijani"), + "ba" => T_("Bashkir"), + "be" => T_("Belarusian"), + "bg" => T_("Bulgarian"), + "bh" => T_("Bihari"), + "bi" => T_("Bislama"), + "bm" => T_("Bambara"), + "bn" => T_("Bengali"), + "bo" => T_("Tibetan"), + "br" => T_("Breton"), + "bs" => T_("Bosnian"), + "ca" => T_("Catalan"), + "ce" => T_("Chechen"), + "ch" => T_("Chamorro"), + "co" => T_("Corsican"), + "cr" => T_("Cree"), + "cs" => T_("Czech"), + "cu" => T_("Church Slavic"), + "cv" => T_("Chuvash"), + "cy" => T_("Welsh"), + "da" => T_("Danish"), + "de" => T_("German"), + "dv" => T_("Divehi"), + "dz" => T_("Dzongkha"), + "ee" => T_("Ewe"), + "el" => T_("Greek"), + "en" => T_("English"), + "eo" => T_("Esperanto"), + "es" => T_("Spanish"), + "et" => T_("Estonian"), + "eu" => T_("Basque"), + "fa" => T_("Persian"), + "ff" => T_("Fulah"), + "fi" => T_("Finnish"), + "fj" => T_("Fijian"), + "fo" => T_("Faroese"), + "fr" => T_("French"), + "fy" => T_("Western Frisian"), + "ga" => T_("Irish"), + "gd" => T_("Scottish Gaelic"), + "gl" => T_("Galician"), + "gn" => T_("Guarani"), + "gu" => T_("Gujarati"), + "gv" => T_("Manx"), + "ha" => T_("Hausa"), + "he" => T_("Hebrew"), + "hi" => T_("Hindi"), + "ho" => T_("Hiri Motu"), + "hr" => T_("Croatian"), + "ht" => T_("Haitian"), + "hu" => T_("Hungarian"), + "hy" => T_("Armenian"), + "hz" => T_("Herero"), + "ia" => T_("Interlingua (International Auxiliary Language Association)"), + "id" => T_("Indonesian"), + "ie" => T_("Interlingue"), + "ig" => T_("Igbo"), + "ii" => T_("Sichuan Yi"), + "ik" => T_("Inupiaq"), + "io" => T_("Ido"), + "is" => T_("Icelandic"), + "it" => T_("Italian"), + "iu" => T_("Inuktitut"), + "ja" => T_("Japanese"), + "jv" => T_("Javanese"), + "ka" => T_("Georgian"), + "kg" => T_("Kongo"), + "ki" => T_("Kikuyu"), + "kj" => T_("Kwanyama"), + "kk" => T_("Kazakh"), + "kl" => T_("Kalaallisut"), + "km" => T_("Khmer"), + "kn" => T_("Kannada"), + "ko" => T_("Korean"), + "kr" => T_("Kanuri"), + "ks" => T_("Kashmiri"), + "ku" => T_("Kurdish"), + "kv" => T_("Komi"), + "kw" => T_("Cornish"), + "ky" => T_("Kirghiz"), + "la" => T_("Latin"), + "lb" => T_("Luxembourgish"), + "lg" => T_("Ganda"), + "li" => T_("Limburgish"), + "ln" => T_("Lingala"), + "lo" => T_("Lao"), + "lt" => T_("Lithuanian"), + "lu" => T_("Luba-Katanga"), + "lv" => T_("Latvian"), + "mg" => T_("Malagasy"), + "mh" => T_("Marshallese"), + "mi" => T_("Maori"), + "mk" => T_("Macedonian"), + "ml" => T_("Malayalam"), + "mn" => T_("Mongolian"), + "mr" => T_("Marathi"), + "ms" => T_("Malay"), + "mt" => T_("Maltese"), + "my" => T_("Burmese"), + "na" => T_("Nauru"), + "nb" => T_("Norwegian Bokmal"), + "nd" => T_("North Ndebele"), + "ne" => T_("Nepali"), + "ng" => T_("Ndonga"), + "nl" => T_("Dutch"), + "nn" => T_("Norwegian Nynorsk"), + "no" => T_("Norwegian"), + "nr" => T_("South Ndebele"), + "nv" => T_("Navajo"), + "ny" => T_("Chichewa"), + "oc" => T_("Occitan"), + "oj" => T_("Ojibwa"), + "om" => T_("Oromo"), + "or" => T_("Oriya"), + "os" => T_("Ossetian"), + "pa" => T_("Panjabi"), + "pi" => T_("Pali"), + "pl" => T_("Polish"), + "ps" => T_("Pashto"), + "pt" => T_("Portuguese"), + "qu" => T_("Quechua"), + "rm" => T_("Raeto-Romance"), + "rn" => T_("Kirundi"), + "ro" => T_("Romanian"), + "ru" => T_("Russian"), + "rw" => T_("Kinyarwanda"), + "sa" => T_("Sanskrit"), + "sc" => T_("Sardinian"), + "sd" => T_("Sindhi"), + "se" => T_("Northern Sami"), + "sg" => T_("Sango"), + "si" => T_("Sinhala"), + "sk" => T_("Slovak"), + "sl" => T_("Slovenian"), + "sm" => T_("Samoan"), + "sn" => T_("Shona"), + "so" => T_("Somali"), + "sq" => T_("Albanian"), + "sr" => T_("Serbian"), + "ss" => T_("Swati"), + "st" => T_("Southern Sotho"), + "su" => T_("Sundanese"), + "sv" => T_("Swedish"), + "sw" => T_("Swahili"), + "ta" => T_("Tamil"), + "te" => T_("Telugu"), + "tg" => T_("Tajik"), + "th" => T_("Thai"), + "ti" => T_("Tigrinya"), + "tk" => T_("Turkmen"), + "tl" => T_("Tagalog"), + "tn" => T_("Tswana"), + "to" => T_("Tonga"), + "tr" => T_("Turkish"), + "ts" => T_("Tsonga"), + "tt" => T_("Tatar"), + "tw" => T_("Twi"), + "ty" => T_("Tahitian"), + "ug" => T_("Uighur"), + "uk" => T_("Ukrainian"), + "ur" => T_("Urdu"), + "uz" => T_("Uzbek"), + "ve" => T_("Venda"), + "vi" => T_("Vietnamese"), + "vo" => T_("Volapuk"), + "wa" => T_("Walloon"), + "wo" => T_("Wolof"), + "xh" => T_("Xhosa"), + "yi" => T_("Yiddish"), + "yo" => T_("Yoruba"), + "za" => T_("Zhuang"), + "zh" => T_("Chinese"), + "zu" => T_("Zulu") + ); + + return $languageCodes[$code]; + } + + /** + * Get subtitle file from language code. + * @param string $lang_code + * @return string + */ + public function get_subtitle_file($lang_code) + { + $subtitle = ''; + if ($lang_code == '__' || $this->get_language_name($lang_code)) { + $pinfo = pathinfo($this->file); + $subtitle = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $pinfo['filename']; + if ($lang_code != '__') { + $subtitle .= '.' . $lang_code; + } + $subtitle .= '.srt'; + } + + return $subtitle; + } + + /** + * Remove the video from disk. + */ + public function remove_from_disk() + { + if (file_exists($this->file)) { + $deleted = unlink($this->file); + } else { + $deleted = true; + } + if ($deleted === true) { + $sql = "DELETE FROM `video` WHERE `id` = ?"; + $deleted = Dba::write($sql, array($this->id)); + if ($deleted) { + Art::gc('video', $this->id); + Userflag::gc('video', $this->id); + Rating::gc('video', $this->id); + Shoutbox::gc('video', $this->id); + } + } else { + debug_event('video', 'Cannot delete ' . $this->file . 'file. Please check permissions.', 1); + } + + return $deleted; + } + + /** + * update_played + * sets the played flag + * @param boolean $new_played + * @param int $song_id + */ + public static function update_played($new_played, $song_id) + { + self::_update_item('played', ($new_played ? 1 : 0),$song_id,'25'); + + } // update_played + + /** + * _update_item + * This is a private function that should only be called from within the video class. + * It takes a field, value video id and level. first and foremost it checks the level + * against $GLOBALS['user'] to make sure they are allowed to update this record + * it then updates it and sets $this->{$field} to the new value + * @param string $field + * @param mixed $value + * @param int $song_id + * @param int $level + * @return boolean + */ + private static function _update_item($field, $value, $song_id, $level) + { + /* Check them Rights! */ + if (!Access::check('interface',$level)) { return false; } + + /* Can't update to blank */ + if (!strlen(trim($value))) { return false; } + + $sql = "UPDATE `video` SET `$field` = ? WHERE `id` = ?"; + Dba::write($sql, array($value, $song_id)); + + return true; + + } // _update_item + } // end Video class diff --git a/sources/lib/class/wanted.class.php b/sources/lib/class/wanted.class.php index d18520d..0fd0127 100644 --- a/sources/lib/class/wanted.class.php +++ b/sources/lib/class/wanted.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,27 +22,74 @@ use MusicBrainz\MusicBrainz; use MusicBrainz\Clients\RequestsMbClient; +use MusicBrainz\Filters\ArtistFilter; class Wanted extends database_object { /* Variables from DB */ + + /** + * @var int $id + */ public $id; + /** + * @var string $mbid + */ public $mbid; + /** + * @var int $artist + */ public $artist; + /** + * @var string $artist_mbid + */ public $artist_mbid; + /** + * @var string $name + */ public $name; + /** + * @var string $year + */ public $year; + /** + * @var boolean $accepted + */ public $accepted; + /** + * @var string $release_mbid + */ public $release_mbid; + /** + * @var int $user + */ public $user; - public $f_name_link; + /** + * @var string $link + */ + public $link; + + /** + * @var string $f_link + */ + public $f_link; + /** + * @var string $f_artist_link + */ public $f_artist_link; + /** + * @var string $f_user + */ public $f_user; + /** + * @var array $songs + */ public $songs; /** * Constructor + * @param int $id */ public function __construct($id=0) { @@ -62,6 +109,9 @@ class Wanted extends database_object /** * get_missing_albums * Get list of library's missing albums from MusicBrainz + * @param Artist|null $artist + * @param string $mbid + * @return array */ public static function get_missing_albums($artist, $mbid='') { @@ -83,62 +133,66 @@ class Wanted extends database_object $albums = $artist->get_albums(); foreach ($albums as $id) { $album = new Album($id); - if ($album->mbid) { - $malbum = $mb->lookup('release', $album->mbid, array('release-groups')); - if ($malbum->{'release-group'}) { - if (!in_array($malbum->{'release-group'}->id, $owngroups)) { - $owngroups[] = $malbum->{'release-group'}->id; + if (trim($album->mbid_group)) { + $owngroups[] = $album->mbid_group; + } else { + if (trim($album->mbid)) { + $malbum = $mb->lookup('release', $album->mbid, array('release-groups')); + if ($malbum['release-group']) { + if (!in_array($malbum['release-group']['id'], $owngroups)) { + $owngroups[] = $malbum['release-group']['id']; + } } } } } } else { $wartist['mbid'] = $mbid; - $wartist['name'] = $martist->name; + $wartist['name'] = $martist['name']; parent::add_to_cache('missing_artist', $mbid, $wartist); $wartist = self::get_missing_artist($mbid); } $results = array(); - foreach ($martist->{'release-groups'} as $group) { - if (in_array(strtolower($group->{'primary-type'}), $types)) { + foreach ($martist['release-groups'] as $group) { + if (in_array(strtolower($group['primary-type']), $types)) { $add = true; - for ($i = 0; $i < count($group->{'secondary-types'}) && $add; ++$i) { - $add = in_array(strtolower($group->{'secondary-types'}[$i]), $types); + for ($i = 0; $i < count($group['secondary-types']) && $add; ++$i) { + $add = in_array(strtolower($group['secondary-types'][$i]), $types); } if ($add) { - if (!in_array($group->id, $owngroups)) { - $wantedid = self::get_wanted($group->id); + if (!in_array($group['id'], $owngroups)) { + $wantedid = self::get_wanted($group['id']); $wanted = new Wanted($wantedid); if ($wanted->id) { $wanted->format(); } else { - $wanted->mbid = $group->id; + $wanted->mbid = $group['id']; if ($artist) { $wanted->artist = $artist->id; } else { $wanted->artist_mbid = $mbid; } - $wanted->name = $group->title; - if (!empty($group->{'first-release-date'})) { - if (strlen($group->{'first-release-date'}) == 4) { - $wanted->year = $group->{'first-release-date'}; + $wanted->name = $group['title']; + if (!empty($group['first-release-date'])) { + if (strlen($group['first-release-date']) == 4) { + $wanted->year = $group['first-release-date']; } else { - $wanted->year = date("Y", strtotime($group->{'first-release-date'})); + $wanted->year = date("Y", strtotime($group['first-release-date'])); } } $wanted->accepted = false; - $wanted->f_name_link = "id; + $wanted->link = AmpConfig::get('web_path') . "/albums.php?action=show_missing&mbid=" . $group['id']; if ($artist) { - $wanted->f_name_link .= "&artist=" . $wanted->artist; + $wanted->link .= "&artist=" . $wanted->artist; } else { - $wanted->f_name_link .= "&artist_mbid=" . $mbid; + $wanted->link .= "&artist_mbid=" . $mbid; } - $wanted->f_name_link .= "\" title=\"" . $wanted->name . "\">" . $wanted->name . ""; - $wanted->f_artist_link = $artist ? $artist->f_name_link : $wartist['link']; - $wanted->f_user = $GLOBALS['user']->fullname; + $wanted->f_link = "link . "\" title=\"" . $wanted->name . "\">" . $wanted->name . ""; + $wanted->f_artist_link = $artist ? $artist->f_link : $wartist['link']; + $wanted->f_user = $GLOBALS['user']->f_name; } $results[] = $wanted; } @@ -149,6 +203,11 @@ class Wanted extends database_object return $results; } // get_missing_albums + /** + * Get missing artist data. + * @param string $mbid + * @return array + */ public static function get_missing_artist($mbid) { $wartist = array(); @@ -166,7 +225,7 @@ class Wanted extends database_object return $wartist; } - $wartist['name'] = $martist->name; + $wartist['name'] = $martist['name']; parent::add_to_cache('missing_artist', $mbid, $wartist); } @@ -175,6 +234,28 @@ class Wanted extends database_object return $wartist; } + public static function search_missing_artists($name) + { + $args = array( + 'artist' => $name + ); + $filter = new ArtistFilter($args); + $mb = new MusicBrainz(new RequestsMbClient()); + $res = $mb->search($filter); + $wartists = array(); + foreach ($res as $r) { + $wartists[] = array( + 'mbid' => $r->id, + 'name' => $r->name, + ); + } + return $wartists; + } + + /** + * Get accepted wanted release count. + * @return int + */ public static function get_accepted_wanted_count() { $sql = "SELECT COUNT(`id`) AS `wanted_cnt` FROM `wanted` WHERE `accepted` = 1"; @@ -186,6 +267,11 @@ class Wanted extends database_object return 0; } + /** + * Get wanted release by mbid. + * @param string $mbid + * @return int + */ public static function get_wanted($mbid) { $sql = "SELECT `id` FROM `wanted` WHERE `mbid` = ?"; @@ -194,9 +280,13 @@ class Wanted extends database_object return $row['id']; } - return false; + return 0; } + /** + * Delete wanted release. + * @param string $mbid + */ public static function delete_wanted($mbid) { $sql = "DELETE FROM `wanted` WHERE `mbid` = ?"; @@ -209,17 +299,27 @@ class Wanted extends database_object Dba::write($sql, $params); } + /** + * Delete a wanted release by mbid. + * @param string $mbid + */ public static function delete_wanted_release($mbid) { if (self::get_accepted_wanted_count() > 0) { $mb = new MusicBrainz(new RequestsMbClient()); $malbum = $mb->lookup('release', $mbid, array('release-groups')); - if ($malbum->{'release-group'}) { - self::delete_wanted($malbum->{'release-group'}->id); + if ($malbum['release-group']) { + self::delete_wanted($malbum['release-group']); } } } + /** + * Delete a wanted release by name. + * @param int $artist + * @param string $album_name + * @param int $year + */ public static function delete_wanted_by_name($artist, $album_name, $year) { $sql = "DELETE FROM `wanted` WHERE `artist` = ? AND `name` = ? AND `year` = ?"; @@ -232,12 +332,15 @@ class Wanted extends database_object Dba::write($sql, $params); } + /** + * Accept a wanted request. + */ public function accept() { if ($GLOBALS['user']->has_access('75')) { $sql = "UPDATE `wanted` SET `accepted` = '1' WHERE `mbid` = ?"; Dba::write($sql, array( $this->mbid )); - $this->accepted = 1; + $this->accepted = true; foreach (Plugin::get_plugins('process_wanted') as $plugin_name) { debug_event('wanted', 'Using Wanted Process plugin: ' . $plugin_name, '5'); @@ -249,6 +352,12 @@ class Wanted extends database_object } } + /** + * Check if a release mbid is already marked as wanted + * @param string $mbid + * @param int $userid + * @return boolean + */ public static function has_wanted($mbid, $userid = 0) { if ($userid == 0) { @@ -266,6 +375,14 @@ class Wanted extends database_object } + /** + * Add a new wanted release. + * @param string $mbid + * @param int $artist + * @param string $artist_mbid + * @param string $name + * @param int $year + */ public static function add_wanted($mbid, $artist, $artist_mbid, $name, $year) { $sql = "INSERT INTO `wanted` (`user`, `artist`, `artist_mbid`, `mbid`, `name`, `year`, `date`, `accepted`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; @@ -277,9 +394,14 @@ class Wanted extends database_object $wantedid = Dba::insert_id(); $wanted = new Wanted($wantedid); $wanted->accept(); + + database_object::remove_from_cache('wanted', $wantedid); } } + /** + * Show action buttons. + */ public function show_action_buttons() { if ($this->id) { @@ -296,6 +418,10 @@ class Wanted extends database_object } } + /** + * Load wanted release data. + * @param boolean $track_details + */ public function load_all($track_details = true) { $mb = new MusicBrainz(new RequestsMbClient()); @@ -304,70 +430,51 @@ class Wanted extends database_object try { $group = $mb->lookup('release-group', $this->mbid, array( 'releases' )); // Set fresh data - $this->name = $group->title; - $this->year = date("Y", strtotime($group->{'first-release-date'})); + $this->name = $group['title']; + $this->year = date("Y", strtotime($group['first-release-date'])); // Load from database if already cached $this->songs = Song_preview::get_song_previews($this->mbid); - - if (count($group->releases) > 0) { - $this->release_mbid = $group->releases[0]->id; + if (count($group['releases']) > 0) { + $this->release_mbid = $group['releases'][0]['id']; if ($track_details && count($this->songs) == 0) { // Use the first release as reference for track content $release = $mb->lookup('release', $this->release_mbid, array( 'recordings' )); - foreach ($release->media as $media) { - foreach ($media->tracks as $track) { + foreach ($release['media'] as $media) { + foreach ($media['tracks'] as $track) { $song = array(); - $song['disk'] = $media->position; - $song['track'] = $track->number; - $song['title'] = $track->title; - $song['mbid'] = $track->id; + $song['disk'] = $media['position']; + $song['track'] = $track['number']; + $song['title'] = $track['title']; + $song['mbid'] = $track['id']; if ($this->artist) { $song['artist'] = $this->artist; } $song['artist_mbid'] = $this->artist_mbid; $song['session'] = session_id(); $song['album_mbid'] = $this->mbid; - if (AmpConfig::get('echonest_api_key')) { - $echonest = new EchoNest_Client(new EchoNest_HttpClient_Requests()); - $echonest->authenticate(AmpConfig::get('echonest_api_key')); - $enSong = null; - try { - $enProfile = $echonest->getTrackApi()->profile('musicbrainz:track:' . $track->id); - $enSong = $echonest->getSongApi()->profile($enProfile['song_id'], array( 'id:7digital-US', 'audio_summary', 'tracks')); - } catch (Exception $e) { - debug_event('echonest', 'EchoNest track error on `' . $track->id . '` (' . $track->title . '): ' . $e->getMessage(), '1'); - } - // Wans't able to get the song with MusicBrainz ID, try a search - if ($enSong == null) { - if ($this->artist) { - $artist = new Artist($this->artist); - $artist_name = $artist->name; - } else { - $wartist = Wanted::get_missing_artist($this->artist_mbid); - $artist_name = $wartist['name']; - } - try { - $enSong = $echonest->getSongApi()->search(array( - 'results' => '1', - 'artist' => $artist_name, - 'title' => $track->title, - 'bucket' => array( 'id:7digital-US', 'audio_summary', 'tracks'), - )); + if ($this->artist) { + $artist = new Artist($this->artist); + $artist_name = $artist->name; + } else { + $wartist = Wanted::get_missing_artist($this->artist_mbid); + $artist_name = $wartist['name']; + } - - } catch (Exception $e) { - debug_event('echonest', 'EchoNest song search error: ' . $e->getMessage(), '1'); - } - } - - if ($enSong != null) { - $song['file'] = $enSong[0]['tracks'][0]['preview_url']; - debug_event('echonest', 'EchoNest `' . $track->title . '` preview: ' . $song['file'], '1'); + $song['file'] = null; + foreach (Plugin::get_plugins('get_song_preview') as $plugin_name) { + $plugin = new Plugin($plugin_name); + if ($plugin->load($GLOBALS['user'])) { + $song['file'] = $plugin->_plugin->get_song_preview($track['id'], $artist_name, $track['title']); + if ($song['file'] != null) + break; } } - $this->songs[] = new Song_Preview(Song_preview::insert($song)); + + if ($song != null) { + $this->songs[] = new Song_Preview(Song_preview::insert($song)); + } } } } @@ -382,22 +489,31 @@ class Wanted extends database_object } } + /** + * Format data. + */ public function format() { if ($this->artist) { $artist = new Artist($this->artist); $artist->format(); - $this->f_artist_link = $artist->f_name_link; + $this->f_artist_link = $artist->f_link; } else { $wartist = Wanted::get_missing_artist($this->artist_mbid); $this->f_artist_link = $wartist['link']; } - $this->f_name_link = "mbid . "&artist=" . $this->artist . "&artist_mbid=" . $this->artist_mbid . "\" title=\"" . $this->name . "\">" . $this->name . ""; + $this->link = AmpConfig::get('web_path') . "/albums.php?action=show_missing&mbid=" . $this->mbid . "&artist=" . $this->artist . "&artist_mbid=" . $this->artist_mbid . "\" title=\"" . $this->name; + $this->f_link = "link . "\">" . $this->name . ""; $user = new User($this->user); - $this->f_user = $user->fullname; + $user->format(); + $this->f_user = $user->f_name; } + /** + * Get wanted list sql. + * @return string + */ public static function get_wanted_list_sql() { $sql = "SELECT `id` FROM `wanted` "; @@ -409,6 +525,10 @@ class Wanted extends database_object return $sql; } + /** + * Get wanted list. + * @return int[] + */ public static function get_wanted_list() { $sql = self::get_wanted_list_sql(); diff --git a/sources/lib/class/waveform.class.php b/sources/lib/class/waveform.class.php index 93d3a91..bf0ac7f 100644 --- a/sources/lib/class/waveform.class.php +++ b/sources/lib/class/waveform.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -65,6 +65,11 @@ class Waveform } // Constructor + /** + * Get a song waveform. + * @param int $song_id + * @return binary|string|null + */ public static function get($song_id) { $song = new Song($song_id); @@ -107,6 +112,8 @@ class Waveform fclose($fp); fclose($tfp); + Stream::kill_process($transcoder); + $waveform = self::create_waveform($tmpfile); //$waveform = self::create_waveform("C:\\tmp\\test.wav"); @@ -143,6 +150,8 @@ class Waveform /** * Great function slightly modified as posted by Minux at * http://forums.clantemplates.com/showthread.php?t=133805 + * @param string $input + * @return array */ protected static function html2rgb($input) { @@ -154,8 +163,17 @@ class Waveform ); } + /** + * Create waveform from song file. + * @param string $filename + * @return binary|string|null + */ protected static function create_waveform($filename) { + if (!file_exists($filename)) { + return null; + } + $detail = 5; $width = 400; $height = 32; @@ -194,7 +212,7 @@ class Waveform // start putting together the initial canvas // $data_size = (size_of_file - header_bytes_read) / skipped_bytes + 1 - $data_size = floor((filesize($filename) - 44) / ($ratio + $byte) + 1); + $data_size = floor((Core::get_filesize($filename) - 44) / ($ratio + $byte) + 1); $data_point = 0; // create original image width based on amount of detail @@ -293,6 +311,12 @@ class Waveform return $imgdata; } + /** + * Save waveform to db. + * @param int $song_id + * @param binary|string $waveform + * @return boolean + */ protected static function save_to_db($song_id, $waveform) { $sql = "UPDATE `song_data` SET `waveform` = ? WHERE `song_id` = ?"; diff --git a/sources/lib/class/webdav_auth.class.php b/sources/lib/class/webdav_auth.class.php new file mode 100644 index 0000000..a5342b5 --- /dev/null +++ b/sources/lib/class/webdav_auth.class.php @@ -0,0 +1,38 @@ +catalog_id = $catalog_id; + } + + public function getChildren() + { + $children = array(); + $catalogs = null; + if ($this->catalog_id > 0) { + $catalogs = array(); + $catalogs[] = $this->catalog_id; + } + $artists = Catalog::get_artists($catalogs); + foreach ($artists as $artist) { + $children[] = new WebDAV_Directory($artist); + } + + return $children; + } + + public function getChild($name) + { + debug_event('webdav', 'Catalog getChild for `' . $name . '`', 5); + $matches = Catalog::search_childrens($name, $this->catalog_id); + debug_event('webdav', 'Found ' . count($matches) . ' childs.', 5); + // Always return first match + // Warning: this means that two items with the same name will not be supported for now + if (count($matches) > 0) + return WebDAV_Directory::getChildFromArray($matches[0]); + + throw new DAV\Exception\NotFound('The artist with name: ' . $name . ' could not be found'); + } + + public function childExists($name) + { + $matches = Catalog::search_childrens($name, $this->catalog_id); + return (count($matches) > 0); + } + + public function getName() + { + if ($this->catalog_id > 0) { + $catalog = Catalog::create_from_id($this->catalog_id); + return $catalog->name; + } + + return AmpConfig::get('site_title'); + } +} diff --git a/sources/lib/class/webdav_directory.class.php b/sources/lib/class/webdav_directory.class.php new file mode 100644 index 0000000..0acfab4 --- /dev/null +++ b/sources/lib/class/webdav_directory.class.php @@ -0,0 +1,102 @@ +libitem = $libitem; + $this->libitem->format(); + } + + public function getChildren() + { + debug_event('webdav', 'Directory getChildren', 5); + $children = array(); + $childs = $this->libitem->get_childrens(); + foreach ($childs as $key => $child) { + if (is_string($key)) { + foreach ($child as $schild) { + $children[] = WebDAV_Directory::getChildFromArray($schild); + } + } else { + $children[] = WebDAV_Directory::getChildFromArray($child); + } + } + return $children; + } + + public function getChild($name) + { + // Clean song name + if (strtolower(get_class($this->libitem)) === "album") { + $splitname = explode('-', $name, 3); + $name = trim($splitname[count($splitname) - 1]); + $nameinfo = pathinfo($name); + $name = $nameinfo['filename']; + } + debug_event('webdav', 'Directory getChild: ' . $name, 5); + $matches = $this->libitem->search_childrens($name); + // Always return first match + // Warning: this means that two items with the same name will not be supported for now + if (count($matches) > 0) + return WebDAV_Directory::getChildFromArray($matches[0]); + + throw new DAV\Exception\NotFound('The child with name: ' . $name . ' could not be found');; + } + + public static function getChildFromArray($array) + { + $libitem = new $array['object_type']($array['object_id']); + if (!$libitem->id) { + throw new DAV\Exception\NotFound('The library item `' . $array['object_type'] . '` with id `' . $array['object_id'] . '` could not be found'); + } + + if ($libitem instanceof media) { + return new WebDAV_File($libitem); + } else { + return new WebDAV_Directory($libitem); + } + } + + public function childExists($name) + { + $matches = $this->libitem->search_childrens($name); + return (count($matches) > 0); + } + + public function getName() + { + return $this->libitem->get_fullname(); + } +} diff --git a/sources/lib/class/webdav_file.class.php b/sources/lib/class/webdav_file.class.php new file mode 100644 index 0000000..fff2962 --- /dev/null +++ b/sources/lib/class/webdav_file.class.php @@ -0,0 +1,74 @@ +libitem = $libitem; + $this->libitem->format(); + } + + public function getName() + { + return $this->libitem->f_file; + } + + public function get() + { + debug_event('webdav', 'File get', 5); + // Only media associated to a local catalog is supported + if ($this->libitem->catalog) { + $catalog = Catalog::create_from_id($this->libitem->catalog); + if ($catalog->get_type() === 'local') { + return fopen($this->libitem->file, 'r'); + } else { + debug_event('webdav', 'Catalog associated to the media is not local. This is currently unsupported.', 3); + } + } else { + debug_event('webdav', 'No catalog associated to the media.', 3); + } + + return null; + } + + public function getSize() + { + return $this->libitem->size; + } + + public function getETag() + { + return md5(get_class($this->libitem) . "_" . $this->libitem->id . "_" . $this->libitem->update_time); + } +} diff --git a/sources/lib/class/webplayer.class.php b/sources/lib/class/webplayer.class.php index 04f2c13..10e7be4 100644 --- a/sources/lib/class/webplayer.class.php +++ b/sources/lib/class/webplayer.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,6 +22,11 @@ class WebPlayer { + /** + * Check if the playlist is a radio playlist. + * @param \Playlist $playlist + * @return boolean + */ public static function is_playlist_radio($playlist) { $radios = array(); @@ -35,87 +40,66 @@ class WebPlayer return (count($playlist->urls) == 1 && count($radios) > 0 && AmpConfig::get('webplayer_flash')); } + /** + * Check if the playlist is a video playlist. + * @param \Playlist $playlist + * @return boolean + */ public static function is_playlist_video($playlist) { return (count($playlist->urls) > 0 && $playlist->urls[0]->type == "video"); } - public static function browser_info($agent=null) - { - // Declare known browsers to look for - $known = array('msie', 'trident', 'firefox', 'safari', 'webkit', 'opera', 'netscape', 'konqueror', 'gecko'); - - // Clean up agent and build regex that matches phrases for known browsers - // (e.g. "Firefox/2.0" or "MSIE 6.0" (This only matches the major and minor - // version numbers. E.g. "2.0.0.6" is parsed as simply "2.0" - $agent = strtolower($agent ? $agent : $_SERVER['HTTP_USER_AGENT']); - $pattern = '#(?' . join('|', $known) . ')[/ ]+(?[0-9]+(?:\.[0-9]+)?)#'; - - // Find all phrases (or return empty array if none found) - if (!preg_match_all($pattern, $agent, $matches)) return array(); - - // Since some UAs have more than one phrase (e.g Firefox has a Gecko phrase, - // Opera 7,8 have a MSIE phrase), use the last one found (the right-most one - // in the UA). That's usually the most correct. - $i = count($matches['browser'])-1; - return array($matches['browser'][$i] => $matches['version'][$i]); - - } - + /** + * Get types information for an item. + * @param \playable_item $item + * @param string $force_type + * @return array + */ protected static function get_types($item, $force_type='') { $types = array('real' => 'mp3', 'player' => ''); - $browsers = array_keys(self::browser_info()); - $browser = ''; - if (count($browsers) > 0 ) { - $browser = $browsers[0]; - } - - if (!empty($force_type)) { - debug_event("webplayer.class.php", "Forcing type to {".$force_type."}", 5); - $types['real'] = $force_type; - } else { - if ($browser == "msie" || $browser == "trident" || $browser == "webkit" || $browser == "safari") { - $types['real'] = "mp3"; - } else { - $types['real'] = "ogg"; - } - } - - $song = null; + $media = null; $urlinfo = Stream_URL::parse($item->url); - if ($urlinfo['id'] && $urlinfo['type'] == 'song') { - $song = new Song($urlinfo['id']); + if ($urlinfo['id'] && Core::is_media($urlinfo['type'])) { + $media = new $urlinfo['type']($urlinfo['id']); } else if ($urlinfo['id'] && $urlinfo['type'] == 'song_preview') { - $song = new Song_Preview($urlinfo['id']); + $media = new Song_Preview($urlinfo['id']); } else if (isset($urlinfo['demo_id'])) { $democratic = new Democratic($urlinfo['demo_id']); if ($democratic->id) { $song_id = $democratic->get_next_object(); if ($song_id) { - $song = new Song($song_id); + $media = new Song($song_id); } } } - if ($song != null) { - $ftype = $song->type; + if ($media != null) { + $ftype = $media->type; $transcode = false; $transcode_cfg = AmpConfig::get('transcode'); // Check transcode is required - $ftype_transcode = AmpConfig::get('transcode_' . $ftype); - $valid_types = Song::get_stream_types_for_type($ftype); - if ($transcode_cfg == 'always' || !empty($force_type) || $ftype_transcode == 'required' || ($types['real'] != $ftype && !AmpConfig::get('webplayer_flash'))) { + $valid_types = Song::get_stream_types_for_type($ftype, 'webplayer'); + if ($transcode_cfg == 'always' || !empty($force_type) || !in_array('native', $valid_types) || ($types['real'] != $ftype && (!AmpConfig::get('webplayer_flash') || $urlinfo['type'] != 'song'))) { if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && in_array('transcode', $valid_types))) { - // Transcode only if excepted type available - $transcode_settings = $song->get_transcode_settings($types['real']); - if ($transcode_settings && AmpConfig::get('transcode_player_customize')) { - $transcode = true; - } else { + // Transcode forced from client side + if (!empty($force_type) && AmpConfig::get('transcode_player_customize')) { + debug_event("webplayer.class.php", "Forcing type to {".$force_type."}", 5); + // Transcode only if excepted type available + $transcode_settings = $media->get_transcode_settings($force_type, 'webplayer'); + if ($transcode_settings) { + $types['real'] = $transcode_settings['format']; + $transcode = true; + } + } + + // Transcode is not forced, transcode only if required + if (!$transcode) { if (!in_array('native', $valid_types)) { - $transcode_settings = $song->get_transcode_settings(null); + $transcode_settings = $media->get_transcode_settings(null, 'webplayer'); if ($transcode_settings) { $types['real'] = $transcode_settings['format']; $transcode = true; @@ -128,18 +112,18 @@ class WebPlayer if (!$transcode) { $types['real'] = $ftype; } - if ($types['real'] == "flac" || $types['real'] == "ogg") $types['player'] = "oga"; - else if ($types['real'] == "mp4") $types['player'] = "m4a"; - } else if ($urlinfo['id'] && $urlinfo['type'] == 'video') { - $video = new Video($urlinfo['id']); - $types['real'] = pathinfo($video->file, PATHINFO_EXTENSION); - if ($types['real'] == "ogg") $types['player'] = "ogv"; - else if ($types['real'] == "webm") $types['player'] = "webmv"; - else if ($types['real'] == "mp4") $types['player'] = "m4v"; - } else if ($item->type == 'radio') { + if ($urlinfo['type'] == 'song') { + if ($types['real'] == "ogg") $types['player'] = "oga"; + else if ($types['real'] == "mp4") $types['player'] = "m4a"; + } else if ($urlinfo['type'] == 'video') { + if ($types['real'] == "ogg") $types['player'] = "ogv"; + else if ($types['real'] == "webm") $types['player'] = "webmv"; + else if ($types['real'] == "mp4") $types['player'] = "m4v"; + } + } else if ($item->type == 'live_stream') { $types['real'] = $item->codec; - if ($types['real'] == "flac" || $types['real'] == "ogg") $types['player'] = "oga"; + if ($types['real'] == "ogg") $types['player'] = "oga"; } else { $ext = pathinfo($item->url, PATHINFO_EXTENSION); if (!empty($ext)) $types['real'] = $ext; @@ -151,6 +135,11 @@ class WebPlayer return $types; } + /** + * Get all supplied types for a playlist. + * @param \Playlist $playlist + * @return array + */ public static function get_supplied_types($playlist) { $jptypes = array(); @@ -168,6 +157,12 @@ class WebPlayer return $jptypes; } + /** + * Get add_media javascript. + * @param \Playlist $playlist + * @param string $callback_container + * @return string + */ public static function add_media_js($playlist, $callback_container='') { $addjs = ""; @@ -183,6 +178,33 @@ class WebPlayer return $addjs; } + /** + * Get play_next javascript. + * @param \Playlist $playlist + * @param string $callback_container + * @return string + */ + public static function play_next_js($playlist, $callback_container='') + { + $addjs = ""; + foreach ($playlist->urls as $item) { + if ($item->type == 'broadcast') { + $addjs .= $callback_container . "startBroadcastListening('" . $item->url . "');"; + break; + } else { + $addjs .= $callback_container . "playNext(" . self::get_media_js_param($item) . ");"; + } + } + + return $addjs; + } + + /** + * Get media javascript parameters. + * @param \playable_item $item + * @param string $force_type + * @return string + */ public static function get_media_js_param($item, $force_type='') { $js = array(); @@ -198,30 +220,37 @@ class WebPlayer $types = self::get_types($item, $force_type); - $song = null; + $media = null; $urlinfo = Stream_URL::parse($url); $url = $urlinfo['base_url']; - if ($urlinfo['id'] && $urlinfo['type'] == 'song') { - $song = new Song($urlinfo['id']); + if ($urlinfo['id'] && Core::is_media($urlinfo['type'])) { + $media = new $urlinfo['type']($urlinfo['id']); } else if ($urlinfo['id'] && $urlinfo['type'] == 'song_preview') { - $song = new Song_Preview($urlinfo['id']); + $media = new Song_Preview($urlinfo['id']); } else if (isset($urlinfo['demo_id'])) { $democratic = new Democratic($urlinfo['demo_id']); if ($democratic->id) { $song_id = $democratic->get_next_object(); if ($song_id) { - $song = new Song($song_id); + $media = new Song($song_id); } } } - if ($song != null) { - $js['artist_id'] = $song->artist; - $js['album_id'] = $song->album; - $js['song_id'] = $song->id; + if ($media != null) { + $media->format(); + if ($urlinfo['type'] == 'song') { + $js['artist_id'] = $media->artist; + $js['album_id'] = $media->album; + $js['replaygain_track_gain'] = $media->replaygain_track_gain; + $js['replaygain_track_peak'] = $media->replaygain_track_peak; + $js['replaygain_album_gain'] = $media->replaygain_album_gain; + $js['replaygain_album_peak'] = $media->replaygain_album_peak; + } + $js['media_id'] = $media->id; - if ($song->type != $types['real']) { + if ($media->type != $types['real']) { $url .= '&transcode_to=' . $types['real']; } //$url .= "&content_length=required"; @@ -230,7 +259,7 @@ class WebPlayer $js['filetype'] = $types['player']; $js['url'] = $url; if ($urlinfo['type'] == 'song') { - $js['poster'] = $item->image_url . (!AmpConfig::get('iframes') ? '&thumb=4' : ''); + $js['poster'] = $item->image_url; } debug_event("webplayer.class.php", "Return get_media_js_param {".json_encode($js)."}", 5); diff --git a/sources/lib/class/xml_data.class.php b/sources/lib/class/xml_data.class.php index ec1de77..0d7869e 100644 --- a/sources/lib/class/xml_data.class.php +++ b/sources/lib/class/xml_data.class.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -31,8 +31,8 @@ class XML_Data { // This is added so that we don't pop any webservers - private static $limit = '5000'; - private static $offset = '0'; + private static $limit = 5000; + private static $offset = 0; private static $type = ''; /** @@ -43,7 +43,6 @@ class XML_Data private function __construct() { // Rien a faire - } // constructor /** @@ -58,7 +57,6 @@ class XML_Data { $offset = intval($offset); self::$offset = $offset; - } // set_offset /** @@ -71,11 +69,12 @@ class XML_Data */ public static function set_limit($limit) { - if (!$limit) { return false; } + if (!$limit) { + return false; + } $limit = intval($limit); self::$limit = $limit; - } // set_limit /** @@ -88,10 +87,11 @@ class XML_Data */ public static function set_type($type) { - if (!in_array($type,array('rss','xspf','itunes'))) { return false; } + if (!in_array($type,array('rss','xspf','itunes'))) { + return false; + } self::$type = $type; - } // set_type /** @@ -108,7 +108,6 @@ class XML_Data { $string = self::_header() . "\t" . self::_footer(); return $string; - } // error /** @@ -131,7 +130,6 @@ class XML_Data $final .= self::_footer(); return $final; - } // single_string /** @@ -145,7 +143,6 @@ class XML_Data public static function header() { return self::_header(); - } // header /** @@ -159,7 +156,6 @@ class XML_Data public static function footer() { return self::_footer(); - } // footer /** @@ -173,19 +169,47 @@ class XML_Data $string = ''; if (is_array($tags)) { - + $atags = array(); foreach ($tags as $tag_id => $data) { - $tag = new Tag($tag_id); - $string .= "\tid . - '" count="' . count($data['users']) . - '">name . "]]>\n"; + if (array_key_exists($data['id'], $atags)) { + $atags[$data['id']]['count']++; + } else { + $atags[$data['id']] = array('name' => $data['name'], + 'count' => 1); + } + } + + foreach ($atags as $id => $data) { + $string .= "\t\n"; } } return $string; - } // tags_string + + /** + * playlist_song_tracks_string + * + * This returns the formatted 'playlistTrack' string for an xml document + * + */ + private static function playlist_song_tracks_string($song,$playlist_data) + { + if ($playlist_data == "") { + return ""; + } + $playlist_track = ""; + foreach ($playlist as $playlist_data) { + if ($playlist_data["object_id"] == $song->id) { + return "\t" . $playlist_data["track"] . "\n"; + } + } + return ""; + } // playlist_song_tracks_string + /** * keyed_array * @@ -215,7 +239,6 @@ class XML_Data } else { $string .= "\t<$key$attribute>\n"; } - } // end foreach if (!$callback) { @@ -223,7 +246,6 @@ class XML_Data } return $string; - } // keyed_array /** @@ -259,7 +281,6 @@ class XML_Data $final = self::_header() . $string . self::_footer(); return $final; - } // tags /** @@ -291,22 +312,21 @@ class XML_Data $string .= "id . "\">\n" . "\tf_full_name . "]]>\n" . $tag_string . - "\t" . $artist->albums . "\n" . - "\t" . $artist->songs . "\n" . - "\t" . $rating->get_user_rating() . "\n" . - "\t" . $rating->get_user_rating() . "\n" . - "\t" . $rating->get_average_rating() . "\n" . + "\t" . ($artist->albums ?: 0) . "\n" . + "\t" . ($artist->songs ?: 0) . "\n" . + "\t" . ($rating->get_user_rating() ?: 0) . "\n" . + "\t" . ($rating->get_user_rating() ?: 0) . "\n" . + "\t" . ($rating->get_average_rating() ?: 0) . "\n" . "\t" . $artist->mbid . "\n" . - "\t" . $artist->summary . "\n" . - "\t" . $artist->summary . "\n" . - "\t" . $artist->summary . "\n" . + "\tsummary . "]]>\n" . + "\t" . $artist->yearformed . "\n" . + "\tplaceformed . "]]>\n" . "\n"; } // end foreach artists $final = self::_header() . $string . self::_footer(); return $final; - } // artists /** @@ -333,7 +353,7 @@ class XML_Data $rating = new Rating($album_id,'album'); // Build the Art URL, include session - $art_url = AmpConfig::get('web_path') . '/image.php?id=' . $album->id . '&auth=' . scrub_out($_REQUEST['auth']); + $art_url = AmpConfig::get('web_path') . '/image.php?object_id=' . $album->id . '&object_type=album&auth=' . scrub_out($_REQUEST['auth']); $string .= "id . "\">\n" . "\tname . "]]>\n"; @@ -360,7 +380,6 @@ class XML_Data $final = self::_header() . $string . self::_footer(); return $final; - } // albums /** @@ -392,15 +411,12 @@ class XML_Data "\t$item_total\n" . "\t$playlist->type\n" . "\n"; - - } // end foreach // Build the final and then send her off $final = self::_header() . $string . self::_footer(); return $final; - } // playlists /** @@ -409,7 +425,7 @@ class XML_Data * This returns an xml document from an array of song ids. * (Spiffy isn't it!) */ - public static function songs($songs) + public static function songs($songs,$playlist_data='') { if (count($songs) > self::$limit OR self::$offset > 0) { $songs = array_slice($songs, self::$offset, self::$limit); @@ -424,8 +440,10 @@ class XML_Data $song = new Song($song_id); // If the song id is invalid/null - if (!$song->id) { continue; } - + if (!$song->id) { + continue; + } + $playlist_track_string = self::playlist_song_tracks_string($song, $playlist_data); $tag_string = self::tags_string(Tag::get_top_tags('song', $song_id)); $rating = new Rating($song_id, 'song'); $art_url = Art::url($song->album, 'album', $_REQUEST['auth']); @@ -441,26 +459,25 @@ class XML_Data $tag_string . "\tfile . "]]>\n" . "\t" . $song->track . "\n" . + $playlist_track_string . "\t\n" . "\t" . $song->year . "\n" . "\t" . $song->bitrate . "\n". "\t" . $song->mode . "\n". "\t" . $song->mime . "\n" . - "\tid) . "]]>\n" . + "\tid, '', 'api') . "]]>\n" . "\t" . $song->size . "\n". "\t" . $song->mbid . "\n". "\t" . $song->album_mbid . "\n". "\t" . $song->artist_mbid . "\n". "\t\n" . - "\t" . $rating->get_user_rating() . "\n" . - "\t" . $rating->get_user_rating() . "\n" . - "\t" . $rating->get_average_rating() . "\n" . + "\t" . ($rating->get_user_rating() ?: 0) . "\n" . + "\t" . ($rating->get_user_rating() ?: 0) . "\n" . + "\t" . ($rating->get_average_rating() ?: 0) . "\n" . "\n"; - } // end foreach return self::_header() . $string . self::_footer(); - } // songs /** @@ -488,15 +505,13 @@ class XML_Data "\t" . $video->f_resolution . "\n" . "\t" . $video->size . "\n" . self::tags_string($video->tags) . - "\tid) . "]]>\n" . + "\tid, '', 'api') . "]]>\n" . "\n"; - } // end foreach $final = self::_header() . $string . self::_footer(); return $final; - } // videos /** @@ -510,7 +525,9 @@ class XML_Data */ public static function democratic($object_ids=array()) { - if (!is_array($object_ids)) { $object_ids = array(); } + if (!is_array($object_ids)) { + $object_ids = array(); + } $democratic = Democratic::get_current_playlist(); @@ -540,7 +557,7 @@ class XML_Data "\t" . $song->track . "\n" . "\t\n" . "\t" . $song->mime . "\n" . - "\tid) . "]]>\n" . + "\tid, '', 'api') . "]]>\n" . "\t" . $song->size . "\n" . "\t\n" . "\t" . $rating->get_user_rating() . "\n" . @@ -548,15 +565,94 @@ class XML_Data "\t" . $rating->get_average_rating() . "\n" . "\t" . $democratic->get_vote($row_id) . "\n" . "\n"; - } // end foreach $final = self::_header() . $string . self::_footer(); return $final; - } // democratic + /** + * user + * + * This handles creating an xml document for an user + * + * @param User $user User + * @return string return xml + */ + public static function user(User $user) + { + $user->format(); + + $string = "id . "\">\n" . + "\tusername . "]]>\n" . + "\t" . $user->create_date . "\n" . + "\t" . $user->last_seen . "\n" . + "\twebsite . "]]>\n" . + "\tstate . "]]>\n" . + "\tcity . "]]>\n"; + if ($user->fullname_public) { + $string .= "\tfullname . "]]>\n"; + } + $string .= "\n"; + + $final = self::_header() . $string . self::_footer(); + + return $final; + } // user + + /** + * users + * + * This handles creating an xml document for an user list + * + * @param int[] $users User identifier list + * @return string return xml + */ + public static function users($users) + { + $string = "\n"; + foreach ($users as $user_id) { + $user = new User($user_id); + $string .= "\tusername . "]]>\n"; + } + $string .= "\n"; + + $final = self::_header() . $string . self::_footer(); + + return $final; + } // users + + /** + * shouts + * + * This handles creating an xml document for a shout list + * + * @param int[] $shouts Shout identifier list + * @return string return xml + */ + public static function shouts($shouts) + { + $string = "\n"; + foreach ($shouts as $shout_id) { + $shout = new Shoutbox($shout_id); + $shout->format(); + $user = new User($shout->user); + $string .= "\t\n" . + "\t\t" . $shout->date . "\n" . + "\t\ttext . "]]>\n"; + if ($user->id) { + $string .= "\t\tusername ."]]>"; + } + $string .= "\tn"; + } + $string .= "\n"; + + $final = self::_header() . $string . self::_footer(); + + return $final; + } // shouts + /** * rss_feed * @@ -572,8 +668,10 @@ class XML_Data */ public static function rss_feed($data,$title,$description,$date) { - $string = "\t$title\n\t" . AmpConfig::get('web_path') . "\n\t" . - "" . date("r",$date) . "\n"; + $string = "\t$title\n\t" . AmpConfig::get('web_path') . "\n\t"; + if ($date != null) { + $string .= "" . date("r",$date) . "\n"; + } // Pass it to the keyed array xml function foreach ($data as $item) { @@ -584,7 +682,6 @@ class XML_Data $final = self::_header() . $string . self::_footer(); return $final; - } // rss_feed /** @@ -633,7 +730,6 @@ class XML_Data } // end switch return $header; - } // _header /** @@ -662,7 +758,68 @@ class XML_Data return $footer; - } // _footer + public static function podcast(library_item $libitem) + { + $xml = new SimpleXMLElement(''); + $xml->addAttribute("version", "2.0"); + $xml->addAttribute("xmlns:xmlns:atom", "http://www.w3.org/2005/Atom"); + $xml->addAttribute("xmlns:xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd"); + $xchannel = $xml->addChild("channel"); + $xchannel->addChild("title", $libitem->get_fullname() . " Podcast"); + $xlink = $xchannel->addChild("atom:link"); + $xlink->addAttribute("type", "text/html"); + $xlink->addAttribute("href", $libitem->link); + if (Art::has_db($libitem->id, get_class($libitem))) { + $ximg = $xchannel->addChild("xmlns:itunes:image"); + $ximg->addAttribute("href", Art::url($libitem->id, get_class($libitem))); + } + $summary = $libitem->get_description(); + if (!empty($summary)) { + $xchannel->addChild("xmlns:itunes:summary", $summary); + } + $xchannel->addChild("xmlns:itunes:category", "Music"); + $owner = $libitem->get_user_owner(); + if ($owner) { + $user_owner = new User($owner); + $user_owner->format(); + $xowner = $xitem->addChild("xmlns:itunes:owner"); + $xowner->addChild("xmlns:itunes:name", $user_owner->f_name); + } + + $medias = $libitem->get_medias(); + foreach ($medias as $media_info) { + $media = new $media_info['object_type']($media_info['object_id']); + $media->format(); + $xitem = $xchannel->addChild("item"); + $xitem->addChild("title", $media->get_fullname()); + if ($media->f_artist) { + $xitem->addChild("xmlns:itunes:author", $media->f_artist); + } + $xmlink = $xitem->addChild("link"); + $xmlink->addAttribute("href", $media->link); + $xitem->addChild("guid", $media->link); + if ($media->addition_time) { + $xitem->addChild("pubDate", date("r", $media->addition_time)); + } + $description = $media->get_description(); + if (!empty($description)) { + $xitem->addChild("description", $description); + } + $xitem->addChild("xmlns:itunes:duration", $media->f_time); + $xencl = $xitem->addChild("enclosure"); + $xencl->addAttribute("type", $media->mime); + $xencl->addAttribute("length", $media->size); + $surl = $media_info['object_type']::play_url($media_info['object_id']); + $xencl->addAttribute("url", $surl); + } + + $xmlstr = $xml->asXml(); + // Format xml output + $dom = new DOMDocument(); + $dom->loadXML($xmlstr); + $dom->formatOutput = true; + return $dom->saveXML($dom->documentElement); + } } // XML_Data diff --git a/sources/lib/debug.lib.php b/sources/lib/debug.lib.php index 2f39a93..740fa59 100644 --- a/sources/lib/debug.lib.php +++ b/sources/lib/debug.lib.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -96,9 +96,10 @@ function check_config_values($conf) if (!$conf['database_username']) { return false; } - if (!$conf['database_password']) { + /* Don't check for password to support mysql socket auth + * if (!$conf['database_password']) { return false; - } + }*/ if (!$conf['session_length']) { return false; } @@ -206,6 +207,52 @@ function check_override_exec_time() return true; } +/** + * check_upload_size + * This checks to see if max upload size is not too small + */ +function check_upload_size() +{ + $upload_max = return_bytes(ini_get('upload_max_filesize')); + $post_max = return_bytes(ini_get('post_max_size')); + $mini = 20971520; // 20M + + return (($upload_max >= $mini || $upload_max <= 0) && ($post_max >= $mini || $post_max <= 0)); +} + +function check_php_int_size() +{ + return (PHP_INT_SIZE > 4); +} + +function check_php_zlib() +{ + return function_exists('gzcompress'); +} + +function check_php_simplexml() +{ + return function_exists('simplexml_load_string'); +} + +function return_bytes($val) +{ + $val = trim($val); + $last = strtolower($val[strlen($val)-1]); + switch ($last) { + // The 'G' modifier is available since PHP 5.1.0 + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + break; + } + + return $val; +} + /** * check_config_writable * This checks whether we can write the config file @@ -217,6 +264,12 @@ function check_config_writable() || (!file_exists(AmpConfig::get('prefix') . '/config/ampache.cfg.php') && is_writeable(AmpConfig::get('prefix') . '/config/'))); } +function check_htaccess_channel_writable() +{ + return ((file_exists(AmpConfig::get('prefix') . '/channel/.htaccess') && is_writable(AmpConfig::get('prefix') . '/channel/.htaccess')) + || (!file_exists(AmpConfig::get('prefix') . '/channel/.htaccess') && is_writeable(AmpConfig::get('prefix') . '/channel/'))); +} + function check_htaccess_rest_writable() { return ((file_exists(AmpConfig::get('prefix') . '/rest/.htaccess') && is_writable(AmpConfig::get('prefix') . '/rest/.htaccess')) @@ -239,7 +292,24 @@ function debug_result($status = false, $value = null, $comment = '') $class = $status ? 'success' : 'danger'; if (!$value) { - $value = $status ? 'OK' : 'ERROR'; + $value = $status ? T_('OK') : T_('ERROR'); + } + + return ''; +} + +/** + * debug_wresult + * + * Convenience function to format the output. + */ +function debug_wresult($status = false, $value = null, $comment = '') +{ + $class = $status ? 'success' : 'warning'; + + if (!$value) { + $value = $status ? T_('OK') : T_('WARNING'); } return ' - +
+
-
-
-
-
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
 
 
-
- -
-
- - -
-
-
-
-
- +

+
+
+
+

+
+
"> +
+ +
 
 
+
+ +
+
+ + +
+
+
+
+
+ +
 
 
+
+ +
+
+ + +
+
+
+
+
+ +
 
 
+
+ +
+
+ + +
+
+
+
+
+ + +
 
 
+
+ +
+
+ + +
+
+
+
+
+
 
 
+ +
+ +
+ [] +
+
+
+
+
-
 
 
- +
- - +
-
-
-
-
-
 
 
- - - -
- -
- [] -
- -
" - enctype="multipart/form-data" -> -
diff --git a/sources/templates/show_install_lang.inc.php b/sources/templates/show_install_lang.inc.php index e28fedf..7a00d1e 100644 --- a/sources/templates/show_install_lang.inc.php +++ b/sources/templates/show_install_lang.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_ip_history.inc.php b/sources/templates/show_ip_history.inc.php index 8745736..e5e1760 100644 --- a/sources/templates/show_ip_history.inc.php +++ b/sources/templates/show_ip_history.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_label.inc.php b/sources/templates/show_label.inc.php new file mode 100644 index 0000000..1a3d716 --- /dev/null +++ b/sources/templates/show_label.inc.php @@ -0,0 +1,109 @@ + +set_type($object_type); + +UI::show_box_top($label->f_name, 'info-box'); +if ($label->website) + echo "website) . "\">" . scrub_out($label->website) . "
"; +?> +
+ +
+
+ id, $label->f_name, 2); ?> +
+ address); ?> +
+
+
+ summary)); ?> +
+
+
+ +
+

:

+
    + + +
  • + + +
  • + + + email) { ?> +
  • + + +
  • + + can_edit()) { ?> +
  • + + + + + + +
  • + + +
  • + + + +
  • + +
+
+ +
+
+
    +
  • +
  • +
+
+
+
+show_objects($object_ids, true); + $browse->store(); +?> +
+id, 'songs')); +?> +
+ +
+
+
diff --git a/sources/templates/show_label_row.inc.php b/sources/templates/show_label_row.inc.php new file mode 100644 index 0000000..3444109 --- /dev/null +++ b/sources/templates/show_label_row.inc.php @@ -0,0 +1,54 @@ + +f_name); +?> + + id, $name, 1, AmpConfig::get('web_path') . '/labels.php?action=show&label=' . $libitem->id); + ?> + + +f_link; ?> +category; ?> +artists; ?> + + + + + + + + +can_edit()) { ?> + + + + + + + + + + diff --git a/sources/templates/show_labels.inc.php b/sources/templates/show_labels.inc.php new file mode 100644 index 0000000..69d2c5d --- /dev/null +++ b/sources/templates/show_labels.inc.php @@ -0,0 +1,76 @@ + + +
+
    +
  • +
+
+ +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> + + + + + + + + + + + + + + format(); + ?> + + + + + + + + + + + + + + + + + + + + + +
id . '&type=label&sort=name', T_('Label'),'label_sort_name'); ?>id . '&type=label&sort=category', T_('Category'),'label_sort_category'); ?>
id . '&type=label&sort=name', T_('Label'),'label_sort_name'); ?>id . '&type=label&sort=category', T_('Category'),'label_sort_category'); ?>
+ + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_license_row.inc.php b/sources/templates/show_license_row.inc.php new file mode 100644 index 0000000..ad146b3 --- /dev/null +++ b/sources/templates/show_license_row.inc.php @@ -0,0 +1,34 @@ + + + f_link; ?> + description; ?> + + + + + + + + + diff --git a/sources/templates/show_live_stream.inc.php b/sources/templates/show_live_stream.inc.php index c1062fe..a662d08 100644 --- a/sources/templates/show_live_stream.inc.php +++ b/sources/templates/show_live_stream.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,12 +20,14 @@ * */ ?> +
  • - +
+ diff --git a/sources/templates/show_live_stream_row.inc.php b/sources/templates/show_live_stream_row.inc.php index 364dfbb..aba6929 100644 --- a/sources/templates/show_live_stream_row.inc.php +++ b/sources/templates/show_live_stream_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,25 +24,20 @@  
- id, 'play', T_('Play live stream'),'play_live_stream_' . $radio->id); ?> + id, 'play', T_('Play live stream'),'play_live_stream_' . $libitem->id); ?>
-f_name_link; ?> - - - id,'add', T_('Add to temporary playlist'),'add_radio_' . $radio->id); ?> - - -f_url_link; ?> -codec; ?> +f_link; ?> +f_url_link; ?> +codec; ?> - + - id,'delete', T_('Delete'),'delete_live_stream_' . $radio->id); ?> + id,'delete', T_('Delete'),'delete_live_stream_' . $libitem->id); ?> diff --git a/sources/templates/show_live_streams.inc.php b/sources/templates/show_live_streams.inc.php index 26f0c21..ccf797a 100644 --- a/sources/templates/show_live_streams.inc.php +++ b/sources/templates/show_live_streams.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -28,7 +28,6 @@ $web_path = AmpConfig::get('web_path'); id . '&sort=name', T_('Name'),'live_stream_sort_name'); ?> - id . '&sort=codec', T_('Codec'),'live_stream_codec'); ?> @@ -37,10 +36,10 @@ $web_path = AmpConfig::get('web_path'); format(); + $libitem = new Live_Stream($radio_id); + $libitem->format(); ?> - + @@ -54,7 +53,6 @@ $web_path = AmpConfig::get('web_path'); id . '&sort=name', T_('Name'),'live_stream_sort_name'); ?> - id . '&sort=codec', T_('Codec'),'live_stream_codec_bottom'); ?> diff --git a/sources/templates/show_localplay_add_instance.inc.php b/sources/templates/show_localplay_add_instance.inc.php index 8f0bf6a..0665460 100644 --- a/sources/templates/show_localplay_add_instance.inc.php +++ b/sources/templates/show_localplay_add_instance.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_control.inc.php b/sources/templates/show_localplay_control.inc.php index 8477728..d084663 100644 --- a/sources/templates/show_localplay_control.inc.php +++ b/sources/templates/show_localplay_control.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_controllers.inc.php b/sources/templates/show_localplay_controllers.inc.php index 4f94417..5d8ec17 100644 --- a/sources/templates/show_localplay_controllers.inc.php +++ b/sources/templates/show_localplay_controllers.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_edit_instance.inc.php b/sources/templates/show_localplay_edit_instance.inc.php index cca208a..5e7c016 100644 --- a/sources/templates/show_localplay_edit_instance.inc.php +++ b/sources/templates/show_localplay_edit_instance.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_instances.inc.php b/sources/templates/show_localplay_instances.inc.php index 47af03a..a555516 100644 --- a/sources/templates/show_localplay_instances.inc.php +++ b/sources/templates/show_localplay_instances.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_playlist.inc.php b/sources/templates/show_localplay_playlist.inc.php index 0f741da..6c9122e 100644 --- a/sources/templates/show_localplay_playlist.inc.php +++ b/sources/templates/show_localplay_playlist.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_localplay_status.inc.php b/sources/templates/show_localplay_status.inc.php index 77ad9a3..e2a8027 100644 --- a/sources/templates/show_localplay_status.inc.php +++ b/sources/templates/show_localplay_status.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_login_form.inc.php b/sources/templates/show_login_form.inc.php index 4a87c9c..8b75713 100644 --- a/sources/templates/show_login_form.inc.php +++ b/sources/templates/show_login_form.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,89 +30,67 @@ if (AmpConfig::get('session_length') >= AmpConfig::get('remember_length')) { $htmllang = str_replace("_","-",AmpConfig::get('lang')); is_rtl(AmpConfig::get('lang')) ? $dir = 'rtl' : $dir = 'ltr'; +$_SESSION['login'] = true; +define('TABLE_RENDERED', 1); + ?> - - - - - - <?php echo scrub_out(AmpConfig::get('site_title')); ?> - - - + + + +
+ +
+

+
+
+ + +
+
+ + +
+
/> +
+ + + +
+ + + + + + + + +
+
+ +
+
+ - - - -"> -
- -
-

-
- -
- - -
-
- - -
-
/> -
- - - - -
- - - - - - - - -
- -
- -
-
- - + UI::show_footer(); + ?> diff --git a/sources/templates/show_lostpassword_form.inc.php b/sources/templates/show_lostpassword_form.inc.php index 99af682..60b11cf 100644 --- a/sources/templates/show_lostpassword_form.inc.php +++ b/sources/templates/show_lostpassword_form.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -28,52 +28,50 @@ if (AmpConfig::get('session_length') >= AmpConfig::get('remember_length')) { } $htmllang = str_replace("_","-",AmpConfig::get('lang')); is_rtl(AmpConfig::get('lang')) ? $dir = 'rtl' : $dir = 'ltr'; - ?> + - - - - - - -<?php echo scrub_out(AmpConfig::get('site_title')); ?> - - - - -
- -
-

-
- -
- -
-
-
-
- - + + + + + + + + + <?php echo scrub_out(AmpConfig::get('site_title')); ?> + + + +
+ +
+

+
+
+ + + +
+ +
+
+
+ + diff --git a/sources/templates/show_lyrics.inc.php b/sources/templates/show_lyrics.inc.php index f0a3c98..00b7291 100644 --- a/sources/templates/show_lyrics.inc.php +++ b/sources/templates/show_lyrics.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -28,21 +28,16 @@ $title = scrub_out($song->title); $album = scrub_out($song->f_album_full); $artist = scrub_out($song->f_artist_full); ?> -
- name != T_('Unknown (Orphaned)')) { - $aa_url = $web_path . "/image.php?id=" . $song->album . "&sid=" . session_id(); - echo ""; - echo "\"".$song-f_album_full."\" alt=\"".$song->f_album_full."\" height=\"128\" width=\"128\" />"; - echo "\n"; - } - ?> -
+album, $song->f_album_full, 2); +} +?>
- +
diff --git a/sources/templates/show_mail_users.inc.php b/sources/templates/show_mail_users.inc.php index 379d1f3..b2349bf 100644 --- a/sources/templates/show_mail_users.inc.php +++ b/sources/templates/show_mail_users.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_manage_catalogs.inc.php b/sources/templates/show_manage_catalogs.inc.php index 89d7644..9748869 100644 --- a/sources/templates/show_manage_catalogs.inc.php +++ b/sources/templates/show_manage_catalogs.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,17 +22,30 @@ ?>
-
    -
  • -
  • -
  • -
  • -
  • -
  • -
  • +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    -
    -
    + diff --git a/sources/templates/show_manage_democratic.inc.php b/sources/templates/show_manage_democratic.inc.php index be15eb2..1acb24a 100644 --- a/sources/templates/show_manage_democratic.inc.php +++ b/sources/templates/show_manage_democratic.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -40,7 +40,7 @@ UI::show_box_top(T_('Manage Democratic Playlists')); ?> ?> - + diff --git a/sources/templates/show_manage_license.inc.php b/sources/templates/show_manage_license.inc.php new file mode 100644 index 0000000..b285d75 --- /dev/null +++ b/sources/templates/show_manage_license.inc.php @@ -0,0 +1,61 @@ + +
    +
      +
    • + +
    • +
    +
    +
    /data/myNewMusic'); ?>
    name); ?>f_name_link; ?>f_link; ?> f_cooldown; ?> f_level; ?> f_primary; ?>
    + + + + + + + + + format(); + + require AmpConfig::get('prefix') . '/templates/show_license_row.inc.php'; + ?> + + + + + + + + + + + + + +
    diff --git a/sources/templates/show_manage_shoutbox.inc.php b/sources/templates/show_manage_shoutbox.inc.php index c1f4a47..23eb2ec 100644 --- a/sources/templates/show_manage_shoutbox.inc.php +++ b/sources/templates/show_manage_shoutbox.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,36 +23,43 @@ $web_path = AmpConfig::get('web_path'); ?> - - - - - - - - - format(); - $object = Shoutbox::get_object($shout->object_type,$shout->object_id); + + + + + + + + + + + + format(); + + $object = Shoutbox::get_object($libitem->object_type, $libitem->object_id); $object->format(); - $client = new User($shout->user); + $client = new User($libitem->user); $client->format(); - require AmpConfig::get('prefix') . '/templates/show_shout_row.inc.php'; - ?> - - - - - - - - - - - - - + require AmpConfig::get('prefix') . '/templates/show_shout_row.inc.php'; + ?> + + + + + + + + + + + + + + + +
    diff --git a/sources/templates/show_missing_album.inc.php b/sources/templates/show_missing_album.inc.php index 0b14fac..90e2c69 100644 --- a/sources/templates/show_missing_album.inc.php +++ b/sources/templates/show_missing_album.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -27,7 +27,7 @@ $title = scrub_out($walbum->name) . ' (' . $walbum->year . ')'; $title .= ' - ' . $walbum->f_artist_link; ?> -
    +
    mbid, 'album'); @@ -50,24 +50,6 @@ if (count($images) > 0 && !empty($images[0]['url'])) {

    :

      - - -
    • - mbid,'play_preview', T_('Play'),'directplay_full_' . $walbum->mbid); ?> - mbid, T_('Play'),'directplay_full_text_' . $walbum->mbid); ?> -
    • - - -
    • - mbid . '&append=true','play_add_preview', T_('Play last'),'addplay_album_' . $walbum->mbid); ?> - mbid . '&append=true', T_('Play last'),'addplay_album_text_' . $walbum->mbid); ?> -
    • - -
    • - mbid,'add', T_('Add to temporary playlist'),'play_full_' . $walbum->mbid); ?> - mbid, T_('Add to temporary playlist'), 'play_full_text_' . $walbum->mbid); ?> -
    • -
    • :
      diff --git a/sources/templates/show_missing_albums.inc.php b/sources/templates/show_missing_albums.inc.php index f458099..b5d07d0 100644 --- a/sources/templates/show_missing_albums.inc.php +++ b/sources/templates/show_missing_albums.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,15 +33,19 @@ - + - + - + diff --git a/sources/templates/show_missing_artist.inc.php b/sources/templates/show_missing_artist.inc.php index 8b2d092..f446359 100644 --- a/sources/templates/show_missing_artist.inc.php +++ b/sources/templates/show_missing_artist.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -27,7 +27,7 @@ UI::show_box_top($wartist['name'], 'info-box'); ?>
      @@ -37,7 +37,7 @@ if (AmpConfig::get('lastfm_api_key')) {
      diff --git a/sources/templates/show_missing_artists.inc.php b/sources/templates/show_missing_artists.inc.php new file mode 100644 index 0000000..8c40689 --- /dev/null +++ b/sources/templates/show_missing_artists.inc.php @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + +
      + +
      + diff --git a/sources/templates/show_newest.inc.php b/sources/templates/show_newest.inc.php index ef57588..b42dd10 100644 --- a/sources/templates/show_newest.inc.php +++ b/sources/templates/show_newest.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_now_playing.inc.php b/sources/templates/show_now_playing.inc.php index 56757c6..43b35c2 100644 --- a/sources/templates/show_now_playing.inc.php +++ b/sources/templates/show_now_playing.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -43,7 +43,13 @@ foreach ($results as $item) { if (!$np_user->fullname) { $np_user->fullname = "Ampache User"; } ?>
      - +
      -
      - - f_tags; ?> -
      + f_tags)) { ?> +
      + + f_tags; ?> +
      +
      - - <?php echo scrub_out($media->f_album_full); ?> - + album, $media->get_fullname(), 1, AmpConfig::get('web_path') . '/albums.php?action=show&album=' . $media->album); ?>
      @@ -85,19 +85,23 @@ $(document).ready(function(){ -
      - -
      - -
      - id,'song'); ?> + +
      + +
      + +
      + id,'song'); ?> +
      -
      -
      - -
      - id,'song'); ?> + + +
      + +
      + id,'song'); ?> +
      +
      -
      diff --git a/sources/templates/show_now_playing_similar.inc.php b/sources/templates/show_now_playing_similar.inc.php index 6fe3e2d..7d087df 100644 --- a/sources/templates/show_now_playing_similar.inc.php +++ b/sources/templates/show_now_playing_similar.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -37,7 +37,7 @@ } else { $artist = new Artist($a['id']); $artist->format(); - echo $artist->f_name_link; + echo $artist->f_link; } ?>
      diff --git a/sources/templates/show_now_playing_video_row.inc.php b/sources/templates/show_now_playing_video_row.inc.php new file mode 100644 index 0000000..5baa222 --- /dev/null +++ b/sources/templates/show_now_playing_video_row.inc.php @@ -0,0 +1,72 @@ +id); +$media->format(); +?> + + +
      +
      + + f_link; ?> +
      +
      + + +
      +
      + get_release_item_art(); + Art::display($release_art['object_type'], $release_art['object_id'], $media->get_fullname(), 6, $media->link); + ?> +
      +
      + + +
      + +
      + +
      + id, 'video'); ?> +
      +
      +
      + +
      + id,'video'); ?> +
      +
      + +
      diff --git a/sources/templates/show_object_rating.inc.php b/sources/templates/show_object_rating.inc.php index fde9c3f..6ee7f61 100644 --- a/sources/templates/show_object_rating.inc.php +++ b/sources/templates/show_object_rating.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_object_row.inc.php b/sources/templates/show_object_row.inc.php index d9edfd7..93b1e62 100644 --- a/sources/templates/show_object_row.inc.php +++ b/sources/templates/show_object_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_object_userflag.inc.php b/sources/templates/show_object_userflag.inc.php index 4f35730..34d205d 100644 --- a/sources/templates/show_object_userflag.inc.php +++ b/sources/templates/show_object_userflag.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_objects.inc.php b/sources/templates/show_objects.inc.php index 3518e25..0b982d5 100644 --- a/sources/templates/show_objects.inc.php +++ b/sources/templates/show_objects.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_partial_clip.inc.php b/sources/templates/show_partial_clip.inc.php new file mode 100644 index 0000000..5655253 --- /dev/null +++ b/sources/templates/show_partial_clip.inc.php @@ -0,0 +1,24 @@ +f_artist; +$videoprops[gettext_noop('Song')] = $video->f_song; diff --git a/sources/templates/show_partial_clip_row.inc.php b/sources/templates/show_partial_clip_row.inc.php new file mode 100644 index 0000000..1063b93 --- /dev/null +++ b/sources/templates/show_partial_clip_row.inc.php @@ -0,0 +1,23 @@ + +f_artist; ?> diff --git a/sources/templates/show_partial_clips.inc.php b/sources/templates/show_partial_clips.inc.php new file mode 100644 index 0000000..1480699 --- /dev/null +++ b/sources/templates/show_partial_clips.inc.php @@ -0,0 +1,23 @@ + +id . '&type=clip&sort=artist', T_('Artist'),'sort_video_artist'); ?> diff --git a/sources/templates/show_partial_edit_clip_row.inc.php b/sources/templates/show_partial_edit_clip_row.inc.php new file mode 100644 index 0000000..f1caac8 --- /dev/null +++ b/sources/templates/show_partial_edit_clip_row.inc.php @@ -0,0 +1,28 @@ + + + + + artist); ?> + + diff --git a/sources/templates/show_partial_edit_movie_row.inc.php b/sources/templates/show_partial_edit_movie_row.inc.php new file mode 100644 index 0000000..a9188dd --- /dev/null +++ b/sources/templates/show_partial_edit_movie_row.inc.php @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/sources/templates/show_partial_edit_personal_video_row.inc.php b/sources/templates/show_partial_edit_personal_video_row.inc.php new file mode 100644 index 0000000..13dce1c --- /dev/null +++ b/sources/templates/show_partial_edit_personal_video_row.inc.php @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/sources/templates/show_partial_edit_tvshow_episode_row.inc.php b/sources/templates/show_partial_edit_tvshow_episode_row.inc.php new file mode 100644 index 0000000..801fc4c --- /dev/null +++ b/sources/templates/show_partial_edit_tvshow_episode_row.inc.php @@ -0,0 +1,40 @@ + + + + + season); ?> + + + + + + + + + + + + + + diff --git a/sources/templates/show_partial_movie.inc.php b/sources/templates/show_partial_movie.inc.php new file mode 100644 index 0000000..c4448df --- /dev/null +++ b/sources/templates/show_partial_movie.inc.php @@ -0,0 +1,22 @@ +year; diff --git a/sources/templates/show_partial_movie_row.inc.php b/sources/templates/show_partial_movie_row.inc.php new file mode 100644 index 0000000..3e84698 --- /dev/null +++ b/sources/templates/show_partial_movie_row.inc.php @@ -0,0 +1,23 @@ + +year; ?> diff --git a/sources/templates/show_partial_movies.inc.php b/sources/templates/show_partial_movies.inc.php new file mode 100644 index 0000000..b10ca8a --- /dev/null +++ b/sources/templates/show_partial_movies.inc.php @@ -0,0 +1,24 @@ + +id . '&type=movie&sort=year', T_('Year'),'sort_video_year'); ?> diff --git a/sources/templates/show_partial_personal_video.inc.php b/sources/templates/show_partial_personal_video.inc.php new file mode 100644 index 0000000..404614c --- /dev/null +++ b/sources/templates/show_partial_personal_video.inc.php @@ -0,0 +1,24 @@ +f_location; +$videoprops[gettext_noop('Summary')] = scrub_out($video->summary); diff --git a/sources/templates/show_partial_personal_video_row.inc.php b/sources/templates/show_partial_personal_video_row.inc.php new file mode 100644 index 0000000..cd181c9 --- /dev/null +++ b/sources/templates/show_partial_personal_video_row.inc.php @@ -0,0 +1,23 @@ + +f_location; ?> diff --git a/sources/templates/show_partial_personal_videos.inc.php b/sources/templates/show_partial_personal_videos.inc.php new file mode 100644 index 0000000..c7604a5 --- /dev/null +++ b/sources/templates/show_partial_personal_videos.inc.php @@ -0,0 +1,23 @@ + +id . '&type=personal_video&sort=location', T_('Location'),'sort_video_location'); ?> diff --git a/sources/templates/show_partial_tvshow_episode.inc.php b/sources/templates/show_partial_tvshow_episode.inc.php new file mode 100644 index 0000000..c42e491 --- /dev/null +++ b/sources/templates/show_partial_tvshow_episode.inc.php @@ -0,0 +1,26 @@ +f_tvshow_link; +$videoprops[gettext_noop('Season')] = $video->f_season_link; +$videoprops[gettext_noop('Episode')] = $video->episode_number; +$videoprops[gettext_noop('Summary')] = $video->summary; diff --git a/sources/templates/show_partial_tvshow_episode_row.inc.php b/sources/templates/show_partial_tvshow_episode_row.inc.php new file mode 100644 index 0000000..4aad400 --- /dev/null +++ b/sources/templates/show_partial_tvshow_episode_row.inc.php @@ -0,0 +1,25 @@ + +episode_number; ?> +f_season_link; ?> +f_tvshow_link; ?> diff --git a/sources/templates/show_partial_tvshow_episodes.inc.php b/sources/templates/show_partial_tvshow_episodes.inc.php new file mode 100644 index 0000000..6e50464 --- /dev/null +++ b/sources/templates/show_partial_tvshow_episodes.inc.php @@ -0,0 +1,25 @@ + +id . '&type=tvshow_episode&sort=episode', T_('Episode'),'sort_video_episode'); ?> +id . '&type=tvshow_episode&sort=season', T_('Season'),'sort_video_season'); ?> +id . '&type=tvshow_episode&sort=tvshow', T_('TV Show'),'sort_video_tvshow'); ?> diff --git a/sources/templates/show_playlist.inc.php b/sources/templates/show_playlist.inc.php index a5f4a0c..b65f0de 100644 --- a/sources/templates/show_playlist.inc.php +++ b/sources/templates/show_playlist.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,8 +33,21 @@ $title = ob_get_contents(); ob_end_clean(); UI::show_box_top('
      ' . $title . '
      ', 'info-box'); ?> + + +
      + id,'playlist'); ?> +
      + + +
      + id,'playlist'); ?> +
      + +
        + has_access('50')) { ?>
      • @@ -42,9 +55,18 @@ UI::show_box_top('
      • -
      • - + +    +
      • +
      • + +    +
      • + + +
      • +    @@ -52,14 +74,14 @@ UI::show_box_top('
        ' . $title . '
      • - id,'play', T_('Play all'),'directplay_full_' . $playlist->id); ?> - id, T_('Play all'),'directplay_full_text_' . $playlist->id); ?> + id,'play', T_('Play all'),'directplay_full_' . $playlist->id); ?> + id, T_('Play all'),'directplay_full_text_' . $playlist->id); ?>
      • - id . '&append=true','play_add', T_('Play all last'),'addplay_playlist_' . $playlist->id); ?> - id . '&append=true', T_('Play all last'),'addplay_playlist_text_' . $playlist->id); ?> + id . '&append=true','play_add', T_('Play all last'),'addplay_playlist_' . $playlist->id); ?> + id . '&append=true', T_('Play all last'),'addplay_playlist_text_' . $playlist->id); ?>
      • @@ -70,7 +92,7 @@ UI::show_box_top('
        ' . $title . 'id,'random', T_('Random all to temporary playlist'),'play_playlist_random'); ?> id, T_('Random all to temporary playlist'),'play_playlist_random_text'); ?>
      • - has_access('50')) { ?> + has_access('50') && AmpConfig::get('channel')) { ?>
      • @@ -80,7 +102,7 @@ UI::show_box_top('
        ' . $title . ' has_access()) { ?>
      • - +    @@ -94,8 +116,8 @@ UI::show_box_top('
        ' . $title . 'set_type('playlist_song'); $browse->add_supplemental_object('playlist', $playlist->id); - $browse->set_static_content(false); - $browse->show_objects($object_ids); + $browse->set_static_content(true); + $browse->show_objects($object_ids, true); $browse->store(); ?>
        diff --git a/sources/templates/show_playlist_row.inc.php b/sources/templates/show_playlist_row.inc.php index feee6eb..c7817c9 100644 --- a/sources/templates/show_playlist_row.inc.php +++ b/sources/templates/show_playlist_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,39 +24,54 @@  
        - id,'play', T_('Play'),'play_playlist_' . $playlist->id); ?> + id,'play', T_('Play'),'play_playlist_' . $libitem->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_playlist_' . $playlist->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_playlist_' . $libitem->id); ?> + + + id . '&playnext=true', 'play_next', T_('Play next'), 'nextplay_playlist_' . $libitem->id); ?>
        -f_name_link; ?> +f_link; ?> - id,'add', T_('Add to temporary playlist'),'add_playlist_' . $playlist->id); ?> - id,'random', T_('Random to temporary playlist'),'random_playlist_' . $playlist->id); ?> - - - + id,'add', T_('Add to temporary playlist'),'add_playlist_' . $libitem->id); ?> + + id,'random', T_('Random to temporary playlist'),'random_playlist_' . $libitem->id); ?> + + + + -f_type; ?> - -f_user); ?> +f_type; ?> +get_song_count(); ?> +f_user); ?> + + + id, 'playlist'); ?> + + + id, 'playlist'); ?> + + - - + + - - + + + id, false); ?> + - has_access()) { ?> - + has_access()) { ?> + - id, 'delete', T_('Delete'), 'delete_playlist_'.$playlist->id, '', '', T_('Do you really want to delete the playlist?')); ?> + id, 'delete', T_('Delete'), 'delete_playlist_'.$libitem->id, '', '', T_('Do you really want to delete the playlist?')); ?> diff --git a/sources/templates/show_playlist_song_row.inc.php b/sources/templates/show_playlist_song_row.inc.php index fc84d39..6649ff2 100644 --- a/sources/templates/show_playlist_song_row.inc.php +++ b/sources/templates/show_playlist_song_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,45 +24,53 @@ '.$playlist_track.''; ?>
        - id, 'play', T_('Play'),'play_playlist_song_' . $song->id); ?> + id, 'play', T_('Play'),'play_playlist_song_' . $libitem->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $song->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $libitem->id); ?>
        -f_link; ?> +f_link; ?> - id,'add', T_('Add to temporary playlist'),'playlist_add_' . $song->id); ?> - - - + id,'add', T_('Add to temporary playlist'),'playlist_add_' . $libitem->id); ?> + + + + + -f_artist_link; ?> -f_album_link; ?> -f_tags; ?> -f_time; ?> - -id,'song'); ?> - - -id,'song'); ?> +f_artist_link; ?> +f_album_link; ?> +f_tags; ?> +f_time; ?> + + + id,'song'); ?> + + + id,'song'); ?> + - + - - + + + id, false); ?> + - has_access()) { ?> + has_access()) { ?> id . '&track_id=' . $object['track_id'],'delete', T_('Delete'),'track_del_' . $object['track_id']); ?> + + diff --git a/sources/templates/show_playlist_songs.inc.php b/sources/templates/show_playlist_songs.inc.php index 9f457b0..c9a4507 100644 --- a/sources/templates/show_playlist_songs.inc.php +++ b/sources/templates/show_playlist_songs.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -34,14 +34,16 @@ $web_path = AmpConfig::get('web_path'); - - - - + + + + + + @@ -53,8 +55,8 @@ $web_path = AmpConfig::get('web_path'); if (!is_array($object)) { $object = (array) $object; } - $song = new Song($object['object_id']); - $song->format(); + $libitem = new Song($object['object_id']); + $libitem->format(); $playlist_track = $object['track']; ?> @@ -71,16 +73,19 @@ $web_path = AmpConfig::get('web_path'); - - - - - - + + + + + + + +
      • + get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_playlist_title.inc.php b/sources/templates/show_playlist_title.inc.php index aad0de6..0a461ba 100644 --- a/sources/templates/show_playlist_title.inc.php +++ b/sources/templates/show_playlist_title.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_playlists.inc.php b/sources/templates/show_playlists.inc.php index cf0a7d3..91ee23e 100644 --- a/sources/templates/show_playlists.inc.php +++ b/sources/templates/show_playlists.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -30,17 +30,24 @@ id . '&type=playlist&sort=user', T_('Owner'),'playlist_sort_owner'); ?> + + + + + + + + format(); - $count = $playlist->get_song_count(); + $libitem = new Playlist($playlist_id); + $libitem->format(); ?> - + @@ -58,6 +65,14 @@ id . '&type=playlist&sort=user', T_('Owner'),'playlist_sort_owner_bottom'); ?> + + + + + + + + diff --git a/sources/templates/show_playlists_dialog.inc.php b/sources/templates/show_playlists_dialog.inc.php index eabf95a..9cbff83 100644 --- a/sources/templates/show_playlists_dialog.inc.php +++ b/sources/templates/show_playlists_dialog.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,7 +23,7 @@
        • - +
        • @@ -35,7 +35,7 @@ $playlist->format(); ?>
        • - + f_name; ?>
        • diff --git a/sources/templates/show_playtype_switch.inc.php b/sources/templates/show_playtype_switch.inc.php index 4ce9f08..0e82e7a 100644 --- a/sources/templates/show_playtype_switch.inc.php +++ b/sources/templates/show_playtype_switch.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_plugins.inc.php b/sources/templates/show_plugins.inc.php index 4d95330..cda3ecf 100644 --- a/sources/templates/show_plugins.inc.php +++ b/sources/templates/show_plugins.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -38,7 +38,7 @@ $web_path = AmpConfig::get('web_path'); foreach ($plugins as $plugin_name) { $plugin = new Plugin($plugin_name); $installed_version = Plugin::get_plugin_version($plugin->_plugin->name); - if (! $installed_version) { + if (!$installed_version) { $action = "" . T_('Activate') . ""; } else { diff --git a/sources/templates/show_popular.inc.php b/sources/templates/show_popular.inc.php index 344a861..9b5a0ad 100644 --- a/sources/templates/show_popular.inc.php +++ b/sources/templates/show_popular.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,25 +22,41 @@ UI::show_box_top(T_('Information')); -$sql = Stats::get_top_sql('song'); +$thresh_value = AmpConfig::get('stats_threshold'); + +$sql = Stats::get_top_sql('song', $thresh_value); $browse = new Browse(); +// We limit threshold for all items otherwise the counter will not be the same that the top_sql query. +// Example: Item '1234' => 3 counts during period with 'get_top_sql'. Without threshold, 'show_objects' would return the total which could be 24 during all time) +$browse->set_threshold($thresh_value); $browse->set_type('song', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); -$sql = Stats::get_top_sql('album'); +$sql = Stats::get_top_sql('album', $thresh_value); $browse = new Browse(); +$browse->set_threshold($thresh_value); $browse->set_type('album', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); -$sql = Stats::get_top_sql('artist'); +$sql = Stats::get_top_sql('artist', $thresh_value); $browse = new Browse(); +$browse->set_threshold($thresh_value); $browse->set_type('artist', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); +if (AmpConfig::get('allow_video')) { + $sql = Stats::get_top_sql('video'); + $browse = new Browse(); + $browse->set_type('video', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(null); + $browse->store(); +} + UI::show_box_bottom(); diff --git a/sources/templates/show_preference_admin.inc.php b/sources/templates/show_preference_admin.inc.php index fafe8b8..a378f01 100644 --- a/sources/templates/show_preference_admin.inc.php +++ b/sources/templates/show_preference_admin.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_preference_box.inc.php b/sources/templates/show_preference_box.inc.php index 9f6fc7e..0f8fca9 100644 --- a/sources/templates/show_preference_box.inc.php +++ b/sources/templates/show_preference_box.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,7 +23,7 @@ /* I'm cheating a little here, check to see if we want to show the * Apply to All button on this page */ -if ((Access::check('interface','100') OR !AmpConfig::get('use_auth')) AND $_REQUEST['action'] == 'admin') { +if (Access::check('interface','100') && $_REQUEST['action'] == 'admin') { $is_admin = true; } ?> diff --git a/sources/templates/show_preferences.inc.php b/sources/templates/show_preferences.inc.php index d8c1446..29ce46e 100644 --- a/sources/templates/show_preferences.inc.php +++ b/sources/templates/show_preferences.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_pvmsg.inc.php b/sources/templates/show_pvmsg.inc.php new file mode 100644 index 0000000..80a21d8 --- /dev/null +++ b/sources/templates/show_pvmsg.inc.php @@ -0,0 +1,45 @@ +f_subject, 'info-box'); +?> +
          + f_from_user_link . ' at ' . $pvmsg->f_creation_date; ?> +
          +
          +

          :

          + + +
          + +
          +
          + f_message); ?> +
          + + diff --git a/sources/templates/show_pvmsg_row.inc.php b/sources/templates/show_pvmsg_row.inc.php new file mode 100644 index 0000000..028cf2c --- /dev/null +++ b/sources/templates/show_pvmsg_row.inc.php @@ -0,0 +1,36 @@ + + +f_link; ?> +f_from_user_link; ?> +f_to_user_link; ?> +f_creation_date; ?> + + + + + + + + diff --git a/sources/templates/show_pvmsgs.inc.php b/sources/templates/show_pvmsgs.inc.php new file mode 100644 index 0000000..b29a03a --- /dev/null +++ b/sources/templates/show_pvmsgs.inc.php @@ -0,0 +1,85 @@ + + +
          +
            +
          • +
          • +
          • +
          • +
          +
          +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> + + + + + + + + + + + + + format(); + ?> + "> + + + + + + + + + + + + + + + + + + + +
          id . '&type=pvmsg&sort=subject', T_('Subject'),'pvmsg_sort_subject'); ?>id . '&type=pvmsg&sort=from_user', T_('Sender'),'pvmsg_sort_from_user'); ?>id . '&type=pvmsg&sort=to_user', T_('Recipient'),'pvmsg_sort_to_user'); ?>id . '&type=pvmsg&sort=creation_date', T_('Date'),'pvmsg_sort_creation_date'); ?>
          id . '&type=pvmsg&sort=subject', T_('Subject'),'pvmsg_sort_subject'); ?>id . '&type=pvmsg&sort=from_user', T_('Sender'),'pvmsg_sort_from_user'); ?>id . '&type=pvmsg&sort=to_user', T_('Recipient'),'pvmsg_sort_to_user'); ?>id . '&type=pvmsg&sort=creation_date', T_('Date'),'pvmsg_sort_creation_date'); ?>
          + + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_random.inc.php b/sources/templates/show_random.inc.php index f7ca1ca..b88e371 100644 --- a/sources/templates/show_random.inc.php +++ b/sources/templates/show_random.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -111,7 +111,7 @@ $browse->save_objects($object_ids); $browse->show_objects(); $browse->store(); - echo Ajax::observe('window','load',Ajax::action('?action=refresh_rightbar','playlist_refresh_load')); + echo Ajax::observe('window', 'load',Ajax::action('?action=refresh_rightbar','playlist_refresh_load')); } ?>
      diff --git a/sources/templates/show_random_albums.inc.php b/sources/templates/show_random_albums.inc.php index 3a556e0..d09ff82 100644 --- a/sources/templates/show_random_albums.inc.php +++ b/sources/templates/show_random_albums.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -35,7 +35,7 @@ if ($albums) {
      - get_http_album_query_ids('album_id'),'play', T_('Play'),'play_album_' . $album->id); ?> + get_http_album_query_ids('object_id'),'play', T_('Play'),'play_album_' . $album->id); ?> - get_http_album_query_ids('album_id') . '&append=true','play_add', T_('Play last'),'addplay_album_' . $album->id); ?> + get_http_album_query_ids('object_id') . '&append=true','play_add', T_('Play last'),'addplay_album_' . $album->id); ?> get_http_album_query_ids('id'),'add', T_('Add to temporary playlist'),'play_full_' . $album->id); ?>
      id . "_album\">"; show_rating($album->id, 'album'); echo "
      "; diff --git a/sources/templates/show_random_videos.inc.php b/sources/templates/show_random_videos.inc.php new file mode 100644 index 0000000..a1fc4f8 --- /dev/null +++ b/sources/templates/show_random_videos.inc.php @@ -0,0 +1,61 @@ + + +format(); + ?> +
      +
      + get_release_item_art(); + Art::display($release_art['object_type'], $release_art['object_id'], $video->get_fullname(), 6, $video->link); + } else { ?> + get_fullname(); ?> + +
      +
      + + id,'play', T_('Play'),'play_album_' . $video->id); ?> + + id . '&append=true','play_add', T_('Play last'),'addplay_video_' . $video->id); ?> + + +
      + id . "_video\">"; + show_rating($video->id, 'video'); + echo "
      "; + } + ?> +
      + + + + diff --git a/sources/templates/show_recent.inc.php b/sources/templates/show_recent.inc.php index 0e815c2..ebb4666 100644 --- a/sources/templates/show_recent.inc.php +++ b/sources/templates/show_recent.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_recently_played.inc.php b/sources/templates/show_recently_played.inc.php index 5e6b27b..193b783 100644 --- a/sources/templates/show_recently_played.inc.php +++ b/sources/templates/show_recently_played.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,7 +33,9 @@ UI::show_box_top(T_('Recently Played') . $link, 'box box_recently_played'); + + @@ -56,37 +58,38 @@ foreach ($data as $row) { if ($is_allowed || $has_allowed_agent) { $agent = $row['agent']; + if (!empty($row['geo_name'])) { + $agent .= ' - ' . $row['geo_name']; + } } if ($is_allowed || $has_allowed_time) { $interval = intval(time() - $row['date']); if ($interval < 60) { - $unit = ngettext('second ago', 'seconds ago', $interval); - } else if ($interval < 3600) { + $time_string = sprintf(ngettext('%d second ago', '%d seconds ago', $interval), $interval); + } elseif ($interval < 3600) { $interval = floor($interval / 60); - $unit = ngettext('minute ago', 'minutes ago', $interval); - } else if ($interval < 86400) { + $time_string = sprintf(ngettext('%d minute ago', '%d minutes ago', $interval), $interval); + } elseif ($interval < 86400) { $interval = floor($interval / 3600); - $unit = ngettext('hour ago', 'hours ago', $interval); - } else if ($interval < 604800) { + $time_string = sprintf(ngettext('%d hour ago', '%d hours ago', $interval), $interval); + } elseif ($interval < 604800) { $interval = floor($interval / 86400); - $unit = ngettext('day ago', 'days ago', $interval); - } else if ($interval < 2592000) { + $time_string = sprintf(ngettext('%d day ago', '%d days ago', $interval), $interval); + } elseif ($interval < 2592000) { $interval = floor($interval / 604800); - $unit = ngettext('week ago', 'weeks ago', $interval); - } else if ($interval < 31556926) { + $time_string = sprintf(ngettext('%d week ago', '%d weeks ago', $interval), $interval); + } elseif ($interval < 31556926) { $interval = floor($interval / 2592000); - $unit = ngettext('month ago', 'months ago', $interval); - } else if ($interval < 631138519) { + $time_string = sprintf(ngettext('%d month ago', '%d months ago', $interval), $interval); + } elseif ($interval < 631138519) { $interval = floor($interval / 31556926); - $unit = ngettext('year ago', 'years ago', $interval); + $time_string = sprintf(ngettext('%d year ago', '%d years ago', $interval), $interval); } else { $interval = floor($interval / 315569260); - $unit = ngettext('decade ago', 'decades ago', $interval); + $time_string = sprintf(ngettext('%d decade ago', '%d decades ago', $interval), $interval); } - - $time_string = sprintf('%d ' . (T_ngettext($unit, $unit, $interval)), $interval); } $song->format(); ?> @@ -95,9 +98,12 @@ foreach ($data as $row) {  
      - id,'play', T_('Play'),'play_song_' . $nb . '_' . $song->id); ?> + id,'play', T_('Play'),'play_song_' . $nb . '_' . $song->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $nb . '_' . $song->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $nb . '_' . $song->id); ?> + + + id . '&playnext=true', 'play_next', T_('Play next'), 'nextplay_song_' . $nb . '_' . $song->id); ?>
      @@ -119,11 +125,13 @@ foreach ($data as $row) {
      + + + + @@ -158,7 +168,7 @@ foreach ($data as $row) {
      diff --git a/sources/templates/show_recommended_artists.inc.php b/sources/templates/show_recommended_artists.inc.php index 47192ff..cc0a86c 100644 --- a/sources/templates/show_recommended_artists.inc.php +++ b/sources/templates/show_recommended_artists.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -51,10 +51,10 @@ $thcount = 8; /* Foreach through every artist that has been passed to us */ foreach ($object_ids as $artist_id) { - $artist = new Artist($artist_id); - $artist->format(); + $libitem = new Artist($artist_id); + $libitem->format(); ?> - + diff --git a/sources/templates/show_registration_confirmation.inc.php b/sources/templates/show_registration_confirmation.inc.php index 2dbaf86..17a7073 100644 --- a/sources/templates/show_registration_confirmation.inc.php +++ b/sources/templates/show_registration_confirmation.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,38 +22,43 @@ $htmllang = str_replace("_","-",AmpConfig::get('lang')); $web_path = AmpConfig::get('web_path'); + +$_SESSION['login'] = true; ?> - - -<?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> - - - - - - - - -
      - + + + + <?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> + + + + + + +
      + + + + +
      + + +

      + +

      +
      +
      -

      - -
      -
      -

      Ampache
      -For the love of Music.

      -
      - - diff --git a/sources/templates/show_rules.inc.php b/sources/templates/show_rules.inc.php index 4a423d9..3af05e1 100644 --- a/sources/templates/show_rules.inc.php +++ b/sources/templates/show_rules.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -59,7 +59,7 @@ $logic_operator = strtolower($logic_operator); if ($playlist) { $out = $playlist->to_js(); } else { - $mysearch = new Search($_REQUEST['type']); + $mysearch = new Search(null, $_REQUEST['type']); $mysearch->parse_rules(Search::clean_request($_REQUEST)); $out = $mysearch->to_js(); } diff --git a/sources/templates/show_run_add_catalog.inc.php b/sources/templates/show_run_add_catalog.inc.php index a7311dc..01d80c8 100644 --- a/sources/templates/show_run_add_catalog.inc.php +++ b/sources/templates/show_run_add_catalog.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_search.inc.php b/sources/templates/show_search.inc.php index 95e0ae6..811f1ff 100644 --- a/sources/templates/show_search.inc.php +++ b/sources/templates/show_search.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -19,42 +19,53 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ - -UI::show_box_top(T_('Search Ampache') . "...", 'box box_advanced_search'); ?> - + + +
      +set_type('playlist_song'); + $browse->add_supplemental_object('search', $playlist->id); + $browse->set_static_content(false); + $browse->show_objects($object_ids); + $browse->store(); +?> +
      diff --git a/sources/templates/show_search_bar.inc.php b/sources/templates/show_search_bar.inc.php index bafa559..acce4c1 100644 --- a/sources/templates/show_search_bar.inc.php +++ b/sources/templates/show_search_bar.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,7 +22,7 @@ ?>
      - + @@ -33,6 +33,12 @@ + + + + + + diff --git a/sources/templates/show_search_descriptor.inc.php b/sources/templates/show_search_descriptor.inc.php index 879680a..857e47d 100644 --- a/sources/templates/show_search_descriptor.inc.php +++ b/sources/templates/show_search_descriptor.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_search_form.inc.php b/sources/templates/show_search_form.inc.php new file mode 100644 index 0000000..8e0d7af --- /dev/null +++ b/sources/templates/show_search_form.inc.php @@ -0,0 +1,60 @@ + + + + + + + + + + +
       
      + + + + + +
      + +
      + + + +
      +    + +    + + +
      +
      + diff --git a/sources/templates/show_search_options.inc.php b/sources/templates/show_search_options.inc.php index a86d8e0..eb15a72 100644 --- a/sources/templates/show_search_options.inc.php +++ b/sources/templates/show_search_options.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -27,9 +27,9 @@ id,'add', T_('Add Search Results'),'add_search_results'); ?>
    • - +
    • - +
    • diff --git a/sources/templates/show_smartplaylist_row.inc.php b/sources/templates/show_search_row.inc.php similarity index 51% rename from sources/templates/show_smartplaylist_row.inc.php rename to sources/templates/show_search_row.inc.php index 95302ec..f938f67 100644 --- a/sources/templates/show_smartplaylist_row.inc.php +++ b/sources/templates/show_search_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,34 +24,36 @@  
      - id,'play', T_('Play'),'play_playlist_' . $playlist->id); ?> + id,'play', T_('Play'),'play_playlist_' . $libitem->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_playlist_' . $playlist->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_playlist_' . $libitem->id); ?>
      -f_name_link; ?> +f_link; ?> - id,'add', T_('Add to temporary playlist'),'add_playlist_' . $playlist->id); ?> - + id,'add', T_('Add to temporary playlist'),'add_playlist_' . $libitem->id); ?> + -f_type; ?> -f_user); ?> +f_type; ?> +random ? T_('Yes') : T_('No')); ?> +limit > 0) ? $libitem->limit : T_('None')); ?> +f_user); ?> - - + + - has_access()) { ?> - + has_access()) { ?> + - id,'delete', T_('Delete'),'delete_playlist_' . $playlist->id); ?> + id,'delete', T_('Delete'),'delete_playlist_' . $libitem->id); ?> diff --git a/sources/templates/show_smartplaylist_title.inc.php b/sources/templates/show_search_title.inc.php similarity index 95% rename from sources/templates/show_smartplaylist_title.inc.php rename to sources/templates/show_search_title.inc.php index 65b0ad1..e197a05 100644 --- a/sources/templates/show_smartplaylist_title.inc.php +++ b/sources/templates/show_search_title.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_smartplaylists.inc.php b/sources/templates/show_searches.inc.php similarity index 79% rename from sources/templates/show_smartplaylists.inc.php rename to sources/templates/show_searches.inc.php index 1d020b7..3e60bc6 100644 --- a/sources/templates/show_smartplaylists.inc.php +++ b/sources/templates/show_searches.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,6 +21,15 @@ */ ?> +
      +
        + +
      • + +
      • + +
      +
      get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php' ?> @@ -29,6 +38,8 @@ + + @@ -36,11 +47,11 @@ format(); + $libitem = new Search($playlist_id, 'song'); + $libitem->format(); ?> - - + + @@ -55,6 +66,8 @@ + + diff --git a/sources/templates/show_share.inc.php b/sources/templates/show_share.inc.php index a6837d4..33e969a 100644 --- a/sources/templates/show_share.inc.php +++ b/sources/templates/show_share.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,23 +22,24 @@ $embed = $_REQUEST['embed']; -if (empty($embed)) { - UI::show_box_top(T_('Shared on') . ' ' . AmpConfig::get('site_title'), 'box box_share'); - echo T_('by') . ' ' . $share->f_user . '
      '; - echo "" . $share->public_url . "
      "; - echo "

      "; +$is_share = true; +$playlist = $share->create_fake_playlist(); +require AmpConfig::get('prefix') . '/templates/show_web_player.inc.php'; + +if (empty($embed)) { + echo "" .T_('Shared by') . ' ' . $share->f_user . "
      "; if ($share->allow_download) { echo "id . "&secret=" . $share->secret . "\">" . UI::get_icon('download', T_('Download')) . " "; echo "id . "&secret=" . $share->secret . "\">" . T_('Download') . ""; } } -$is_share = true; -$iframed = true; -$playlist = $share->create_fake_playlist(); -require AmpConfig::get('prefix') . '/templates/show_web_player.inc.php'; - if (!empty($embed)) { UI::show_box_bottom(); +} else { +?> + + + + + + + + + + + + + + + + diff --git a/sources/templates/show_shared_object_row.inc.php b/sources/templates/show_shared_object_row.inc.php deleted file mode 100644 index be73c5f..0000000 --- a/sources/templates/show_shared_object_row.inc.php +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - diff --git a/sources/templates/show_shared_objects.inc.php b/sources/templates/show_shared_objects.inc.php index 6c6eb82..cc5c5dc 100644 --- a/sources/templates/show_shared_objects.inc.php +++ b/sources/templates/show_shared_objects.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -32,7 +32,7 @@ - + @@ -40,11 +40,11 @@ format(); + $libitem = new Share($share_id); + $libitem->format(); ?> - - + + diff --git a/sources/templates/show_shares.inc.php b/sources/templates/show_shares.inc.php index b2e4123..0c4a6b1 100644 --- a/sources/templates/show_shares.inc.php +++ b/sources/templates/show_shares.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,5 +21,13 @@ */ ?> +
      +
        +
      • + +
      • +
      +
      + diff --git a/sources/templates/show_shout_row.inc.php b/sources/templates/show_shout_row.inc.php index 67efdbd..97d6066 100644 --- a/sources/templates/show_shout_row.inc.php +++ b/sources/templates/show_shout_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,17 +20,17 @@ * */ ?> - + - - - + + + diff --git a/sources/templates/show_shoutbox.inc.php b/sources/templates/show_shoutbox.inc.php index b84d80f..80338fd 100644 --- a/sources/templates/show_shoutbox.inc.php +++ b/sources/templates/show_shoutbox.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -25,8 +25,9 @@ format(); ?> -
      +
      get_display(true, true); ?>
      diff --git a/sources/templates/show_smartplaylist.inc.php b/sources/templates/show_smartplaylist.inc.php deleted file mode 100644 index 0589369..0000000 --- a/sources/templates/show_smartplaylist.inc.php +++ /dev/null @@ -1,60 +0,0 @@ - -id . '">' . $title . '
      ' , 'box box_smartplaylist'); -?> -
      -
        - -
      • - - -
      • - -
      • - id,'add', T_('Add All'),'play_playlist'); ?> - -
      • - has_access()) { ?> -
      • - - - - -
      • - -
      -
      - - - -
      - -
      - - - diff --git a/sources/templates/show_song.inc.php b/sources/templates/show_song.inc.php index b33600f..01fd49c 100644 --- a/sources/templates/show_song.inc.php +++ b/sources/templates/show_song.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,24 +24,26 @@ $icon = $song->enabled ? 'disable' : 'enable'; $button_flip_state_id = 'button_flip_state_' . $song->id; ?> title . ' ' . T_('Details'), 'box box_song_details'); ?> -
      +
      - - -
      -
      -
      id,'song'); ?> -
      -
      - + + + +
      +
      +
      id,'song'); ?> +
      +
      + - - -
      -
      -
      id,'song'); ?> -
      -
      + + +
      +
      +
      id,'song'); ?> +
      +
      + @@ -56,42 +58,76 @@ $button_flip_state_id = 'button_flip_state_' . $song->id;
      - id, 'play', T_('Play'),'play_song_' . $song->id); ?> + id, 'play', T_('Play'),'play_song_' . $song->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $song->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_song_' . $song->id); ?> + + + id . '&playnext=true','play_next', T_('Play next'),'nextplay_song_' . $song->id); ?> show_custom_play_actions(); ?> id,'add', T_('Add to temporary playlist'),'add_song_' . $song->id); ?> - - - - - - - + + + + + + + + id, false); ?> + - - + + - + + + + + + user_upload == $GLOBALS['user']->id && AmpConfig::get('upload_allow_edit'))) { ?> + + + + + user_upload == $GLOBALS['user']->id && AmpConfig::get('upload_allow_edit'))) { ?> id,$icon, T_(ucfirst($icon)),'flip_song_' . $song->id); ?> + + + + +
      title); $songprops[gettext_noop('Artist')] = $song->f_artist_link; + if (!empty($song->f_albumartist_link)) { + $songprops[gettext_noop('Album Artist')] = $song->f_albumartist_link; + } $songprops[gettext_noop('Album')] = $song->f_album_link . ($song->year ? " (" . scrub_out($song->year). ")" : ""); - $songprops[gettext_noop('Genre')] = $song->f_genre_link; + $songprops[gettext_noop('Composer')] = scrub_out($song->composer); + $songprops[gettext_noop('Genre')] = $song->f_tags; + $songprops[gettext_noop('Year')] = $song->year; + $songprops[gettext_noop('Links')] = "f_artist) . "%22+%22" . rawurlencode($song->f_title) . "%22\" target=\"_blank\">" . UI::get_icon('google', T_('Search on Google ...')) . ""; + $songprops[gettext_noop('Links')] .= " f_artist) . "%22+%22" . rawurlencode($song->f_title) . "%22&type=track\" target=\"_blank\">" . UI::get_icon('lastfm', T_('Search on Last.fm ...')) . ""; $songprops[gettext_noop('Length')] = scrub_out($song->f_time); $songprops[gettext_noop('Comment')] = scrub_out($song->comment); - $songprops[gettext_noop('Label')] = scrub_out($song->label); + $songprops[gettext_noop('Label')] = AmpConfig::get('label') ? "label) . "\">" . scrub_out($song->label) . "" : scrub_out($song->label); $songprops[gettext_noop('Song Language')]= scrub_out($song->language); $songprops[gettext_noop('Catalog Number')] = scrub_out($song->catalog_number); $songprops[gettext_noop('Bitrate')] = scrub_out($song->f_bitrate); + $songprops[gettext_noop('Channels')] = scrub_out($song->channels); + if ($song->replaygain_track_gain != 0) { + $songprops[gettext_noop('ReplayGain Track Gain')] = scrub_out($song->replaygain_track_gain); + } + if ($song->replaygain_album_gain != 0) { + $songprops[gettext_noop('ReplayGain Album Gain')] = scrub_out($song->replaygain_album_gain); + } if (Access::check('interface','75')) { $songprops[gettext_noop('Filename')] = scrub_out($song->file) . " " . $song->f_size; } @@ -107,12 +143,28 @@ $button_flip_state_id = 'button_flip_state_' . $song->id; $songprops[gettext_noop('Lyrics')] = $song->f_lyrics; } - foreach ($songprops as $key => $value) { - if (trim($value)) { - $rowparity = UI::flip_class(); - echo "
      " . T_($key) . "
      " . $value . "
      "; - } + if (AmpConfig::get('licensing')) { + if ($song->license) { + $license = new License($song->license); + $license->format(); + $songprops[gettext_noop('Licensing')] = $license->f_link; } + } + + $owner_id = $song->get_user_owner(); + if (AmpConfig::get('sociable') && $owner_id > 0) { + $owner = new User($owner_id); + $owner->format(); + $songprops[gettext_noop('Uploaded by')] = $owner->f_link; + } + + + foreach ($songprops as $key => $value) { + if (trim($value)) { + $rowparity = UI::flip_class(); + echo "
      " . T_($key) . "
      " . $value . "
      "; + } + } ?>
      diff --git a/sources/templates/show_song_preview_row.inc.php b/sources/templates/show_song_preview_row.inc.php index 6377d54..da2c5df 100644 --- a/sources/templates/show_song_preview_row.inc.php +++ b/sources/templates/show_song_preview_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,28 +22,30 @@ ?>
      - + - - - - + + + + diff --git a/sources/templates/show_song_previews.inc.php b/sources/templates/show_song_previews.inc.php index ae500ed..e8bc2b2 100644 --- a/sources/templates/show_song_previews.inc.php +++ b/sources/templates/show_song_previews.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -38,9 +38,9 @@ - + diff --git a/sources/templates/show_song_row.inc.php b/sources/templates/show_song_row.inc.php index 5576c45..9bfafd4 100644 --- a/sources/templates/show_song_row.inc.php +++ b/sources/templates/show_song_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,63 +21,81 @@ */ ?> - + - - - - - - + + + + + + - - + + + + + + + - + diff --git a/sources/templates/show_songs.inc.php b/sources/templates/show_songs.inc.php index dd00abb..41df917 100644 --- a/sources/templates/show_songs.inc.php +++ b/sources/templates/show_songs.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,41 +24,46 @@ $web_path = AmpConfig::get('web_path'); $thcount = 8; ?> get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> -
      id . '&type=smartplaylist&sort=name', T_('Playlist Name'),'playlist_sort_name'); ?> id . '&type=smartplaylist&sort=user', T_('Owner'),'playlist_sort_owner'); ?>
      id . '&type=playlist&sort=name', T_('Playlist Name'),'playlist_sort_name_bottom'); ?> id . '&type=playlist&sort=user', T_('Owner'),'playlist_sort_owner_bottom'); ?>
      f_object_link; ?>object_type; ?>f_user; ?>f_creation_date; ?>f_lastvisit_date; ?>counter; ?>max_counter; ?>f_allow_stream; ?>f_allow_download; ?>expire_days; ?>public_url; ?> +
      + show_action_buttons(); + ?> +
      +
      f_object_link; ?>object_type; ?>f_user; ?>f_creation_date; ?>f_lastvisit_date; ?>counter; ?>max_counter; ?>f_allow_stream; ?>f_allow_download; ?>expire_days; ?>public_url; ?> -
      - show_action_buttons(); - ?> -
      -
      id . '&type=share&sort=max_counter', T_('Max Counter'),'sort_share_max_counter'); ?> id . '&type=share&sort=allow_stream', T_('Allow Stream'),'sort_share_allow_stream'); ?> id . '&type=share&sort=allow_download', T_('Allow Download'),'sort_share_allow_download'); ?>id . '&type=share&sort=expire', T_('Expire Days'),'sort_share_expire'); ?>id . '&type=share&sort=expire', T_('Expiry Days'),'sort_share_expire'); ?>
      f_link; ?> f_link; ?>sticky; ?>text); ?>date; ?>sticky; ?>text); ?>date; ?> - + - +
      - file)) { ?> - id,'play_preview', T_('Play'),'play_song_' . $song->id); ?> + file) { ?> + id,'play_preview', T_('Play'),'play_song_' . $libitem->id); ?> - id . '&append=true','play_add_preview', T_('Play last'),'addplay_song_' . $song->id); ?> + id . '&append=true','play_add_preview', T_('Play last'),'addplay_song_' . $libitem->id); ?> title; ?>title; ?> - file)) { ?> - id,'add', T_('Add to temporary playlist'),'add_' . $song->id); ?> - - - + file) { ?> + id,'add', T_('Add to temporary playlist'),'add_' . $libitem->id); ?> + + + + + f_artist_link; ?>f_album_link; ?>track; ?>disk; ?>f_artist_link; ?>f_album_link; ?>track; ?>disk; ?>
      - '.$song->f_track.''; } ?> + '.$libitem->f_track.''; } ?>
      - id, 'play', T_('Play'), 'play_song_' . $song->id); ?> + id, 'play', T_('Play'), 'play_song_' . $libitem->id); ?> - id . '&append=true', 'play_add', T_('Play last'), 'addplay_song_' . $song->id); ?> + id . '&append=true', 'play_add', T_('Play last'), 'addplay_song_' . $libitem->id); ?> + + + id . '&playnext=true', 'play_next', T_('Play next'), 'nextplay_song_' . $libitem->id); ?>
      f_link; ?>f_link; ?> - id,'add', T_('Add to temporary playlist'),'add_' . $song->id); ?> - - - + id,'add', T_('Add to temporary playlist'),'add_' . $libitem->id); ?> + + + + + - show_custom_play_actions(); ?> + show_custom_play_actions(); ?> f_artist_link; ?>f_album_link; ?>f_tags; ?>f_time; ?>id,'song'); ?>f_artist_link; ?>f_album_link; ?>f_tags; ?>f_time; ?>object_cnt; ?> id,'song'); ?>id,'song'); ?>id,'song'); ?> - - - - - - - - + + + + + + + id, false); ?> + - - - + + + user_upload == $GLOBALS['user']->id && AmpConfig::get('upload_allow_edit'))) { ?> + - enabled ? 'disable' : 'enable'; ?> - + + user_upload == $GLOBALS['user']->id && AmpConfig::get('upload_allow_edit'))) { ?> + enabled ? 'disable' : 'enable'; ?> + id; ?> - id,$icon, T_(ucfirst($icon)),'flip_song_' . $song->id); ?> + id,$icon, T_(ucfirst($icon)),'flip_song_' . $libitem->id); ?> + + + + +
      +
      - + - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + + format(); + $libitem = new Song($song_id, $limit_threshold); + $libitem->format(); ?> - + @@ -72,24 +77,30 @@ $thcount = 8; - + - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + +
      id . '&sort=title', T_('Song Title'), 'sort_song_title'.$browse->id); ?>id . '&sort=title' . $argument_param, T_('Song Title'), 'sort_song_title'.$browse->id); ?> id . '&sort=artist', T_('Artist'), 'sort_song_artist'.$browse->id); ?>id . '&sort=album', T_('Album'), 'sort_song_album'.$browse->id); ?>id . '&sort=artist' . $argument_param, T_('Artist'), 'sort_song_artist'.$browse->id); ?>id . '&sort=album' . $argument_param, T_('Album'), 'sort_song_album'.$browse->id); ?> id . '&sort=time', T_('Time'), 'sort_song_time'.$browse->id); ?>id . '&sort=time' . $argument_param, T_('Time'), 'sort_song_time'.$browse->id); ?>
      id . '&sort=title', T_('Song Title'), 'sort_song_title'.$browse->id); ?>id . '&sort=title' . $argument_param, T_('Song Title'), 'sort_song_title'.$browse->id); ?> id . '&sort=artist', T_('Artist'), 'sort_song_artist'.$browse->id); ?>id . '&sort=album', T_('Album'), 'sort_song_album'.$browse->id); ?>id . '&sort=artist' . $argument_param, T_('Artist'), 'sort_song_artist'.$browse->id); ?>id . '&sort=album' . $argument_param, T_('Album'), 'sort_song_album'.$browse->id); ?> id . '&sort=time', T_('Time'), 'sort_song_time'.$browse->id); ?>id . '&sort=time' . $argument_param, T_('Time'), 'sort_song_time'.$browse->id); ?>
      - + + get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_stats.inc.php b/sources/templates/show_stats.inc.php index b03d6e6..7c5c1e7 100644 --- a/sources/templates/show_stats.inc.php +++ b/sources/templates/show_stats.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -26,67 +26,80 @@ $catalogs = Catalog::get_catalogs(); - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + +

      -- - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + format(); $stats = Catalog::get_stats($catalog_id); ?> - - - - - - - - - - + + + + + + + + + + - +
      name; ?>f_path); ?>f_update); ?>f_add); ?>f_clean); ?>
      name; ?>f_path); ?>f_update); ?>f_add); ?>f_clean); ?>
      + + diff --git a/sources/templates/show_stats_highest.inc.php b/sources/templates/show_stats_highest.inc.php index 0fbd11f..8b9adaf 100644 --- a/sources/templates/show_stats_highest.inc.php +++ b/sources/templates/show_stats_highest.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -40,3 +40,12 @@ $browse->set_type('artist', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); + +if (AmpConfig::get('allow_video')) { + $sql = Rating::get_highest_sql('video'); + $browse = new Browse(); + $browse->set_type('video', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); +} diff --git a/sources/templates/show_stats_newest.inc.php b/sources/templates/show_stats_newest.inc.php index 9d1e166..5b424af 100644 --- a/sources/templates/show_stats_newest.inc.php +++ b/sources/templates/show_stats_newest.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,3 +33,12 @@ $browse->set_type('artist', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); + +if (AmpConfig::get('allow_video')) { + $sql = Stats::get_newest_sql('video'); + $browse = new Browse(); + $browse->set_type('video', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); +} diff --git a/sources/templates/show_stats_popular.inc.php b/sources/templates/show_stats_popular.inc.php index cb0072a..4b700b6 100644 --- a/sources/templates/show_stats_popular.inc.php +++ b/sources/templates/show_stats_popular.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -29,10 +29,18 @@ require AmpConfig::get('prefix') . '/templates/show_objects.inc.php'; UI::show_box_bottom(); $objects = Stats::get_top('artist'); -$headers = array('f_name_link' => T_('Most Popular Artists')); +$headers = array('f_link' => T_('Most Popular Artists')); UI::show_box_top('','info-box box_popular_artists'); require AmpConfig::get('prefix') . '/templates/show_objects.inc.php'; UI::show_box_bottom(); +if (AmpConfig::get('allow_video')) { + $objects = Stats::get_top('video'); + $headers = array('f_link' => T_('Most Popular Videos')); + UI::show_box_top('','info-box box_popular_videos'); + require AmpConfig::get('prefix') . '/templates/show_objects.inc.php'; + UI::show_box_bottom(); +} + ?>
    diff --git a/sources/templates/show_stats_recent.inc.php b/sources/templates/show_stats_recent.inc.php index 46f8651..ba16843 100644 --- a/sources/templates/show_stats_recent.inc.php +++ b/sources/templates/show_stats_recent.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -40,3 +40,12 @@ $browse->set_type('song', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); + +if (AmpConfig::get('allow_video')) { + $sql = Stats::get_recent_sql('video', $user_id); + $browse = new Browse(); + $browse->set_type('video', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); +} diff --git a/sources/templates/show_stats_share.inc.php b/sources/templates/show_stats_share.inc.php index c2cdb0e..3cb0180 100644 --- a/sources/templates/show_stats_share.inc.php +++ b/sources/templates/show_stats_share.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_stats_upload.inc.php b/sources/templates/show_stats_upload.inc.php new file mode 100644 index 0000000..8cff5f1 --- /dev/null +++ b/sources/templates/show_stats_upload.inc.php @@ -0,0 +1,44 @@ +id); +$browse = new Browse(); +$browse->set_type('song', $sql); +$browse->set_simple_browse(true); +$browse->show_objects(); +$browse->store(); + +$sql = Catalog::get_uploads_sql('album', $GLOBALS['user']->id); +$browse = new Browse(); +$browse->set_type('album', $sql); +$browse->set_simple_browse(true); +$browse->show_objects(); +$browse->store(); + +if (!AmpConfig::get('upload_user_artist')) { + $sql = Catalog::get_uploads_sql('artist', $GLOBALS['user']->id); + $browse = new Browse(); + $browse->set_type('artist', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); +} diff --git a/sources/templates/show_stats_userflag.inc.php b/sources/templates/show_stats_userflag.inc.php index af96c1a..84852c7 100644 --- a/sources/templates/show_stats_userflag.inc.php +++ b/sources/templates/show_stats_userflag.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -40,3 +40,19 @@ $browse->set_type('artist', $sql); $browse->set_simple_browse(true); $browse->show_objects(); $browse->store(); + +if (AmpConfig::get('allow_video')) { + $sql = Userflag::get_latest_sql('video'); + $browse = new Browse(); + $browse->set_type('video', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); +} + +$sql = Userflag::get_latest_sql('playlist'); +$browse = new Browse(); +$browse->set_type('playlist', $sql); +$browse->set_simple_browse(true); +$browse->show_objects(); +$browse->store(); diff --git a/sources/templates/show_stats_wanted.inc.php b/sources/templates/show_stats_wanted.inc.php index 1740b78..dac9a56 100644 --- a/sources/templates/show_stats_wanted.inc.php +++ b/sources/templates/show_stats_wanted.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_tag_row.inc.php b/sources/templates/show_tag_row.inc.php new file mode 100644 index 0000000..f70ea1f --- /dev/null +++ b/sources/templates/show_tag_row.inc.php @@ -0,0 +1,23 @@ +name; diff --git a/sources/templates/show_tagcloud.inc.php b/sources/templates/show_tagcloud.inc.php index 918a139..f276a10 100644 --- a/sources/templates/show_tagcloud.inc.php +++ b/sources/templates/show_tagcloud.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,8 +20,31 @@ * */ +$tag_types = array( + 'song' => T_('Song'), + 'album' => T_('Album'), + 'artist' => T_('Artist'), + 'video' => T_('Video'), +); ?> + +
    + : + + +
    +
    @@ -32,7 +55,7 @@
    • - +
    • @@ -44,6 +67,18 @@
    + +


    + + + diff --git a/sources/templates/show_test.inc.php b/sources/templates/show_test.inc.php index 6c0308d..fa68af6 100644 --- a/sources/templates/show_test.inc.php +++ b/sources/templates/show_test.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,6 +23,7 @@ + Ampache -- Debug Page diff --git a/sources/templates/show_test_config.inc.php b/sources/templates/show_test_config.inc.php index 498efa3..d375efb 100644 --- a/sources/templates/show_test_config.inc.php +++ b/sources/templates/show_test_config.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,6 +23,7 @@ + Ampache -- Config Debug Page diff --git a/sources/templates/show_test_table.inc.php b/sources/templates/show_test_table.inc.php index 8e10fcb..0d43eed 100644 --- a/sources/templates/show_test_table.inc.php +++ b/sources/templates/show_test_table.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -95,10 +95,28 @@ - + - + + + + + + + + + + + + + + + + + + + @@ -107,25 +125,43 @@ - + - + - + - + - + + + + + + + + + + = 20M). This is not strictly necessary, but may result in a better experience.'); ?> + + + + + + + + + 2GB). This is not strictly necessary, but may result in a better experience. This generally requires 64-bit operating system.'); ?> +set_type($object_type); + +UI::show_box_top($tvshow->f_name, 'info-box'); +?> +
    + id, $tvshow->f_name, 6); + ?> + summary) { ?> +
    + summary; ?> +
    + +
    + + +
    + id, 'tvshow'); ?> +
    + + +
    + id,'tvshow'); ?> +
    + + +
    +

    :

    +
      + +
    • + id,'play', T_('Play all'),'directplay_full_' . $tvshow->id); ?> + id, T_('Play all'),'directplay_full_text_' . $tvshow->id); ?> +
    • + + +
    • + id . '&append=true','play_add', T_('Play all last'),'addplay_tvshow_' . $tvshow->id); ?> + id . '&append=true', T_('Play all last'),'addplay_tvshow_text_' . $tvshow->id); ?> +
    • + + +
    • + + + + + + +
    • + + +
    • + + + +
    • + +
    +
    + +
    +
    +
      +
    • + +
    • +
    +
    +
    +
    +show_objects($object_ids, true); + $browse->store(); +?> +
    +
    +
    diff --git a/sources/templates/show_tvshow_row.inc.php b/sources/templates/show_tvshow_row.inc.php new file mode 100644 index 0000000..802854c --- /dev/null +++ b/sources/templates/show_tvshow_row.inc.php @@ -0,0 +1,64 @@ + + +   +
    + + id,'play', T_('Play'),'play_tvshow_' . $libitem->id); ?> + + id . '&append=true','play_add', T_('Play last'),'addplay_tvshow_' . $libitem->id); ?> + + +
    + + + + id, $libitem->f_name, 6, $libitem->link); ?> + + +f_link; ?> +episodes; ?> +seasons; ?> +f_tags; ?> + + + id,'tvshow'); ?> + + + id,'tvshow'); ?> + + + + + + + + + + + + + + diff --git a/sources/templates/show_tvshow_season.inc.php b/sources/templates/show_tvshow_season.inc.php new file mode 100644 index 0000000..ef8b27e --- /dev/null +++ b/sources/templates/show_tvshow_season.inc.php @@ -0,0 +1,101 @@ + +set_type($object_type); + +UI::show_box_top($season->f_name . ' - ' . $season->f_tvshow_link, 'info-box'); +?> +
    + id, $season->f_name, 6); + ?> +
    + + +
    + id, 'tvshow_season'); ?> +
    + + +
    + id,'tvshow_season'); ?> +
    + + +
    +

    :

    +
      + +
    • + id,'play', T_('Play all'),'directplay_full_' . $season->id); ?> + id, T_('Play all'),'directplay_full_text_' . $season->id); ?> +
    • + + +
    • + id . '&append=true','play_add', T_('Play all last'),'addplay_season_' . $season->id); ?> + id . '&append=true', T_('Play all last'),'addplay_season_text_' . $season->id); ?> +
    • + + +
    • + + + + + + +
    • + + +
    • + + + +
    • + +
    +
    + +
    +
    +
      +
    • + +
    • +
    +
    +
    +
    +show_objects($object_ids, true); + $browse->store(); +?> +
    +
    +
    diff --git a/sources/templates/show_tvshow_season_row.inc.php b/sources/templates/show_tvshow_season_row.inc.php new file mode 100644 index 0000000..5b66459 --- /dev/null +++ b/sources/templates/show_tvshow_season_row.inc.php @@ -0,0 +1,63 @@ + + +   +
    + + id,'play', T_('Play'),'play_season_' . $libitem->id); ?> + + id . '&append=true','play_add', T_('Play last'),'addplay_season_' . $libitem->id); ?> + + +
    + + + + id, $libitem->f_name, 6, $libitem->link); ?> + + +f_link; ?> +f_tvshow_link; ?> +episodes; ?> + + + id,'tvshow_season'); ?> + + + id,'tvshow_season'); ?> + + + + + + + + + + + + + + diff --git a/sources/templates/show_tvshow_seasons.inc.php b/sources/templates/show_tvshow_seasons.inc.php new file mode 100644 index 0000000..4e3419e --- /dev/null +++ b/sources/templates/show_tvshow_seasons.inc.php @@ -0,0 +1,89 @@ + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> + + + + + + + + + + + + + + + + + + + + + + + format(); + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id . '&sort=season', T_('Season'),'season_sort_season'); ?>id . '&sort=tvshow', T_('TV Show'),'season_sort_tvshow'); ?>
    id . '&sort=season', T_('Season'),'season_sort_name_bottom'); ?>id . '&sort=tvshow', T_('TV Show'),'season_sort_artist_bottom'); ?>
    + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_tvshows.inc.php b/sources/templates/show_tvshows.inc.php new file mode 100644 index 0000000..c72481d --- /dev/null +++ b/sources/templates/show_tvshows.inc.php @@ -0,0 +1,95 @@ + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> + + + + + + + + + + + + + + + + + + + + + + + + format(); + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id . '&type=tvshow&sort=name', T_('TV Show'),'tvshow_sort_name'); ?>
    id . '&type=tvshow&sort=name', T_('TV Show'),'tvshow_sort_name'); ?>
    + +get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_update_items.inc.php b/sources/templates/show_update_items.inc.php index d2fa992..cc87fbf 100644 --- a/sources/templates/show_update_items.inc.php +++ b/sources/templates/show_update_items.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_uploads.inc.php b/sources/templates/show_uploads.inc.php new file mode 100644 index 0000000..0b39e7a --- /dev/null +++ b/sources/templates/show_uploads.inc.php @@ -0,0 +1,25 @@ + + + + diff --git a/sources/templates/show_user.inc.php b/sources/templates/show_user.inc.php index 0045cac..a5394c0 100644 --- a/sources/templates/show_user.inc.php +++ b/sources/templates/show_user.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -24,25 +24,65 @@ $last_seen = $client->last_seen ? date("m\/d\/y - H:i",$client->last_seen) : T_( $create_date = $client->create_date ? date("m\/d\/y - H:i",$client->create_date) : T_('Unknown'); $client->format(); ?> -fullname); ?> +f_name); ?> +
    f_avatar) { - echo '
    ' . $client->f_avatar . '
    '; + echo $client->f_avatar . "

    "; } ?> -
    +get_display_follow(); + + $plugins = Plugin::get_plugins('display_user_field'); +?> +
      +load($client)) { +?> +
    • _plugin->display_user_field(); ?>
    • + +
    + +
    +
    -
    -
    fullname; ?>
    +
    +
    + f_name; ?> + + + + + + + + + id == $GLOBALS['user']->id) { ?> + + +
    -
    +
    +
    -
    f_useage; ?>
    +
    + f_useage; ?> + + + +
    +
    @@ -54,27 +94,95 @@ if ($client->f_avatar) {

    - - - - - -
    - id)); - $object_ids = $tmp_playlist->get_items(); - foreach ($object_ids as $object_data) { - $type = array_shift($object_data); - $object = new $type(array_shift($object_data)); - $object->format(); - echo $object->f_link; ?> -
    + +
    +
    +
      +
    • + +
    • -

    - -id); - Song::build_cache(array_keys($data)); - $user_id = $client->id; - require AmpConfig::get('prefix') . '/templates/show_recently_played.inc.php'; -?> +
  • + +
  • +
  • + +
+
+
+
+ id)); + $object_ids = $tmp_playlist->get_items(); + if (count($object_ids) > 0) { + UI::show_box_top(T_('Active Playlist')); + ?> + + + + +
+ format(); + echo $object->f_link; ?> +
+ +

+ + + id); + Song::build_cache(array_keys($data)); + $user_id = $client->id; + require AmpConfig::get('prefix') . '/templates/show_recently_played.inc.php'; + ?> +
+ +
+ id); + $browse = new Browse(); + $browse->set_type('artist', $sql); + $browse->set_simple_browse(true); + $browse->show_objects(); + $browse->store(); + ?> +
+ +
+ id); + $browse = new Browse(); + $browse->set_type('playlist'); + $browse->set_simple_browse(false); + $browse->show_objects($playlist_ids); + $browse->store(); + ?> +
+ +
+ get_following(); + $browse = new Browse(); + $browse->set_type('user'); + $browse->set_simple_browse(false); + $browse->show_objects($following_ids); + $browse->store(); + ?> +
+
+ get_followers(); + $browse = new Browse(); + $browse->set_type('user'); + $browse->set_simple_browse(false); + $browse->show_objects($follower_ids); + $browse->store(); + ?> +
+ +
+
diff --git a/sources/templates/show_user_activate.inc.php b/sources/templates/show_user_activate.inc.php index 6dc4125..498ef98 100644 --- a/sources/templates/show_user_activate.inc.php +++ b/sources/templates/show_user_activate.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -25,23 +25,25 @@ $web_path = AmpConfig::get('web_path'); ?> - - -<?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> - - - - - - - - - - -
+ + + + <?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> + + + + + + +
+ + + + +

-
-
-

Ampache
-For the love of Music.

-
- - +
+
+ diff --git a/sources/templates/show_user_preferences.inc.php b/sources/templates/show_user_preferences.inc.php index c91e5ae..e4cb976 100644 --- a/sources/templates/show_user_preferences.inc.php +++ b/sources/templates/show_user_preferences.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_user_registration.inc.php b/sources/templates/show_user_registration.inc.php index 20e598f..d28cf77 100644 --- a/sources/templates/show_user_registration.inc.php +++ b/sources/templates/show_user_registration.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -22,99 +22,131 @@ $htmllang = str_replace("_","-",AmpConfig::get('lang')); $web_path = AmpConfig::get('web_path'); + +$display_fields = (array) AmpConfig::get('registration_display_fields'); +$mandatory_fields = (array) AmpConfig::get('registration_mandatory_fields'); + +$_SESSION['login'] = true; ?> + - - -<?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> - - - - - - - + + + + <?php echo AmpConfig::get('site_title'); ?> - <?php echo T_('Registration'); ?> + + + + + + + + + -
- - + + +
+
+ +

+
+
+ +
-$action = scrub_in($_REQUEST['action']); -$fullname = scrub_in($_REQUEST['fullname']); -$username = scrub_in($_REQUEST['username']); -$email = scrub_in($_REQUEST['email']); -$website = scrub_in($_REQUEST['website']); -?> -
- - -

-
-
- -
+
+ + +
+
+ +

+
+ + + + +
+ +
+ + + +
+ -
- - -
-
- -

-
- - - - -
-
- - - -
+
+ + + +
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ -
- - - -
-
- - - -
+
+ + + +
-
- - - -
+
+ + +
-
- - -
+
+
+ +
-
- -
+ - - - - - -
- - ' /> -
- +
+ + ' /> +
+ diff --git a/sources/templates/show_user_row.inc.php b/sources/templates/show_user_row.inc.php index de50683..b941555 100644 --- a/sources/templates/show_user_row.inc.php +++ b/sources/templates/show_user_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,42 +21,55 @@ */ ?> - + f_avatar_mini) { - echo $client->f_avatar_mini; +if ($libitem->f_avatar_mini) { + echo $libitem->f_avatar_mini; } ?> - fullname; ?> (username; ?>) + username; ?> + fullname_public || Access::check('interface', 100)) { + echo "(" . $libitem->fullname . ")"; + } ?> - - - f_useage; ?> + + + + f_useage; ?> - - - ip_history; ?> - - + + + ip_history; ?> + + + + + + get_display_follow(); ?> - - + + + + + + disabled == '1') { - echo "id\">" . UI::get_icon('enable', T_('Enable')) . ""; + if ($libitem->disabled == '1') { + echo "id\">" . UI::get_icon('enable', T_('Enable')) . ""; } else { - echo "id\">" . UI::get_icon('disable', T_('Disable')) .""; + echo "id\">" . UI::get_icon('disable', T_('Disable')) .""; } ?> - + + - is_logged_in()) AND ($client->is_online())) { + is_logged_in()) AND ($libitem->is_online())) { echo "   "; - } elseif ($client->disabled == 1) { + } elseif ($libitem->disabled == 1) { echo "   "; } else { echo "   "; diff --git a/sources/templates/show_userflag.inc.php b/sources/templates/show_userflag.inc.php index 3f24100..58e56a2 100644 --- a/sources/templates/show_userflag.inc.php +++ b/sources/templates/show_userflag.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_users.inc.php b/sources/templates/show_users.inc.php index da9145a..65381b3 100644 --- a/sources/templates/show_users.inc.php +++ b/sources/templates/show_users.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -29,35 +29,42 @@ $web_path = AmpConfig::get('web_path'); + - + - + + - id . '&type=user&sort=fullname', T_('Fullname'),'users_sort_fullname'); ?>( ) + id . '&type=user&sort=fullname', T_('Fullname'),'users_sort_fullname'); ?> () id . '&type=user&sort=last_seen', T_('Last Seen'),'users_sort_lastseen'); ?> id . '&type=user&sort=create_date', T_('Registration Date'),'users_sort_createdate'); ?> + - + - - + + + + + + format(); - $last_seen = $client->last_seen ? date("m\/d\/Y - H:i",$client->last_seen) : T_('Never'); - $create_date = $client->create_date ? date("m\/d\/Y - H:i",$client->create_date) : T_('Unknown'); + $libitem = new User($user_id); + $libitem->format(); + $last_seen = $libitem->last_seen ? date("m\/d\/Y - H:i",$libitem->last_seen) : T_('Never'); + $create_date = $libitem->create_date ? date("m\/d\/Y - H:i",$libitem->create_date) : T_('Unknown'); ?> - + @@ -67,11 +74,16 @@ foreach ($object_ids as $user_id) { id . '&type=user&sort=fullname', T_('Fullname'),'users_sort_fullname1'); ?>( ) id . '&type=user&sort=last_seen', T_('Last Seen'),'users_sort_lastseen1'); ?> id . '&type=user&sort=create_date', T_('Registration Date'),'users_sort_createdate1'); ?> + - + - - + + + + + + diff --git a/sources/templates/show_verify_catalog.inc.php b/sources/templates/show_verify_catalog.inc.php index 3bc7315..411f5fb 100644 --- a/sources/templates/show_verify_catalog.inc.php +++ b/sources/templates/show_verify_catalog.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -23,8 +23,6 @@ UI::show_box_top(T_('Verify Catalog'), 'box box_verify_catalog'); /* HINT: Catalog Name */ printf(T_('Updating the %s catalog'), "[ $this->name ]"); -echo "
\n"; -printf(ngettext('%d item found checking tag information', '%d items found checking tag information', $number), $number); echo "
\n\n"; echo T_('Verified') . ': ' . $catalog_verify_found . '
'; echo T_('Reading') . ': ' . $catalog_verify_directory . ''; diff --git a/sources/templates/show_video.inc.php b/sources/templates/show_video.inc.php new file mode 100644 index 0000000..a608d6c --- /dev/null +++ b/sources/templates/show_video.inc.php @@ -0,0 +1,142 @@ + +f_title . ' ' . T_('Details'), 'box box_video_details'); ?> +
+id, $video->f_title, 8, null, false, 'preview'); +} +if (!$gart) { + $gart = Art::display('video', $video->id, $video->f_title, 7); +} +?> + +
+: + +
+ +
+
+ + + +
+
+
id,'video'); ?> +
+
+ + + + +
+
+
id,'video'); ?> +
+
+ + + +
+
+ + id, 'play', T_('Play'),'play_video_' . $video->id); ?> + + id . '&append=true','play_add', T_('Play last'),'addplay_video_' . $video->id); ?> + + + + + + + + id, false); ?> + + + + + + + + + + + + + + + + + + + +
+f_title); + $videoprops[gettext_noop('Length')] = scrub_out($video->f_time); +if (!strtolower(get_class($video)) != 'video') { + require AmpConfig::get('prefix') . '/templates/show_partial_' . strtolower(get_class($video)) . '.inc.php'; +} + $videoprops[gettext_noop('Release Date')] = scrub_out($video->f_release_date); + $videoprops[gettext_noop('Codec')] = scrub_out($video->f_codec); + $videoprops[gettext_noop('Resolution')] = scrub_out($video->f_resolution); + $videoprops[gettext_noop('Display')] = scrub_out($video->f_display); + $videoprops[gettext_noop('Audio Bitrate')] = scrub_out($video->f_bitrate); + $videoprops[gettext_noop('Video Bitrate')] = scrub_out($video->f_video_bitrate); + $videoprops[gettext_noop('Frame Rate')] = scrub_out($video->f_frame_rate); + $videoprops[gettext_noop('Channels')] = scrub_out($video->channels); + if (Access::check('interface','75')) { + $videoprops[gettext_noop('Filename')] = scrub_out($video->file) . " " . $video->f_size; + } + if ($video->update_time) { + $videoprops[gettext_noop('Last Updated')] = date("d/m/Y H:i",$video->update_time); + } + $videoprops[gettext_noop('Added')] = date("d/m/Y H:i",$video->addition_time); + if (AmpConfig::get('show_played_times')) { + $videoprops[gettext_noop('# Played')] = scrub_out($video->object_cnt); + } + + foreach ($videoprops as $key => $value) { + if (trim($value)) { + $rowparity = UI::flip_class(); + echo "
" . T_($key) . "
" . $value . "
"; + } + } +?> +
+ diff --git a/sources/templates/show_video_row.inc.php b/sources/templates/show_video_row.inc.php index 838de25..ad10a5f 100644 --- a/sources/templates/show_video_row.inc.php +++ b/sources/templates/show_video_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -19,30 +19,84 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ +if (!isset($video_type)) { + $libitem = Video::create_from_id($libitem->id); + $libitem->format(); + $video_type = strtolower(get_class($libitem)); +} ?>  
- id,'play', T_('Play'),'play_video_' . $video->id); ?> + id,'play', T_('Play'),'play_video_' . $libitem->id); ?> - id . '&append=true','play_add', T_('Play last'),'addplay_video_' . $video->id); ?> + id . '&append=true','play_add', T_('Play last'),'addplay_video_' . $libitem->id); ?> + + + id . '&playnext=true', 'play_next', T_('Play next'), 'nextplay_video_' . $libitem->id); ?>
-f_title; ?> - - - id,'add', T_('Add to temporary playlist'),'add_video_' . $video->id); ?> - + + + get_default_art_kind() == 'preview') { + $art_showed = Art::display('video', $libitem->id, $libitem->f_title, 9, $libitem->link, false, 'preview'); + } + if (!$art_showed) { + Art::display('video', $libitem->id, $libitem->f_title, 6, $libitem->link); + } + ?> -f_codec; ?> -f_resolution; ?> -f_length; ?> -f_tags; ?> + +f_link; ?> + +f_release_date; ?> +f_codec; ?> +f_resolution; ?> +f_length; ?> + +object_cnt; ?> + +f_tags; ?> + + + id, 'video'); ?> + + + id, 'video'); ?> + + + + + + + + + id, false); ?> + + - + + + + + + + + + + + diff --git a/sources/templates/show_videos.inc.php b/sources/templates/show_videos.inc.php index f39aa15..b9357b4 100644 --- a/sources/templates/show_videos.inc.php +++ b/sources/templates/show_videos.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -27,12 +27,31 @@ if ($browse->get_show_header()) require AmpConfig::get('prefix') . '/templates/l + + + id . '&type=video&sort=title', T_('Title'),'sort_video_title'); ?> - + + id . '&type=video&sort=release_date', T_('Release Date'),'sort_video_release_date'); ?> id . '&type=video&sort=codec', T_('Codec'),'sort_video_codec'); ?> id . '&type=video&sort=resolution', T_('Resolution'),'sort_video_rez'); ?> id . '&type=video&sort=length', T_('Time'),'sort_video_length'); ?> + + + + + + + + + + + @@ -40,31 +59,54 @@ if ($browse->get_show_header()) require AmpConfig::get('prefix') . '/templates/l format(); + if (isset($video_type)) { + $libitem = new $video_type($video_id); + } else { + $libitem = new Video($video_id); + } + $libitem->format(); ?> - + - + + + + id . '&type=video&sort=title', T_('Title'),'sort_video_title'); ?> - + + id . '&type=video&sort=release_date', T_('Release Date'),'sort_video_release_date'); ?> id . '&type=video&sort=codec', T_('Codec'),'sort_video_codec'); ?> id . '&type=video&sort=resolution', T_('Resolution'),'sort_video_rez'); ?> id . '&type=video&sort=length', T_('Time'),'sort_video_length'); ?> + + + + + + + + + + + - + get_show_header()) require AmpConfig::get('prefix') . '/templates/list_header.inc.php'; ?> diff --git a/sources/templates/show_wanted.inc.php b/sources/templates/show_wanted.inc.php index f23b436..f767c09 100644 --- a/sources/templates/show_wanted.inc.php +++ b/sources/templates/show_wanted.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/templates/show_wanted_album_row.inc.php b/sources/templates/show_wanted_album_row.inc.php index a1fee76..4e0e326 100644 --- a/sources/templates/show_wanted_album_row.inc.php +++ b/sources/templates/show_wanted_album_row.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,14 +21,14 @@ */ ?> -f_name_link; ?> -f_artist_link; ?> -year; ?> -f_user; ?> +f_link; ?> +f_artist_link; ?> +year; ?> +f_user; ?> -
+
show_action_buttons(); + $libitem->show_action_buttons(); ?>
diff --git a/sources/templates/show_wanted_albums.inc.php b/sources/templates/show_wanted_albums.inc.php index 9a1f674..5b1ba2b 100644 --- a/sources/templates/show_wanted_albums.inc.php +++ b/sources/templates/show_wanted_albums.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -33,10 +33,10 @@ format(); + $libitem = new Wanted($wanted_id); + $libitem->format(); ?> - + diff --git a/sources/templates/show_web_player.inc.php b/sources/templates/show_web_player.inc.php index ab9140e..cb9ef1e 100644 --- a/sources/templates/show_web_player.inc.php +++ b/sources/templates/show_web_player.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2013 Ampache.org + * Copyright 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 @@ -28,52 +28,17 @@ header('Expires: ' . gmdate(DATE_RFC1123, time()-1)); + <?php echo AmpConfig::get('site_title'); ?> + + + + - - -'home', 'title' => T_('Home'), 'icon'=>'home', 'access'=>5); -$sidebar_items[] = array('id'=>'localplay', 'title' => T_('Localplay'), 'icon'=>'volumeup', 'access'=>5); -$sidebar_items[] = array('id'=>'preferences', 'title' => T_('Preferences'), 'icon'=>'edit', 'access'=>5); -$sidebar_items[] = array('id'=>'modules','title' => T_('Modules'),'icon'=>'plugin','access'=>100); -$sidebar_items[] = array('id'=>'admin', 'title' => T_('Admin'), 'icon'=>'admin', 'access'=>100); - -$web_path = AmpConfig::get('web_path'); ?> - + + diff --git a/sources/templates/sidebar.light.inc.php b/sources/templates/sidebar.light.inc.php new file mode 100644 index 0000000..762f68d --- /dev/null +++ b/sources/templates/sidebar.light.inc.php @@ -0,0 +1,33 @@ + + + diff --git a/sources/templates/sidebar_admin.inc.php b/sources/templates/sidebar_admin.inc.php index 4fe7df7..9f81f44 100644 --- a/sources/templates/sidebar_admin.inc.php +++ b/sources/templates/sidebar_admin.inc.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -21,26 +21,28 @@ */ ?>
-

3.3.3.5. According to your database your current version is: %s.'), Update::format_version($version)); ?>

-

+

3.3.3.5. Your current version is %s with database version %s.'), AmpConfig::get('version'), $version); ?>

+

diff --git a/sources/upload.php b/sources/upload.php new file mode 100644 index 0000000..7d2016b --- /dev/null +++ b/sources/upload.php @@ -0,0 +1,58 @@ + 0 && ($post_max < $upload_max || $upload_max == 0)) { + $upload_max = $post_max; +} +// Check to handle POST requests exceeding max post size. +if ($_SERVER['CONTENT_LENGTH'] > 0 && $post_max > 0 && $_SERVER['CONTENT_LENGTH'] > $post_max) { + Upload::rerror(); + exit; +} + +/* Switch on the action passed in */ +switch ($_REQUEST['actionp']) { + case 'upload': + if (AmpConfig::get('demo_mode')) { + UI::access_denied(); + exit; + } + + Upload::process(); + exit; + + default: + UI::show_header(); + require AmpConfig::get('prefix') . '/templates/show_add_upload.inc.php'; + break; +} // switch on the action + +UI::show_footer(); diff --git a/sources/upnp/MediaServerConnectionManager.xml b/sources/upnp/MediaServerConnectionManager.xml new file mode 100644 index 0000000..ae9af0a --- /dev/null +++ b/sources/upnp/MediaServerConnectionManager.xml @@ -0,0 +1,134 @@ + + + +1 +0 + + + + GetProtocolInfo + + + Source + out + SourceProtocolInfo + + + Sink + out + SinkProtocolInfo + + + + + GetCurrentConnectionIDs + + + ConnectionIDs + out + CurrentConnectionIDs + + + + + GetCurrentConnectionInfo + + + ConnectionID + in + A_ARG_TYPE_ConnectionID + + + RcsID + out + A_ARG_TYPE_RcsID + + + AVTransportID + out + A_ARG_TYPE_AVTransportID + + + ProtocolInfo + out + A_ARG_TYPE_ProtocolInfo + + + PeerConnectionManager + out + A_ARG_TYPE_ConnectionManager + + + PeerConnectionID + out + A_ARG_TYPE_ConnectionID + + + Direction + out + A_ARG_TYPE_Direction + + + Status + out + A_ARG_TYPE_ConnectionStatus + + + + + + + + SourceProtocolInfo + string + + + SinkProtocolInfo + string + + + CurrentConnectionIDs + string + + + A_ARG_TYPE_ConnectionStatus + string + + OK + ContentFormatMismatch + InsufficientBandwidth + UnreliableChannel + Unknown + + + + A_ARG_TYPE_ConnectionManager + string + + + A_ARG_TYPE_Direction + string + + Input + Output + + + + A_ARG_TYPE_ProtocolInfo + string + + + A_ARG_TYPE_ConnectionID + i4 + + + A_ARG_TYPE_AVTransportID + i4 + + + A_ARG_TYPE_RcsID + i4 + + + + diff --git a/sources/upnp/MediaServerContentDirectory.xml b/sources/upnp/MediaServerContentDirectory.xml new file mode 100644 index 0000000..d7eba64 --- /dev/null +++ b/sources/upnp/MediaServerContentDirectory.xml @@ -0,0 +1,208 @@ + + + + 1 + 0 + + + + GetSearchCapabilities + + + SearchCaps + out + SearchCapabilities + + + + + GetSortCapabilities + + + SortCaps + out + SortCapabilities + + + + + GetSystemUpdateID + + + Id + out + SystemUpdateID + + + + + Browse + + + ObjectID + in + A_ARG_TYPE_ObjectID + + + BrowseFlag + in + A_ARG_TYPE_BrowseFlag + + + Filter + in + A_ARG_TYPE_Filter + + + StartingIndex + in + A_ARG_TYPE_Index + + + RequestedCount + in + A_ARG_TYPE_Count + + + SortCriteria + in + A_ARG_TYPE_SortCriteria + + + Result + out + A_ARG_TYPE_Result + + + NumberReturned + out + A_ARG_TYPE_Count + + + TotalMatches + out + A_ARG_TYPE_Count + + + UpdateID + out + A_ARG_TYPE_UpdateID + + + + + + Search + + + ContainerID + in + A_ARG_TYPE_ObjectID + + + SearchCriteria + in + A_ARG_TYPE_SearchCriteria + + + Filter + in + A_ARG_TYPE_Filter + + + StartingIndex + in + A_ARG_TYPE_Index + + + RequestedCount + in + A_ARG_TYPE_Count + + + SortCriteria + in + A_ARG_TYPE_SortCriteria + + + Result + out + A_ARG_TYPE_Result + + + NumberReturned + out + A_ARG_TYPE_Count + + + TotalMatches + out + A_ARG_TYPE_Count + + + UpdateID + out + A_ARG_TYPE_UpdateID + + + + + + + A_ARG_TYPE_ObjectID + string + + + A_ARG_TYPE_Result + string + + + A_ARG_TYPE_SearchCriteria + string + + + A_ARG_TYPE_BrowseFlag + string + + BrowseMetadata + BrowseDirectChildren + + + + A_ARG_TYPE_Filter + string + + + A_ARG_TYPE_SortCriteria + string + + + A_ARG_TYPE_Index + ui4 + + + A_ARG_TYPE_Count + ui4 + + + A_ARG_TYPE_UpdateID + ui4 + + + SearchCapabilities + string + + + SortCapabilities + string + + + SystemUpdateID + ui4 + + + ContainerUpdateIDs + string + + + diff --git a/sources/upnp/MediaServerServiceDesc.php b/sources/upnp/MediaServerServiceDesc.php new file mode 100644 index 0000000..b6a87c2 --- /dev/null +++ b/sources/upnp/MediaServerServiceDesc.php @@ -0,0 +1,91 @@ +'; +?> + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaServer:1 + Ampache + ampache.org + http://ampache.org + + Ampache + + http://ampache.org + uuid: + + + image/png + 32 + 32 + 24 + /upnp/images/icon32.png + + + image/png + 48 + 48 + 24 + /upnp/images/icon48.png + + + image/png + 120 + 120 + 24 + /upnp/images/icon120.png + + + image/jpeg + 32 + 32 + 24 + /upnp/images/icon32.jpg + + + image/jpeg + 48 + 48 + 24 + /upnp/images/icon48.jpg + + + image/jpeg + 120 + 120 + 24 + /upnp/images/icon120.jpg + + + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /upnp/control-reply.php + /upnp/event-reply.php + /upnp/MediaServerContentDirectory.xml + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /upnp/cm-control-reply.php + /upnp/cm-event-reply.php + /upnp/MediaServerConnectionManager.xml + + + + diff --git a/sources/upnp/cm-control-reply.php b/sources/upnp/cm-control-reply.php new file mode 100644 index 0000000..e2fbc85 --- /dev/null +++ b/sources/upnp/cm-control-reply.php @@ -0,0 +1,32 @@ + diff --git a/sources/upnp/cm-event-reply.php b/sources/upnp/cm-event-reply.php new file mode 100644 index 0000000..99dcd3f --- /dev/null +++ b/sources/upnp/cm-event-reply.php @@ -0,0 +1,3 @@ + diff --git a/sources/upnp/control-reply.php b/sources/upnp/control-reply.php new file mode 100644 index 0000000..2aaab78 --- /dev/null +++ b/sources/upnp/control-reply.php @@ -0,0 +1,104 @@ + '0', + 'parentID' => '-1', + 'childCount' => '2', + 'dc:title' => T_('root'), + 'upnp:class' => 'object.container', + ); + } else { + $items = $rootMediaItems; + } + break; + } else { + # The parse_url function returns an array in this format: + # Array ( + # [scheme] => http + # [host] => hostname + # [user] => username + # [pass] => password + # [path] => /path + # [query] => arg=value + # [fragment] => anchor + # ) + $reqObjectURL = parse_url($upnpRequest['objectid']); + switch ($reqObjectURL['scheme']) { + case 'amp': + switch ($reqObjectURL['host']) { + case 'music': + if ($upnpRequest['browseflag'] == 'BrowseMetadata') { + $items = Upnp_Api::_musicMetadata($reqObjectURL['path'], $reqObjectURL['query']); + } else { + list($totMatches, $items) = Upnp_Api::_musicChilds($reqObjectURL['path'], $reqObjectURL['query'], $upnpRequest['startingindex'], $upnpRequest['requestedcount']); + } + break; + case 'video': + if ($upnpRequest['browseflag'] == 'BrowseMetadata') { + $items = Upnp_Api::_videoMetadata($reqObjectURL['path'], $reqObjectURL['query']); + } else { + list($totMatches, $items) = Upnp_Api::_videoChilds($reqObjectURL['path'], $reqObjectURL['query'], $upnpRequest['startingindex'], $upnpRequest['requestedcount']); + } + break; + } + break; + } + }; + break; + } + + $totMatches = ($totMatches == 0) ? count($items) : $totMatches; + if ($items == null || $totMatches == 0) { + $domDIDL = Upnp_Api::createDIDL(''); + $numRet = 0; + } else { + $domDIDL = Upnp_Api::createDIDL($items); + $numRet = count($items); + } + + $xmlDIDL = $domDIDL->saveXML(); + $domSOAP = Upnp_Api::createSOAPEnvelope($xmlDIDL, $numRet, $totMatches, $responseType); + $soapXML = $domSOAP->saveXML(); + + echo $soapXML; + //!!debug_event('upnp', 'Response: ' . $soapXML, '5'); +?> diff --git a/sources/upnp/event-reply.php b/sources/upnp/event-reply.php new file mode 100644 index 0000000..4c91f2c --- /dev/null +++ b/sources/upnp/event-reply.php @@ -0,0 +1,19 @@ + diff --git a/sources/upnp/find.php b/sources/upnp/find.php new file mode 100644 index 0000000..0f64cdd --- /dev/null +++ b/sources/upnp/find.php @@ -0,0 +1,140 @@ +initByDiscoveryReponse($response)) { + + $device->saveToCache(); + + try { + $client = $device->getClient('ConnectionManager'); + $protocolInfo = $client->call('GetProtocolInfo'); + + $sink = $protocolInfo['Sink']; + $tmp = explode(',', $sink); + + $protocols = array(); + + foreach ($tmp as $protocol) { + $t = explode(':', $protocol); + if ($t[0] == 'http-get') { + $protocols[] = $t[2]; + } + } + } catch (UPnPException $upnpe) { + $protocols = array(); + } + + $device->protocolInfo = $protocols; + + $cache[$device->getId()] = array( + 'name' => $device->getName(), + 'services' => $device->getServices(), + 'icons' => $device->getIcons(), + 'protocols' => $device->getProtocolInfo() + ); + } + } + + return $cache; + */ + } + + /** + * Performs a standardized UPnP multicast request to 239.255.255.250:1900 + * and listens $timeout seconds for responses + * + * Thanks to artheus (https://github.com/artheus/PHP-UPnP/blob/master/phpupnp.class.php) + * + * @param int $timeout Timeout to wait for responses + * + * @return array Response + */ + private static function discover($timeout = 2) + { + $msg = 'M-SEARCH * HTTP/1.1' . "\r\n"; + $msg .= 'HOST: 239.255.255.250:1900' . "\r\n"; + $msg .= 'MAN: "ssdp:discover"' . "\r\n"; + $msg .= "MX: 3\r\n"; + $msg .= "ST: upnp:rootdevice\r\n"; + $msg .= '' . "\r\n"; + + $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1); + socket_sendto($socket, $msg, strlen($msg), 0, '239.255.255.250', 1900); + + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $timeout, 'usec' => 0)); + + $response = array(); + do { + $buf = null; + @socket_recvfrom($socket, $buf, 1024, MSG_WAITALL, $from, $port); + + if (!is_null($buf)) + $response[] = self::discoveryReponse2Array($buf); + + } while (!is_null($buf)); + //socket_close($socket); + + return $response; + } + + /** + * Transforms discovery response string to key/value array + * + * @param string $res discovery response + * + * @return \stdObj + */ + private static function discoveryReponse2Array($res) + { + $result = array(); + $lines = explode("\n", trim($res)); + + if (trim($lines[0]) == 'HTTP/1.1 200 OK') { + array_shift($lines); + } + + foreach ($lines as $line) { + $tmp = explode(':', trim($line)); + + $key = strtoupper(array_shift($tmp)); + $value = (count($tmp) > 0 ? trim(join(':', $tmp)) : null); + + $result[$key] = $value; + } + + return (Object)$result; + } + +} + + +$devices = UPnPFind::findDevices(); + +?> + +
+
+
diff --git a/sources/upnp/images/icon120.jpg b/sources/upnp/images/icon120.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6a633877b933d5eaaf785c879867918fabc3e04e GIT binary patch literal 9325 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 BznuU8 literal 0 HcmV?d00001 diff --git a/sources/upnp/images/icon32.png b/sources/upnp/images/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..eb98c7ed137a68a6c0416bb1dfb56c291e5f98c5 GIT binary patch literal 4867 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 pR#jw~-BnP(Imh=-&VU002ovPDHLkV1kiYP0s)T literal 0 HcmV?d00001 diff --git a/sources/upnp/images/icon48.jpg b/sources/upnp/images/icon48.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05e31a2a595fd6827f38d7fcf3ba08881bb1b460 GIT binary patch literal 5490 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 literal 0 HcmV?d00001 diff --git a/sources/upnp/images/icon48.png b/sources/upnp/images/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..31423663a0fd2604d4f8581789acaa2b57739995 GIT binary patch literal 6742 zcmV-c8mZ-pP)|D^_ww@lRz|vCuzLs)$;-`! zo*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!& zC1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2hoGcOF60t^# zFqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTXa!E_i;d2ub z1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqKG_|(0G&D0Z z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl z*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY_n(^h55xYX z#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^bXThc7C4-yr zInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qjZ=)yBuQ3=5 z4Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK%>{;v(b^`kb zN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<)0>40zCTJ7v z2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01)S~6}jY?%U? zgEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j*2tcg9i<^O zEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfK zTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761jmyXF)a;mc z^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQqHZJR2&bcD4 z9Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^TY0bZ?)4%0 z1p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK8LKk71XR(_ zRKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS<&CX#T35dw zS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@qL5!WvekBL z-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW%ue3U;av{9 z4wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#o zSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%oZ=0JGnu?n~ z9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8No_-(u{qS+0 z<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-UsyQuty7Ua; zOu?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimkUAw*F_TX^n z@STz9kDQ$NC=!KfXWC z8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgUAAWQEt$#LR zcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6?<+s(e(3(_ z^YOu_)K8!O1p}D#{JO;G(*OVf24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_f zl`9S#00007bV*G`2i^uA6dE_k*;b1H01uu?L_t(&-nE*0j2~BZ$3OSpnfbk6``d@t z&-FS9#7Q8D0||&{LP`ZKP+Fiskt&KD{;4XVwgFP9Qd^azYJ-HTN-+^dBBc=1hNu0!w#Q3%Aef{2#x%Zy_F~8ll6F)X4oz-Zi*_qjM?m6dszu$94i3qR8 zAtETH5D_l)PNme+JxZxp<^Kl1t~arvIS~+%C8N8&9_>ETj5Ymk8(OE(oheM`3@UCQ+TemBae54w8iwpJ z>A+A-K{gZIp3P);20?()2Gswf1B+pI&Jo*~cBj8}zH9F5blT*PeVg*pZ;)DeoUqfx zIFJC80uew!d9VTWbK^9sZ)IxT$7xq@rke3o^3lFRK6g_Xg=qE4qOu|f7V|oeV_NO* z*12AIpPzn`>Qi@9J@g%11eC^5xiLtUL8lNFYe7(GP=c}@=*~j(B&ycHrQn4t?&Qq6 zk5MdD87oKoipA1RVHm!84u}X|Jl$@Og|?Ggz0LUUKcjf)VN|6E)y)v*an9i-24@j( z5$_T4h;x7k5kwT)1O!o*Af18k9QyQ=SO-V0`Fl>T|5G+r6vIPhmC0n5ivG89Aht1` zc895^mgva0SpTC>;!7DAe-kK$w-ymlV(|(%%XqxUiv#bIh&Ycz@ZKUW#)LW2n$eAlB=$V`GcsL<2-TW{kS!^v1z<; zPq|Y5tk&k0J7BG)*=$XoYN*3&AGwv>%s!aB73T!-C`eWnNh07K9#GCEM*G3ai-1oY zaLy&qiN`yeyurJ{X~_*w5E(%oevJLE`vUd#f5>as4c${Jl|Or-NSxOJ?>)_ClM|hs ztbOm&YmP$Jp5$d8FnfIPzC%ewU&jJmGJ0)QF`_psEJ##@wvvU!OwZOcB81x z_ju?Ra|A|F%o^5IBT8A#?&q6?<~+|SO|oMvlnUwAXJ|KOkck_)<-kWdeP)j7nOU(m zei=7hUvyaF0J|Q<29nRzR1pWwm+I~?KD0!PO)>0^upuJ%+#=**sg(ni+Sf5t*9rrB&BUUEQ0aMsea zn$pp4;7j9p=M##sg2ELMOg7-&?MDc;UP2w`h4D(r*oG{`!dtG+)3=^NTJz1_3#8A7 zM&}8GQs8WiwGM!x`X1)$O`LO!wW4t1>BlkY*~d`XQC^A2(Mo|){QD11k;^Rai=DpX zgSS+nZt*?KJFhL$>3dR<=HYz}$eNXl7H#OY<{(>Sr12Q_Rv+i=as;gPNb3w?XEx#U ztX$*-`H*`bILJ^zFVhSWIz7)H+&n@*_Vg{h?V2L}SWrM1D0cm-Nh(xae65mn(pnL> z>W~>`sB?f?+p<{58o+u#sXAxS9>hc|wI8DubS)45p+N~l&kkDq{rF*jjx}a8sJ|cq2 zgnaqCM~R(~I}25MvFB5_uL2D(&UE?pi)|i1)Szbtr64qlUC(#0{iLwnN>rh$U>h0usISF6BB)wIldT=cQ7R4fxXbql}cobN-eJHQ(Jc%l(g>X1-+!jUoy( zfl-7eL9H_l%Yoxvt{Mv%q%#-Y9MmA4yhW_Vi{G+T#CY(7EkWrJq5^n-(bW&L0r!9F z5b5ycvM(@-salK|Aron=OLW2d(Gc&G=kqDe_Pq_VkzPR+FP3G3iwU)1xrnuvojxce zSH+=HC6tLk+=Y=x!Pj;kBbNyVe17?;w`1m;F_WWdKK<@heDgC`@yok6^B@1XncJ?* z(~X5xsCf9v7Nqq`&;qnZiN~1;Q2|EjeE@+{3a!*mn;K5M@-fOg$c*CK({|Z(z3X`M)di*+F(;Q4Mrn*mBBr%MD=pYR)H{M*#7+&>Dz>8tf^+`^xP6fuC-#ieGihLOW!7Ex9{onJ*}>%*-e%^ z3KWk%-N8GTv{0ffIyZ_gZN!u&F_n!3<&CJ~TB7_2v`;gYem$jZL>Pw4lF}GMI`Gs+ zZzX@?tB8-m1WDzHN2P{9Hz2M-7_#kqFHp*bSSM`WP+;eiX9%@st`YOOk8I?cHCbw{ zKF)cZbF_ONr9ACUV)$4iQ=>Uq|T6p5YSeT*734i>u^q5@Kx985V* zWZuX%h0IGG&=k{}6YDloj{=&kXsrCYRAXw7bTgA1u*txtFm35g|&Y zQN20ZsbTu*afV8TCFIiyrfV|kP{qY7=;q5vYYK5)l-5hUNW?=S!{@$ofJ!0ZJh_bF z$NOhVM}}6<@!_|PL!(bG7JAmxi#@$T>U8^#cBe<%YVgCn_YEWTVoxSC?09Snl@HHb zyk0!nTp1P)vOoL#tS(373kCHO2Nda4nvtyM=oNp1o_PlCI;gYIM=1rZJ|B47By)93 zY(1gT>^oE=G@5SD^NDw?!gb?8E72q3k_HzF33I5zYIJ@TeH-wxx38hmaTu+bX*iyK zW`XcLO3j#nRH*TtF3;uP%eqw+vV*qcIT|hq0?PUH9rJ76M_)(KK8?6OTAQUP*q-IC zJKn_izxaEJ{%!OLjly})#_=3$uAIO|B~(;EMP*dBipq|o@~hG5a)M-HiJjy1lSNX2 z=7s4#Lpg)<=kHJDi&dD~%QKY^kgJR^T&>;{1mXD`Tm62Y7fzg!-l6~Iy1k#mU-3?S zW)yMDC?g_-p@DLSZ4d0_OAkH;z~6rI20nD_74*9?;*y@I!XZ}h%A>^Lg{9vaBkX>5 zj_W52Z=PhBF>@l3$!?yL0@RrTvTzS=1YO#P{)>a9Fkdbocj@tO| zQXATgO#ccUH&ACqK`Di`9y`-w>xZxB`}e+w%@Y+q^o~u$y?9WB33^?O)^_@io31Q@ zK*buTA1DgtF*vb@okMrBVIvIvET1AGdnBKy?C0p{`5u|el1is zAtsM_dk*gr5yHSgF2vS4IG;pHyu~|7BH%c01e8nANnoG=lk0ekbB@aJD)jLmayavL zem;5^@7!_)BSU3&-?RgB@*p%$qFf71c#c|AN@1NRuJ@7! zR~PA4T~fd^m?1!^x(YqLkB%PV(b8Y>`b|}ahewn-PnTMsk0z(nY1WTdRd)PZcHi_> z%x|86`Y}wu4m#ixjkU`g7!Dd}rOGhD^d35~=7H+{Y`Job$%#poPN&a1?9dnD`ng=5 ziB*+Ndi9(6#g?yOj{h2&`86i457@0KFTR4$i~Tx_m<$q|$D zj#jh9v6EABY`V$jqkqT5iS4+`Fsgbj5@tcCu!JD-U}#|xCnyr5#S4gAVwfTj8X*iL zOuT?Py$1w-Q29JlmD_mTylUYD7adK?)>2ghY8%kX_Oc_!#0As#k|@ z102+{YuTTDKSv6;Q7R8})w*HE#>Va_7K_^g6Ra2+Uh$-Uu@T&8G`7wz)b2ZZrpZjx zk!d~4NaL3bx1S>4J4Px#16V-eb((gtiuq`Qsm%49O5cd2E3B@hSUWnz$Z+-cd?CLx zFu^PS>_A=a#~xlh&N@1s4y|@e7HUoEtseDGOsD5?E*^*=L6pH5O*Yh&auLN`no6Ze zE|*uCbcP@ZF8h0l%XZ-GuTvnIKw4`T&q?RpRzxNRnFMtt2+X!12zQ2IfH48am{;YC s74_SGZgaLHXFGJkUea05yc);<0a9d;K(*j&wg3PC07*qoM6N<$f{PpudH?_b literal 0 HcmV?d00001 diff --git a/sources/upnp/images/upnp.jpg b/sources/upnp/images/upnp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0428e96db2d3c9485a590d623c455066b04d1608 GIT binary patch literal 20423 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 z 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 literal 0 HcmV?d00001 diff --git a/sources/upnp/index.php b/sources/upnp/index.php new file mode 100644 index 0000000..8b65d0a --- /dev/null +++ b/sources/upnp/index.php @@ -0,0 +1,55 @@ + + + + + + +'; + } +?> +Ampache UPnP + + + + +

+ +
+
+
+ + + +
+ + + diff --git a/sources/upnp/playstatus.php b/sources/upnp/playstatus.php new file mode 100644 index 0000000..a60d843 --- /dev/null +++ b/sources/upnp/playstatus.php @@ -0,0 +1,49 @@ +get_instance(); +echo "UPnP instance = " . $instance['name'] . "\n"; + +$deviceDescr = $instance['url']; +//!!echo "UPnP device = " . $deviceDescr . "\n"; +$player = new UPnPPlayer("background controller", $deviceDescr); + +//!!echo "Current playlist: \n" . print_r($player->GetPlaylistItems(), true); +//!!echo "Current item: \n" . print_r($player->GetCurrentItem(), true); + +// periodically (every second) checking state of renderer, until it is STOPPED +$played = false; +while (($state = $player->GetState()) == "PLAYING") { + $played = true; + echo "."; + sleep(1); +} +echo "STATE = " . $state . "\n"; + +// If the song was played and then finished, start to play next song in list. +// Do not start anything if playback was stopped from beginning +if ($played) { + echo "UPnP play next" . "\n"; + if ($player->Next(false)) + echo "Next song started" . "\n"; + else + echo "Next song FAILED!" . "\n"; +} + +?> diff --git a/sources/upnp/readme.txt b/sources/upnp/readme.txt new file mode 100644 index 0000000..3808451 --- /dev/null +++ b/sources/upnp/readme.txt @@ -0,0 +1,4 @@ +playstatus.php - ... + +find.php - ... + diff --git a/sources/util.php b/sources/util.php index fee9294..171e51b 100644 --- a/sources/util.php +++ b/sources/util.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/video.php b/sources/video.php new file mode 100644 index 0000000..371a132 --- /dev/null +++ b/sources/video.php @@ -0,0 +1,64 @@ +id . '`.', 1); + UI::access_denied(); + exit; + } + + if ($video->remove_from_disk()) { + show_confirmation(T_('Video Deletion'), T_('Video has been deleted.'), AmpConfig::get('web_path')); + } else { + show_confirmation(T_('Video Deletion'), T_('Cannot delete this video.'), AmpConfig::get('web_path')); + } + break; + case 'show_video': + default: + $video = Video::create_from_id($_REQUEST['video_id']); + $video->format(); + require_once AmpConfig::get('prefix') . '/templates/show_video.inc.php'; + break; +} + +UI::show_footer(); diff --git a/sources/waveform.php b/sources/waveform.php index b5fe77e..8544d57 100644 --- a/sources/waveform.php +++ b/sources/waveform.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -35,6 +35,11 @@ if (!AmpConfig::get('waveform')) exit(); ignore_user_abort(true); set_time_limit(300); +// Write/close session data to release session lock for this script. +// This to allow other pages from the same session to be processed +// Do NOT change any session variable after this call +session_write_close(); + $id = $_REQUEST['song_id']; $waveform = Waveform::get($id); if ($waveform) { diff --git a/sources/web_player.php b/sources/web_player.php index 791eca1..06e5f06 100644 --- a/sources/web_player.php +++ b/sources/web_player.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/web_player_embedded.php b/sources/web_player_embedded.php index 63008d9..82845c1 100644 --- a/sources/web_player_embedded.php +++ b/sources/web_player_embedded.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 diff --git a/sources/server/show_edit_playlist.server.php b/sources/webdav/index.php similarity index 59% rename from sources/server/show_edit_playlist.server.php rename to sources/webdav/index.php index 91bebcf..9b050ce 100644 --- a/sources/server/show_edit_playlist.server.php +++ b/sources/webdav/index.php @@ -3,7 +3,7 @@ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) - * Copyright 2001 - 2014 Ampache.org + * 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 @@ -20,27 +20,25 @@ * */ -/* Because this is accessed via Ajax we are going to allow the session_id - * as part of the get request - */ - -define('AJAX_INCLUDE','1'); - +define('NO_SESSION','1'); require_once '../lib/init.php'; -$results = ''; - -debug_event('show_edit_playlist.server.php', 'Called.', '5'); - -switch ($_REQUEST['action']) { - case 'show_edit_object': - ob_start(); - require AmpConfig::get('prefix') . '/templates/show_playlists_dialog.inc.php'; - $results = ob_get_contents(); - ob_end_clean(); - break; - default: - exit(); +if (!AmpConfig::get('webdav_backend')) { + echo "Disabled."; + exit; } -echo $results; +use Sabre\DAV; + +$rootDir = new WebDAV_Catalog(); +$server = new DAV\Server($rootDir); + +$baseUri = ((AmpConfig::get('raw_web_path') !== "/") ? AmpConfig::get('raw_web_path') : "") . '/webdav/index.php'; +$server->setBaseUri($baseUri); +if (AmpConfig::get('use_auth')) { + $authBackend = new WebDAV_Auth(); + $authPlugin = new DAV\Auth\Plugin($authBackend, 'Ampache'); + $server->addPlugin($authPlugin); +} + +$server->exec(); From 48580eacc799ee91dd2453857bdbfed130a47231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9veloppeur=20=C3=A9gar=C3=A9?= Date: Wed, 15 Jul 2015 16:15:10 +0200 Subject: [PATCH 2/3] add avconv vs ffmpeg installer --- scripts/install | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/install b/scripts/install index 5931a79..c78524f 100644 --- a/scripts/install +++ b/scripts/install @@ -5,6 +5,8 @@ domain=$1 path=$2 admin_ampache=$3 +debianversionname=$(lsb_release -a | grep Codename | awk -F' ' '{print $2}') + # Check domain/path availability sudo yunohost app checkurl $domain$path -a ampache if [[ ! $? -eq 0 ]]; then @@ -64,6 +66,9 @@ echo "127.0.0.1 $domain #yunoampache" | sudo tee -a /etc/hosts sleep 1 curl -kL -X POST http://$domain$path/update.php?action=update > /dev/null 2>&1 sleep 5 +[ "$debianversionname" == "wheezy" ] && \ +sudo sed -i 's/;transcode_cmd = "ffmpeg"/transcode_cmd = "ffmpeg"/g' /var/www/ampache/config/ampache.cfg.php && \ +sudo sed -i 's/^transcode_cmd = "avconv"/;transcode_cmd = "avconv"/g' /var/www/ampache/config/ampache.cfg.php sudo yunohost app setting ampache skipped_uris -d sudo yunohost app setting ampache skipped_uris -v "/rest" sudo yunohost app ssowatconf From 2c7c65fb51e59bcda11ca80b783b5e075972610c Mon Sep 17 00:00:00 2001 From: aymhce Date: Thu, 16 Jul 2015 01:47:53 +0200 Subject: [PATCH 3/3] add video transcoding required for web player --- conf/ampache.cfg.php | 64 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/conf/ampache.cfg.php b/conf/ampache.cfg.php index a851dad..bc3450e 100644 --- a/conf/ampache.cfg.php +++ b/conf/ampache.cfg.php @@ -742,7 +742,7 @@ max_bit_rate = 576 ; New dynamically downsampled streams will be denied if they are forced below ; this value. ; DEFAULT: 8 -;min_bit_rate = 48 +min_bit_rate = 48 ;###################################################### ; These are commands used to transcode non-streaming @@ -763,31 +763,31 @@ max_bit_rate = 576 ; transcode_TYPE = {allowed|required|false} ; DEFAULT: false ;;; Audio -;transcode_m4a = allowed +transcode_m4a = required transcode_flac = required -;transcode_mpc = required -;transcode_ogg = required -;transcode_oga = required -;transcode_wav = required -;transcode_wma = required -;transcode_aif = required -;transcode_aiff = required -;transcode_ape = required -;transcode_shn = required +transcode_mpc = required +transcode_ogg = required +transcode_oga = required +transcode_wav = required +transcode_wma = required +transcode_aif = required +transcode_aiff = required +transcode_ape = required +transcode_shn = required transcode_mp3 = allowed ;;; Video -;transcode_avi = allowed -;transcode_mkv = allowed -;transcode_mpg = allowed -;transcode_mpeg = allowed -;transcode_m4v = allowed -;transcode_mp4 = allowed -;transcode_mov = allowed -;transcode_wmv = allowed -;transcode_ogv = allowed -;transcode_divx = allowed -;transcode_m2ts = allowed -;transcode_webm = allowed +transcode_avi = allowed +transcode_mkv = allowed +transcode_mpg = allowed +transcode_mpeg = allowed +transcode_m4v = allowed +transcode_mp4 = allowed +transcode_mov = allowed +transcode_wmv = allowed +transcode_ogv = allowed +transcode_divx = allowed +transcode_m2ts = allowed +transcode_webm = allowed ; Default audio output format ; DEFAULT: none @@ -795,20 +795,31 @@ encode_target = mp3 ; Default video output format ; DEFAULT: none -;encode_video_target = webm +encode_video_target = webm ; Override the default output format on a per-type basis ; encode_target_TYPE = TYPE ; DEFAULT: none -;encode_target_flac = ogg +encode_target_flac = ogg ; Override the default TYPE transcoding behavior on a per-player basis ; transcode_player_PLAYER_TYPE = TYPE ; Valid PLAYER is: webplayer, api ; DEFAULT: none -;transcode_player_webplayer_m4a = required +transcode_player_webplayer_m4a = required ;transcode_player_webplayer_flac = required ;transcode_player_webplayer_mpc = required +transcode_player_webplayer_avi = required +transcode_player_webplayer_mkv = required +transcode_player_webplayer_mpg = required +transcode_player_webplayer_mpeg = required +transcode_player_webplayer_m4v = required +transcode_player_webplayer_mp4 = required +transcode_player_webplayer_mov = required +transcode_player_webplayer_wmv = required +transcode_player_webplayer_ogv = required +transcode_player_webplayer_divx = required +transcode_player_webplayer_m2ts = required ; Override the default output format on a per-player basis ; encode_player_PLAYER_target = TYPE @@ -858,6 +869,7 @@ encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" encode_args_flv = "-b:a %SAMPLE%K -ar 44100 -ac 2 -v 0 -f flv -c:v libx264 -preset superfast -threads 0 pipe:1" encode_args_webm = "-q %QUALITY% -f webm -c:v libvpx -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" +;encode_args_webm = "-q %QUALITY% -f webm -c:v libvpx -maxrate 800k -preset superfast -threads 0 pipe:1" encode_args_ts = "-q %QUALITY% -s %RESOLUTION% -f mpegts -c:v libx264 -c:a libmp3lame -maxrate %MAXBITRATE%k -preset superfast -threads 0 pipe:1" ; Encoding arguments to retrieve an image from a single frame