1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 01:39:28 +02:00
ampache/lib/class/plex_api.class.php
Phyks (Lucas Verney) 2bb142eeb8 Fix issue #1260
Fix coding guidelines incoherences. Code should match PSR1/2 now, and
php-cs is set to check it on each commit.

Also fixed the Git hook to take into account only added, modified,
copied and renamed files (preventing errors when trying to check deleted
files).

Closes #1260.
2016-08-01 21:55:14 +02:00

1545 lines
55 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU Affero General Public License, version 3 (AGPLv3)
* Copyright 2001 - 2015 Ampache.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Plex Class
*
* This class wrap Ampache to Plex API library functions. See http://wiki.plexapp.com/index.php/HTTP_API
* These are all static calls.
*
* @SuppressWarnings("unused")
*/
class Plex_Api
{
/**
* constructor
* This really isn't anything to do here, so it's private
*/
private function __construct()
{
}
protected static function is_local()
{
$local = false;
$local_auth = AmpConfig::get('plex_local_auth');
if (!$local_auth) {
$ip = $_SERVER['REMOTE_ADDR'];
$lip = ip2long($ip);
$rangs = array(
array('127.0.0.1', '127.0.0.1'),
array('10.0.0.1', '10.255.255.254'),
array('172.16.0.1', '172.31.255.254'),
array('192.168.0.1', '192.168.255.254'),
);
foreach ($rangs as $rang) {
$ld = ip2long($rang[0]);
$lu = ip2long($rang[1]);
if ($lip <= $lu && $ld <= $lip) {
debug_event('Access Control', 'Local client ip address (' . $ip . '), bypass authentication.', '3');
$local = true;
break;
}
}
}
return $local;
}
public static function auth_user()
{
$isLocal = self::is_local();
$headers = apache_request_headers();
$myplex_token = $headers['X-Plex-Token'];
if (empty($myplex_token)) {
$myplex_token = $_REQUEST['X-Plex-Token'];
}
if (!$isLocal) {
$match_users = AmpConfig::get('plex_match_email');
$myplex_username = $headers['X-Plex-Username'];
if (empty($myplex_token)) {
// Never fail OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
self::setPlexHeader($headers);
exit();
} else {
debug_event('Access Control', 'Authentication token is missing.', '3');
self::createError(401);
}
}
$createSession = false;
Session::gc();
$username = "";
$email = trim(Session::read((string) $myplex_token));
if (empty($email)) {
$createSession = true;
$xml = self::get_server_authtokens();
$validToken = false;
foreach ($xml->access_token as $tk) {
if ((string) $tk['token'] == $myplex_token) {
$username = (string) $tk['username'];
// We should apply filter and access restriction to shared sections only, but that's not easily possible with current Ampache architecture
$validToken = true;
break;
}
}
if (!$validToken) {
debug_event('Access Control', 'Auth-Token ' . $myplex_token . ' invalid for this server.', '3');
self::createError(401);
}
}
// Need to get a match between Plex and Ampache users
if ($match_users) {
if (!AmpConfig::get('access_control')) {
debug_event('Access Control', 'Error Attempted to use Plex with Access Control turned off and plex/ampache link enabled.', '3');
self::createError(401);
}
if (empty($email)) {
$xml = self::get_users_account();
if ((string) $xml->username == $username) {
$email = (string) $xml->email;
} else {
$xml = self::get_server_friends();
foreach ($xml->User as $xuser) {
if ((string) $xuser['username'] == $username) {
$email = (string) $xuser['email'];
}
}
}
}
if (!empty($email)) {
$user = User::get_from_email($email);
}
if (!isset($user) || !$user->id) {
debug_event('Access Denied', 'Unable to get an Ampache user match for email ' . $email, '3');
self::createError(401);
} else {
$username = $user->username;
if (!Access::check_network('init-api', $username, 5)) {
debug_event('Access Denied', 'Unauthorized access attempt to Plex [' . $_SERVER['REMOTE_ADDR'] . ']', '3');
self::createError(401);
} else {
$GLOBALS['user'] = $user;
$GLOBALS['user']->load_playlist();
}
}
} else {
$email = $username;
$username = null;
$GLOBALS['user'] = new User();
$GLOBALS['user']->load_playlist();
}
if ($createSession) {
// Create an Ampache session from Plex authtoken
Session::create(array(
'type' => 'api',
'sid' => $myplex_token,
'username' => $username,
'value' => $email
));
}
} else {
AmpConfig::set('cookie_path', '/', true);
$sid = $_COOKIE[AmpConfig::get('session_name')];
if (!$sid) {
$sid = $myplex_token;
if ($sid) {
session_id($sid);
Session::create_cookie();
}
}
if (!empty($sid) && Session::exists('api', $sid)) {
Session::check();
$GLOBALS['user'] = User::get_from_username($_SESSION['userdata']['username']);
} else {
$GLOBALS['user'] = new User();
$data = array(
'type' => 'api',
'sid' => $sid,
);
Session::create($data);
Session::check();
}
$GLOBALS['user']->load_playlist();
}
}
protected static function check_access($level)
{
if (self::is_local()) {
// Promote all users as content manager if local
if ($GLOBALS['user']->access < 50) {
$GLOBALS['user']->access = 50;
}
}
if (($GLOBALS['user']->access < $level || AmpConfig::get('demo_mode'))) {
debug_event('plex', 'User ' . $GLOBALS['user']->username . ' is unauthorized to complete the action.', '3');
self::createError(401);
exit;
}
}
public static function setHeader($f)
{
header("HTTP/1.1 200 OK", true, 200);
header("Connection: close", true);
header_remove("x-powered-by");
if (strtolower($f) == "xml") {
header("Cache-Control: no-cache", true);
header("Content-type: text/xml; charset=" . AmpConfig::get('site_charset'), true);
} elseif (substr(strtolower($f), 0, 6) == "image/") {
header("Cache-Control: public, max-age=604800", true);
header("Content-type: " . $f, true);
} else {
header("Content-type: " . $f, true);
}
}
public static function setPlexHeader($reqheaders)
{
header("X-Plex-Protocol: 1.0");
header('Access-Control-Allow-Origin: *');
$acm = $reqheaders['Access-Control-Request-Method'];
if ($acm) {
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT, HEAD');
}
$ach = $reqheaders['Access-Control-Request-Headers'];
if ($ach) {
//$filter = explode(',', $ach);
$filter = null;
$headers = self::getPlexHeaders(true, $filter);
$headerkeys = array();
foreach ($headers as $key => $value) {
$headerkeys[] = $key;
}
header('Access-Control-Allow-Headers: ' . implode(',', $headerkeys));
}
if ($acm || $ach) {
header('Access-Control-Max-Age: 1209600');
} else {
header('Access-Control-Expose-Headers: Location');
}
}
public static function apiOutputXml($xml)
{
// Format xml output
$dom = new DOMDocument();
$dom->loadXML($xml);
$dom->formatOutput = true;
self::apiOutput($dom->saveXML());
}
public static function apiOutput($string)
{
if ($_SERVER['REQUEST_METHOD'] != 'OPTIONS') {
ob_clean();
ob_start('ob_gzhandler');
echo $string;
ob_end_flush();
$reqheaders = getallheaders();
if ($reqheaders['Accept-Encoding']) {
header("X-Plex-Content-Compressed-Length: " . ob_get_length(), true);
header("X-Plex-Content-Original-Length: " . strlen($string), true);
}
} else {
header("Content-type: text/plain", true);
//header("Content-length: 0", true);
}
}
public static function createError($code)
{
$error = "";
switch ($code) {
case 404:
$error = "Not Found";
break;
case 401:
$error = "Unauthorized";
break;
}
header("Content-type: text/html", true);
header("HTTP/1.0 " . $code . " " . $error, true, $code);
$html = "<html><head><title>" . $error . "</title></head><body><h1>" . $code . " " . $error . "</h1></body></html>";
self::apiOutput($html);
exit();
}
public static function validateMyPlex($myplex_username, $myplex_password)
{
$options = array(
CURLOPT_USERPWD => $myplex_username . ':' . $myplex_password,
//CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_POST => true,
);
$headers = array(
'Content-Length: 0'
);
$action = 'users/sign_in.xml';
$res = self::myPlexRequest($action, $options, $headers);
;
return $res['xml']['authenticationToken'];
}
public static function getPublicIp()
{
$action = 'pms/:/ip';
$res = self::myPlexRequest($action);
return trim($res['raw']);
}
public static function registerMyPlex($authtoken)
{
$headers = array(
'Content-Type: text/xml'
);
$action = 'servers.xml?auth_token=' . $authtoken;
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setServerInfo($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
$curlopts = array(
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $r->asXML()
);
return self::myPlexRequest($action, $curlopts, $headers, true);
}
public static function publishDeviceConnection($authtoken)
{
$headers = array();
$action = 'devices/' . Plex_XML_Data::getMachineIdentifier() . '?Connection[][uri]=' . Plex_XML_Data::getServerUri() . '&X-Plex-Token=' . $authtoken;
$curlopts = array(
CURLOPT_CUSTOMREQUEST => "PUT"
);
return self::myPlexRequest($action, $curlopts, $headers);
}
public static function unregisterMyPlex($authtoken)
{
$headers = array(
'Content-Type: text/xml'
);
$action = 'servers/' . Plex_XML_Data::getMachineIdentifier() . '.xml?auth_token=' . $authtoken;
$curlopts = array(
CURLOPT_CUSTOMREQUEST => "DELETE"
);
return self::myPlexRequest($action, $curlopts, $headers);
}
protected static function get_server_authtokens()
{
$action = 'servers/' . Plex_XML_Data::getMachineIdentifier() . '/access_tokens.xml?auth_token=' . Plex_XML_Data::getMyPlexAuthToken();
$res = self::myPlexRequest($action);
return $res['xml'];
}
protected static function get_server_friends()
{
$action = 'pms/friends/all?auth_token=' . Plex_XML_Data::getMyPlexAuthToken();
$res = self::myPlexRequest($action);
return $res['xml'];
}
protected static function getPlexHeaders($private = false, $filters = null)
{
$headers = array(
'X-Plex-Client-Identifier' => Plex_XML_Data::getClientIdentifier(),
'X-Plex-Product' => 'Plex Media Server',
'X-Plex-Version' => Plex_XML_Data::getPlexVersion(),
'X-Plex-Platform' => Plex_XML_Data::getPlexPlatform(),
'X-Plex-Platform-Version' => Plex_XML_Data::getPlexPlatformVersion(),
'X-Plex-Client-Platform' => Plex_XML_Data::getPlexPlatform(),
'X-Plex-Protocol' => 1.0,
'X-Plex-Device' => 'Ampache',
'X-Plex-Device-Name' => 'Ampache',
'X-Plex-Provides' => 'server'
);
if ($private) {
if (Plex_XML_Data::getMyPlexUsername()) {
$headers['X-Plex-Username'] = Plex_XML_Data::getMyPlexUsername();
}
if (Plex_XML_Data::getMyPlexUsername()) {
$headers['X-Plex-Token'] = Plex_XML_Data::getMyPlexAuthToken();
}
}
if ($filters) {
$fheaders = array();
foreach ($headers as $key => $value) {
if (array_search(strtolower($key), $filters)) {
$fheaders[$key] = $value;
}
}
$headers = $fheaders;
}
return $headers;
}
public static $request_headers = array();
public static function request_output_header($ch, $header)
{
self::$request_headers[] = $header;
return strlen($header);
}
public static function replay_header($ch, $header)
{
$rheader = trim($header);
$rhpart = explode(':', $rheader);
if (!empty($rheader) && count($rhpart) > 1) {
if ($rhpart[0] != "Transfer-Encoding") {
header($rheader);
}
}
return strlen($header);
}
public static function replay_body($ch, $data)
{
if (connection_status() != 0) {
curl_close($ch);
debug_event('plex', 'Stream cancelled.', 5);
exit;
}
echo $data;
ob_flush();
return strlen($data);
}
protected static function myPlexRequest($action, $curlopts = array(), $headers = array(), $proxy = false)
{
$server = Plex_XML_Data::getServerUri();
$allheaders = array();
if (!$proxy) {
$allheadersarr = self::getPlexHeaders();
foreach ($allheadersarr as $key => $value) {
$allheaders[] = $key . ': ' . $value;
}
$allheaders += array(
'Origin: ' . $server,
'Referer: ' . $server . '/web/index.html',
);
if (!$curlopts[CURLOPT_POST]) {
$allheaders[] = 'Content-length: 0';
}
}
$allheaders = array_merge($allheaders, $headers);
$url = 'https://my.plexapp.com/' . $action;
debug_event('plex', 'Calling ' . $url, '5');
$options = array(
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADERFUNCTION => array('Plex_Api', 'request_output_header'),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => $allheaders,
);
$options += $curlopts;
$ch = curl_init($url);
curl_setopt_array($ch, $options);
$r = curl_exec($ch);
$res = array();
$res['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$res['headers'] = self::$request_headers;
$res['raw'] = $r;
try {
$res['xml'] = simplexml_load_string($r);
} catch (Exception $e) {
// If exception, wrong data returned (Plex API changes?)
}
return $res;
}
public static function root()
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setRootContent($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library($params)
{
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setLibraryContent($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system($params)
{
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setSystemContent($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function clients($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function channels($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function photos($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function photo($params)
{
if (count($params) == 2) {
if ($params[0] == ':' && $params[1] == 'transcode') {
$width = $_REQUEST['width'];
$height = $_REQUEST['height'];
$url = $_REQUEST['url'];
// Replace 32400 request port with the real listening port
// *** To `Plex Inc`: ***
// Please allow listening port server configuration for your Plex server
// and fix your clients to not request resources so hard-coded on 127.0.0.1:32400.
// May be ok on Apple & UPnP world but that's really ugly for a server...
// Yes, it's a little hack but it works.
$localrs = "http://127.0.0.1:32400/";
$options = Core::requests_options();
if (strpos($url, $localrs) !== false) {
$options = array(); // In case proxy is set, no proxy for local addresses
$url = "http://127.0.0.1:" . Plex_XML_Data::getServerPort() . "/" . substr($url, strlen($localrs));
}
if ($width && $height && $url) {
$request = Requests::get($url, array(), $options);
if ($request->status_code == 200) {
ob_clean();
$mime = $request->headers['content-type'];
self::setHeader($mime);
$art = new Art(0);
$art->raw = $request->body;
$thumb = $art->generate_thumb($art->raw, array('width' => $width, 'height' => $height), $mime);
echo $thumb['thumb'];
exit();
}
}
}
}
}
public static function music($params)
{
if (count($params) > 2) {
if ($params[0] == ':' && $params[1] == 'transcode') {
if (count($params) == 3) {
$format = $_REQUEST['format'] ?: pathinfo($params[2], PATHINFO_EXTENSION);
$url = $_REQUEST['url'];
$br = $_REQUEST['audioBitrate'];
if (preg_match("/\/parts\/([0-9]+)\//", $url, $matches)) {
$song_id = Plex_XML_Data::getAmpacheId($matches[1]);
}
} elseif (count($params) == 4 && $params[2] == 'universal') {
$format = pathinfo($params[3], PATHINFO_EXTENSION);
$path = $_REQUEST['path'];
// Should be the maximal allowed bitrate, not necessary the bitrate used but Ampache doesn't support this kind of option yet
$br = $_REQUEST['maxAudioBitrate'];
if (preg_match("/\/metadata\/([0-9]+)/", $path, $matches)) {
$song_id = Plex_XML_Data::getAmpacheId($matches[1]);
}
}
if (!empty($format) && !empty($song_id)) {
$urlparams = '&transcode_to=' . $format;
if (!empty($br)) {
$urlparams .= '&bitrate=' . $br;
}
$url = Song::play_url($song_id, $urlparams, 'api', true);
self::stream_url($url);
}
}
}
}
public static function video($params)
{
if (count($params) > 1 && $params[0] == ':' && $params[1] == 'transcode') {
array_shift($params);
array_shift($params);
self::video_transcode($params);
} else {
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
}
private static function video_transcode($params)
{
$n = count($params);
if ($n == 2) {
$transcode_to = $params[0];
$action = $params[1];
$id = '';
$path = $_GET['path'];
$protocol = $_GET['protocol'];
$offset = $_GET['offset'];
// Transcode arguments.
$videoQuality = $_GET['videoQuality'];
$videoResolution = $_GET['videoResolution'];
$maxVideoBitrate = $_GET['maxVideoBitrate'];
$subtitleSize = $_GET['subtitleSize'];
$audioBoost = $_GET['audioBoost'];
$additional_params = '&vsettings=';
if ($videoResolution) {
$additional_params .= 'resolution-' . $videoResolution . '-';
}
if ($maxVideoBitrate) {
$additional_params .= 'maxbitrate-' . $maxVideoBitrate . '-';
}
if ($videoQuality) {
$additional_params .= 'quality-' . $videoQuality . '-';
}
if ($offset) {
$additional_params .= '&frame=' . $offset;
}
// Several Media and Part per Video is not supported
//$mediaIndex = $_GET['mediaIndex'];
//$partIndex = $_GET['partIndex'];
// What's that?
//$fastSeek = $_GET['fastSeek'];
//$directPlay = $_GET['directPlay'];
//$directStream = $_GET['directStream'];
$uriroot = '/library/metadata/';
$upos = strrpos($path, $uriroot);
if ($upos !== false) {
$id = substr($path, $upos + strlen($uriroot));
}
$session = $_GET['session'];
if ($action == "stop") {
// We should kill associated transcode session here
} elseif (strpos($action, "start") === 0) {
if (empty($protocol) || $protocol == "hls") {
header('Content-Type: application/vnd.apple.mpegurl');
$videoResolution = $_GET['videoResolution'];
$maxVideoBitrate = $_GET['maxVideoBitrate'];
if (!$maxVideoBitrate) {
$maxVideoBitrate = 8175;
}
echo "#EXTM3U\n";
echo "#EXT-X-STREAM-INF:PROGRAM-ID=1";
if ($maxVideoBitrate) {
echo ",BANDWIDTH=" . ($maxVideoBitrate * 1000);
}
if ($videoResolution) {
echo ",RESOLUTION=" . $videoResolution;
}
echo "\n";
echo "hls.m3u8?" . substr($_SERVER['QUERY_STRING'], strpos($_SERVER['QUERY_STRING'], '&') + 1);
} elseif ($protocol == "http") {
$url = null;
if ($transcode_to == 'universal') {
$additional_params .= '&transcode_to=webm';
if (AmpConfig::get('encode_args_webm')) {
debug_event('plex', 'Universal transcoder requested but `webm` transcode settings not configured. This will probably failed.', 3);
}
}
if ($id) {
if (Plex_XML_Data::isSong($id)) {
$url = Song::play_url(Plex_XML_Data::getAmpacheId($id), $additional_params, 'api');
} elseif (Plex_XML_Data::isVideo($id)) {
$url = Video::play_url(Plex_XML_Data::getAmpacheId($id), $additional_params, 'api');
}
if ($url) {
self::stream_url($url);
}
}
}
} elseif ($action == "hls.m3u8") {
if ($id) {
$pl = new Stream_Playlist();
$media = null;
if (Plex_XML_Data::isSong($id)) {
$media = array(
'object_type' => 'song',
'object_id' => Plex_XML_Data::getAmpacheId($id),
);
} elseif (Plex_XML_Data::isVideo($id)) {
$media = array(
'object_type' => 'video',
'object_id' => Plex_XML_Data::getAmpacheId($id),
);
}
if ($media != null) {
$pl->add(array($media), $additional_params);
}
$pl->generate_playlist('hls');
}
}
}
}
public static function applications($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_sections($params)
{
$r = Plex_XML_Data::createLibContainer();
$n = count($params);
if ($n == 0) {
Plex_XML_Data::setSections($r, Catalog::get_catalogs());
} else {
$key = $params[0];
$catalog = Catalog::create_from_id($key);
if (!$catalog) {
self::createError(404);
}
if ($n == 1) {
Plex_XML_Data::setSectionContent($r, $catalog);
} elseif ($n == 2) {
$view = $params[1];
$gtypes = $catalog->get_gather_types();
if ($gtypes[0] == 'music') {
$type = 'artist';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
switch ($type) {
case 'artist':
Plex_XML_Data::setSectionAll_Artists($r, $catalog);
break;
case 'album':
Plex_XML_Data::setSectionAll_Albums($r, $catalog);
break;
}
} elseif ($view == "albums") {
Plex_XML_Data::setSectionAlbums($r, $catalog);
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('album', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'song');
}
} elseif ($gtypes[0] == "tvshow") {
$type = 'tvshow';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
switch ($type) {
case 'tvshow':
Plex_XML_Data::setSectionAll_TVShows($r, $catalog);
break;
case 'season':
Plex_XML_Data::setSectionAll_Seasons($r, $catalog);
break;
case 'episode':
Plex_XML_Data::setSectionAll_Episodes($r, $catalog);
break;
}
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('tvshow_episode', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'video');
}
} elseif ($gtypes[0] == "movie") {
$type = 'tvshow';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
Plex_XML_Data::setSectionAll_Movies($r, $catalog);
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('movie', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'video');
}
}
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_metadata($params)
{
$r = Plex_XML_Data::createLibContainer();
$n = count($params);
$litem = null;
$createMode = ($_SERVER['REQUEST_METHOD'] == 'POST');
$editMode = ($_SERVER['REQUEST_METHOD'] == 'PUT');
if ($n > 0) {
$key = $params[0];
$id = Plex_XML_Data::getAmpacheId($key);
if ($editMode) {
self::check_access(50);
}
if ($n == 1) {
// Should we check that files still exists here?
$checkFiles = $_REQUEST['checkFiles'];
$extra = $_REQUEST['includeExtra'];
if (Plex_XML_Data::isArtist($key)) {
$litem = new Artist($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addArtist($r, $litem);
} elseif (Plex_XML_Data::isAlbum($key)) {
$litem = new Album($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'year' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addAlbum($r, $litem);
} elseif (Plex_XML_Data::isTrack($key)) {
$litem = new Song($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addSong($r, $litem);
} elseif (Plex_XML_Data::isTVShow($key)) {
$litem = new TVShow($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'year' => null,
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addTVShow($r, $litem);
} elseif (Plex_XML_Data::isTVShowSeason($key)) {
$litem = new TVShow_Season($id);
$litem->format();
Plex_XML_Data::addTVShowSeason($r, $litem);
} elseif (Plex_XML_Data::isVideo($key)) {
$litem = Video::create_from_id($id);
if ($editMode) {
$dmap = array(
'title' => null,
'year' => null,
'originallyAvailableAt' => 'release_date',
'originalTitle' => 'original_name',
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
$litem->format();
$subtype = strtolower(get_class($litem));
if ($subtype == 'tvshow_episode') {
Plex_XML_Data::addEpisode($r, $litem, true);
} elseif ($subtype == 'movie') {
Plex_XML_Data::addMovie($r, $litem, true);
}
} elseif (Plex_XML_Data::isPlaylist($key)) {
$litem = new Playlist($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addPlaylist($r, $litem);
}
} else {
$subact = $params[1];
if ($subact == "children") {
if (Plex_XML_Data::isArtist($key)) {
$litem = new Artist($id);
$litem->format();
Plex_XML_Data::setArtistRoot($r, $litem);
} else {
if (Plex_XML_Data::isAlbum($key)) {
$litem = new Album($id);
$litem->format();
Plex_XML_Data::setAlbumRoot($r, $litem);
} else {
if (Plex_XML_Data::isTVShow($key)) {
$litem = new TVShow($id);
$litem->format();
Plex_XML_Data::setTVShowRoot($r, $litem);
} else {
if (Plex_XML_Data::isTVShowSeason($key)) {
$litem = new TVShow_Season($id);
$litem->format();
Plex_XML_Data::setTVShowSeasonRoot($r, $litem);
}
}
}
}
} elseif ($subact == "thumbs" || $subact == "posters" || $subact == "arts" || $subact == 'backgrounds') {
$kind = Plex_XML_Data::getPhotoKind($subact);
if ($createMode) {
// Upload art
$litem = Plex_XML_Data::createLibraryItem($key);
if ($litem != null) {
$uri = Plex_XML_Data::getMetadataUri($key) . '/' . Plex_XML_Data::getPhotoPlexKind($kind) . '/' . $key;
if (is_a($litem, 'video')) {
$type = 'video';
} else {
$type = get_class($litem);
}
$art = new Art($litem->id, $type, $kind);
$raw = file_get_contents("php://input");
$art->insert($raw);
header('Content-Type: text/html');
echo $uri;
exit;
}
}
Plex_XML_Data::addPhotos($r, $key, $kind);
} elseif ($subact == "thumb" || $subact == "poster" || $subact == "art" || $subact == "background") {
if ($n == 3) {
$kind = Plex_XML_Data::getPhotoKind($subact);
// Ignore art id as we can only have 1 thumb
$art = null;
if (Plex_XML_Data::isArtist($key)) {
$art = new Art($id, "artist", $kind);
} else {
if (Plex_XML_Data::isAlbum($key)) {
$art = new Art($id, "album", $kind);
} else {
if (Plex_XML_Data::isTrack($key)) {
$art = new Art($id, "song", $kind);
} else {
if (Plex_XML_Data::isTVShow($key)) {
$art = new Art($id, "tvshow", $kind);
} else {
if (Plex_XML_Data::isTVShowSeason($key)) {
$art = new Art($id, "tvshow_season", $kind);
} else {
if (Plex_XML_Data::isVideo($key)) {
$art = new Art($id, "video", $kind);
}
}
}
}
}
}
if ($art != null) {
$art->get_db();
ob_clean();
if (!isset($size)) {
self::setHeader($art->raw_mime);
echo $art->raw;
} else {
$dim = array();
$dim['width'] = $size;
$dim['height'] = $size;
$thumb = $art->get_thumb($dim);
self::setHeader($art->thumb_mime);
echo $thumb['thumb'];
}
exit();
}
}
}
}
}
if ($litem != null) {
$catalog_ids = $litem->get_catalogs();
if (count($catalog_ids) > 0) {
$catalog = Catalog::create_from_id($catalog_ids[0]);
Plex_XML_Data::addCatalogIdentity($r, $catalog);
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
protected static function stream_url($url)
{
set_time_limit(0);
ob_end_clean();
$headers = apache_request_headers();
$reqheaders = array();
if (isset($headers['Range'])) {
$reqheaders[] = "Range: " . $headers['Range'];
}
// Curl support, we stream transparently to avoid redirect. Redirect can fail on few clients
debug_event('plex-api', 'Stream proxy: ' . $url, 5);
// header("Location: " . $url);
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_HTTPHEADER => $reqheaders,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_WRITEFUNCTION => array('Plex_Api', 'replay_body'),
CURLOPT_HEADERFUNCTION => array('Plex_Api', 'replay_header'),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 0
));
if (curl_exec($ch) === false) {
debug_event('plex-api', 'Curl error: ' . curl_error($ch), 1);
}
curl_close($ch);
}
public static function library_parts($params)
{
$n = count($params);
if ($n > 0) {
$key = $params[0];
if ($n == 2) {
$file = $params[1];
$id = Plex_XML_Data::getAmpacheId($key);
if (Plex_XML_Data::isSong($key)) {
$media = new Song($id);
if ($media->id) {
$url = Song::play_url($id, '', 'api', true);
self::stream_url($url);
} else {
self::createError(404);
}
} elseif (Plex_XML_Data::isVideo($key)) {
$media = new Video($id);
if ($media->id) {
$url = Video::play_url($id, '', 'api', true);
self::stream_url($url);
} else {
self::createError(404);
}
}
} elseif ($n == 1) {
if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
if (isset($_GET['subtitleStreamID'])) {
$lang_code = dechex(hex2bin(substr($_GET['subtitleStreamID'], 0, 2)));
$_SESSION['iframe']['subtitle'] = $lang_code;
}
}
}
}
}
public static function library_recentlyadded($params)
{
$data = array();
$data['album'] = Stats::get_newest('album', 25);
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setCustomView($r, $data);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_ondeck($params)
{
$data = array();
$data['album'] = Stats::get_recent('album', 25);
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setCustomView($r, $data);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_library_sections($params)
{
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setSysSections($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function manage_frameworks_ekspinner_resources($params)
{
// Image file used to 'ping' the server
if ($params[0] == "small_black_7.png") {
header("Content-type: image/png", true);
echo file_get_contents(AmpConfig::get('prefix') . '/plex/resources/small_black_7.png');
exit;
}
}
public static function myplex_account($params)
{
$r = Plex_XML_Data::createMyPlexAccount();
self::apiOutputXml($r->asXML());
}
public static function system_agents($params)
{
$r = Plex_XML_Data::createSysContainer();
$addcontributors = false;
$mediaType = $_REQUEST['mediaType'];
if (count($params) >= 3 && $params[1] == 'config') {
$mediaType = $params[2];
$addcontributors = true;
}
if ($mediaType) {
switch ($mediaType) {
case '1':
Plex_XML_Data::setSysMovieAgents($r);
break;
case '2':
Plex_XML_Data::setSysTVShowAgents($r);
break;
case '13':
Plex_XML_Data::setSysPhotoAgents($r);
break;
case '8':
Plex_XML_Data::setSysMusicAgents($r);
break;
case '9':
Plex_XML_Data::setSysMusicAgents($r, 'Albums');
break;
default:
self::createError(404);
break;
}
} else {
Plex_XML_Data::setSysAgents($r);
}
if ($addcontributors) {
Plex_XML_Data::setAgentsContributors($r, $mediaType, 'com.plexapp.agents.none');
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_agents_contributors($params)
{
$mediaType = $_REQUEST['mediaType'];
$primaryAgent = $_REQUEST['primaryAgent'];
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setAgentsContributors($r, $mediaType, $primaryAgent);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_agents_attribution($params)
{
$identifier = $_REQUEST['identifier'];
self::createError(404);
}
public static function system_scanners($params)
{
if (count($params) > 0) {
$type = $params[0];
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setScanners($r, $type);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
} else {
self::createError(404);
}
}
public static function system_appstore($params)
{
$r = Plex_XML_Data::createAppStore();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function accounts($params)
{
$userid = '';
if (isset($params[0])) {
$userid = $params[0];
}
// Not supported yet
if ($userid > 1) {
self::createError(404);
}
$r = Plex_XML_Data::createAccountContainer();
Plex_XML_Data::setAccounts($r, $userid);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function status($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setStatus($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function status_sessions($params)
{
self::createError(403);
}
public static function prefs($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setPrefs($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function help($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function plexonline($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function plugins($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function services($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setServices($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function services_browse($params)
{
self::check_access(75);
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setBrowseService($r, $params[0]);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function timeline($params)
{
$ratingKey = $_REQUEST['ratingKey'];
$key = $_REQUEST['key'];
$state = $_REQUEST['state'];
$time = $_REQUEST['time'];
$duration = $_REQUEST['duration'];
// Not supported right now (maybe in a future for broadcast?)
header('Content-Type: text/html');
}
public static function rate($params)
{
$id = $_REQUEST['key'];
$identifier = $_REQUEST['identifier'];
$rating = $_REQUEST['rating'];
if ($identifier == 'com.plexapp.plugins.library') {
$robj = new Rating(Plex_XML_Data::getAmpacheId($id), Plex_XML_Data::getLibraryItemType($id));
$robj->set_rating($rating / 2);
}
}
protected static function get_users_account($authtoken='')
{
if (empty($authtoken)) {
$authtoken = Plex_XML_Data::getMyPlexAuthToken();
}
$action = 'users/account?auth_token=' . $authtoken;
$res = self::myPlexRequest($action);
return $res['xml'];
}
public static function users_account($params)
{
$xml = self::get_users_account();
Plex_XML_Data::setMyPlexSubscription($xml);
self::apiOutput($xml->asXML());
}
/**
myPlex server function to grant plexpass access dynamically.
Used for testing purpose only.
*/
public static function users($params)
{
if ($params[0] == 'sign_in.xml') {
$curlopts = array();
$headers = array();
$res = self::myPlexRequest('users/sign_in.xml', $curlopts, $headers, true);
foreach ($res['headers'] as $header) {
header($header);
}
if ($res['status'] == '201') {
Plex_XML_Data::setMyPlexSubscription($res['xml']);
self::apiOutput($res['xml']->asXML());
} else {
self::createError($res['status']);
}
}
}
public static function servers($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setLocalServerInfo($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function playlists($params)
{
$r = Plex_XML_Data::createContainer();
$n = count($params);
$createMode = ($_SERVER['REQUEST_METHOD'] == 'POST');
$editMode = ($_SERVER['REQUEST_METHOD'] == 'PUT');
$delMode = ($_SERVER['REQUEST_METHOD'] == 'DELETE');
if ($createMode || $editMode || $delMode) {
self::check_access(50);
}
if ($n <= 1) {
$plid = 0;
if ($n == 0 && $createMode) {
// Create a new playlist
//$type = $_GET['type'];
$title = $_GET['title'];
//$smart = $_GET['smart'];
//$summary = $_GET['summary'];
$uri = $_GET['uri'];
$plid = Playlist::create($title, 'public');
$playlist = new Playlist($plid);
$key = Plex_XML_Data::getKeyFromFullUri($uri);
$id = Plex_XML_Data::getKeyFromMetadataUri($key);
if ($id) {
$item = Plex_XML_Data::createLibraryItem($id);
$medias = $item->get_medias();
$playlist->add_medias($medias);
}
$plid = Plex_XML_Data::getPlaylistId($plid);
} else {
if ($n == 1 && $params[0] != "all") {
$plid = $params[0];
}
}
if ($plid) {
if (Plex_XML_Data::isPlaylist($plid)) {
$playlist = new Playlist(Plex_XML_Data::getAmpacheId($plid));
if ($playlist->id) {
if ($delMode) {
// Delete playlist
$playlist->delete();
} else {
// Display playlist information
Plex_XML_Data::addPlaylist($r, $playlist);
}
}
}
} else {
// List all playlists
Plex_XML_Data::setPlaylists($r);
}
} elseif ($n >= 2) {
$plid = $params[0];
if (Plex_XML_Data::isPlaylist($plid) && $params[1] == "items") {
$playlist = new Playlist(Plex_XML_Data::getAmpacheId($plid));
if ($playlist->id) {
if ($n == 2) {
if ($editMode) {
// Add a new item to playlist
$uri = $_GET['uri'];
$key = Plex_XML_Data::getKeyFromFullUri($uri);
$id = Plex_XML_Data::getKeyFromMetadataUri($key);
if ($id) {
$item = Plex_XML_Data::createLibraryItem($id);
$medias = $item->get_medias();
$playlist->add_medias($medias);
Plex_XML_Data::addPlaylist($r, $playlist);
}
} else {
Plex_XML_Data::setPlaylistItems($r, $playlist);
}
} elseif ($n == 3) {
$index = intval($params[2]);
if ($delMode) {
$playlist->delete_track_number($index);
$playlist->regenerate_track_numbers();
exit;
}
}
}
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function playqueues($params)
{
$n = count($params);
$r = Plex_XML_Data::createLibContainer();
if ($n == 1) {
$playlistID = $params[0];
Plex_XML_Data::setTmpPlayQueue($r, $playlistID);
} else {
$type = $_GET['type'];
$playlistID = $_GET['playlistID'];
$uri = $_GET['uri'];
$key = $_GET['key'];
$shuffle = $_GET['shuffle'];
Plex_XML_Data::setPlayQueue($r, $type, $playlistID, $uri, $key, $shuffle);
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
private static function get_data_from_map($dmap)
{
$data = array();
foreach ($dmap as $key=>$value) {
if (isset($_GET[$key])) {
if ($value == null) {
$value = $key;
}
$data[$value] = $_GET[$key];
}
}
if (isset($_GET['genre'])) {
$data['edit_tags'] = implode(',', $_GET['genre']);
}
return $data;
}
}