LctvApi.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /**
  3. * LiveEdu.tv API.
  4. *
  5. * @package LctvApi\LctvApi
  6. * @since 0.0.3
  7. * @version 0.0.9
  8. */
  9. /* LctvApi Constants. */
  10. require_once( 'LctvApiConstants.inc' );
  11. /* LCTV API Helpers. */
  12. require_once( 'LctvApiHelpers.inc' );
  13. /* LCTV API Credentials. */
  14. if ( file_exists( CREDENTIALS_INCLUDE ) ) {
  15. require_once( CREDENTIALS_INCLUDE );
  16. } else {
  17. die( NOT_AUTHORIZED_MSG );
  18. }
  19. /**
  20. * LiveEdu.tv API class.
  21. *
  22. * @since 0.0.3
  23. * @class LctvApi
  24. */
  25. class LctvApi {
  26. /**
  27. * App client id.
  28. *
  29. * Used for authorization and token retrieval.
  30. *
  31. * @since 0.0.3
  32. * @access public
  33. * @var string
  34. */
  35. public $client_id = '';
  36. /**
  37. * App client secret.
  38. *
  39. * Used for authorization and token retrieval.
  40. *
  41. * @since 0.0.3
  42. * @access public
  43. * @var string
  44. */
  45. public $client_secret = '';
  46. /**
  47. * Redirect url after liveedu.tv authorization.
  48. *
  49. * Used for authorization code return from API.
  50. *
  51. * @since 0.0.3
  52. * @access public
  53. * @var string URL.
  54. */
  55. public $redirect_url = '';
  56. /**
  57. * Token.
  58. *
  59. * Used to make API calls.
  60. *
  61. * @since 0.0.3
  62. * @access public
  63. * @var stdClass|boolean
  64. */
  65. public $token = false;
  66. /**
  67. * Scope.
  68. *
  69. * Used to define authorization scope.
  70. *
  71. * @since 0.0.3
  72. * @access public
  73. * @var string
  74. */
  75. public $scope = '';
  76. /**
  77. * User name/slug.
  78. *
  79. * Used to identify the user token for API calls.
  80. *
  81. * @since 0.0.3
  82. * @access public
  83. * @var string
  84. */
  85. public $user = '';
  86. /**
  87. * Data store.
  88. *
  89. * Used to store, recall and delete data.
  90. *
  91. * @since 0.0.3
  92. * @access private
  93. * @var string|data store object
  94. */
  95. private $data_store = '';
  96. /**
  97. * Constructor.
  98. *
  99. * Handle auth requests and token checks on instantiation.
  100. *
  101. * @since 0.0.3
  102. *
  103. * @param string $user - LCTV user to authenticate with (default: LCTV_MASTER_USER).
  104. *
  105. * @throws Exception - If curl not accessible.
  106. * @throws Exception - If missing credentials (via LctvApiHelpers::ValidateConstants()).
  107. * @throws Exception - If missing credentials (via LctvApiDataStoreFlatFiles->__construct()).
  108. * @throws Exception - If MySQL unsupported (via LctvApiDataStoreMySQL->__construct()).
  109. */
  110. function __construct( $auth_user = '' ) {
  111. /** Throw execption if curl is unavailable. */
  112. if ( ! function_exists( 'curl_version' ) ) {
  113. throw new Exception(CURL_NOT_FOUND_MSG, 1);
  114. }
  115. /** Throw execption if constants pertaining to this class are undefined . */
  116. LctvApiHelpers::ValidateConstants(array(
  117. 'LCTV_CLIENT_ID',
  118. 'LCTV_CLIENT_SECRET',
  119. 'LCTV_REDIRECT_URL',
  120. 'LCTV_MASTER_USER',
  121. 'LCTVAPI_STORAGE_CLASS',
  122. ) );
  123. $this->auth_user = ( empty( $auth_user ) ) ? LCTV_MASTER_USER : $auth_user;
  124. $this->client_id = LCTV_CLIENT_ID;
  125. $this->client_secret = LCTV_CLIENT_SECRET;
  126. $this->redirect_url = LCTV_REDIRECT_URL;
  127. $this->data_store = LCTVAPI_STORAGE_CLASS;
  128. $this->scope = READ_SCOPE . ' ' . READUSER_SCOPE . ' ' . READCHANNEL_SCOPE ;
  129. /** Instatiate data store. */
  130. $this->data_store = new $this->data_store();
  131. /** Delete user token and all user data. */
  132. if ( isset( $_GET['delete'] ) && ! empty( $this->auth_user ) ) {
  133. $token_del = $this->data_store->get_data( $this->auth_user, 'token' );
  134. if ( $_GET['delete'] === $token_del->delete_id ) {
  135. $this->data_store->delete_data( $this->auth_user, '*' );
  136. }
  137. }
  138. /** Attempt to get token data. */
  139. $this->token = $this->data_store->get_data( $this->auth_user, 'token' );
  140. /** If the API isn't authorized start a PHP session. */
  141. if ( ! $this->is_authorized() ) {
  142. session_start();
  143. }
  144. /** Received authorization redirect from API. */
  145. if ( isset( $_GET['state'] ) && isset( $_GET['code'] ) && session_status() === PHP_SESSION_ACTIVE ) {
  146. if ( $_GET['state'] === session_id() ) {
  147. $this->token = new stdClass();
  148. $this->token->code = $_GET['code'];
  149. }
  150. }
  151. /** Refresh the token if necessary. */
  152. $this->refresh_token();
  153. }
  154. /**
  155. * Check the token and get/refresh it if necessary.
  156. *
  157. * @since 0.0.3
  158. * @access public
  159. */
  160. public function refresh_token() {
  161. /** Check if token exists or errored token. */
  162. if ( $this->token === false || isset( $this->token->error ) ) {
  163. return;
  164. }
  165. /** Check if token has expired yet. */
  166. if ( isset( $this->token->access_token ) && ( time() - $this->token->created_at ) < $this->token->expires_in ) {
  167. return;
  168. }
  169. /** POST parameters for token refresh request. */
  170. $token_params = array(
  171. 'grant_type' => 'authorization_code',
  172. 'code' => $this->token->code,
  173. 'redirect_uri' => $this->redirect_url,
  174. );
  175. /** Add refresh POST parameters if available. */
  176. if ( isset( $this->token->refresh_token ) ) {
  177. $token_params['grant_type'] = 'refresh_token';
  178. $token_params['refresh_token'] = $this->token->refresh_token;
  179. }
  180. /** Token request headers. */
  181. $token_headers = array(
  182. 'Cache-Control: no-cache',
  183. 'Pragma: no-cache',
  184. 'Authorization: Basic ' . base64_encode( $this->client_id . ':' . $this->client_secret ),
  185. );
  186. /** POST token request to API. */
  187. $token_ret = $this->post_url_contents( LCTV_TOKEN_URL, $token_params, $token_headers );
  188. $token = json_decode( $token_ret );
  189. /** Stop checking on error. */
  190. if ( isset( $token->error ) ) {
  191. return;
  192. }
  193. /** Setup new token and save to data store. */
  194. $token->code = $this->token->code;
  195. $token->created_at = time();
  196. $this->token = $token;
  197. $this->token->delete_id = uniqid();
  198. $user_data = $this->api_request( 'v1/user/' );
  199. if ( ! isset( $user_data->result->detail ) ) { // no error
  200. if ( empty( $this->auth_user ) ) {
  201. $this->auth_user = $user_data->result->slug;
  202. }
  203. $this->data_store->put_data( $user_data->result->slug, 'token', $this->token );
  204. }
  205. }
  206. /**
  207. * Get authorization url.
  208. *
  209. * @since 0.0.3
  210. * @access public
  211. */
  212. public function get_authorization_url() {
  213. /** If PHP session hasn't been started, start it now. */
  214. if ( session_status() !== PHP_SESSION_ACTIVE ) {
  215. if ( session_start() === false ) {
  216. return '';
  217. }
  218. }
  219. return LCTV_AUTH_URL .'
  220. ?scope=' . urlencode( $this->scope ) .
  221. '&state=' . urlencode( session_id() ) .
  222. '&redirect_uri=' . urlencode( $this->redirect_url ) .
  223. '&response_type=code' .
  224. '&client_id=' . urlencode( $this->client_id );
  225. }
  226. /**
  227. * Check if there's a token.
  228. *
  229. * @since 0.0.3
  230. * @access public
  231. */
  232. public function is_authorized() {
  233. return ( $this->token !== false && isset( $this->token->access_token ) );
  234. }
  235. /**
  236. * Make an API request/call.
  237. *
  238. * @since 0.0.3
  239. * @access public
  240. *
  241. * @param string $api_path API endpoint. ex: 'v1/livestreams/'
  242. * @param int $cache_expires_in (optional) Override LCTVAPI_CACHE_EXPIRES_IN constant. Default: false
  243. * @param bool $cache (optional) True to cache result, false to not. Default: true
  244. */
  245. public function api_request( $api_path, $cache_expires_in = false, $cache = true ) {
  246. /** Check if we're authorized. */
  247. if ( ! $this->is_authorized() ) {
  248. $ret = new stdClass();
  249. $ret->result->detail = 'LctvApi not authorized';
  250. return $ret;
  251. }
  252. /** Setup api request type for data store. */
  253. $api_request_type = preg_replace( "/[^a-zA-Z0-9]+/", "", $api_path );
  254. /** Attempt to load API request from cache. */
  255. $api_cache = $this->data_store->get_data( $this->auth_user, $api_request_type );
  256. /** Check for cache expire time override. */
  257. if ( $cache_expires_in !== false ) {
  258. $cache_check = $cache_expires_in;
  259. } else {
  260. $cache_check = LCTVAPI_CACHE_EXPIRES_IN;
  261. }
  262. /** Make API request call if we have no cache or if cache is expired. */
  263. if ( ! isset( $api_cache->created_at ) || ( time() - $api_cache->created_at ) > $cache_check) {
  264. $headers = array(
  265. 'Cache-Control: no-cache',
  266. 'Pragma: no-cache',
  267. 'Authorization: ' . $this->token->token_type . ' ' . $this->token->access_token,
  268. );
  269. $api_ret = $this->get_url_contents( LCTV_API_URL . $api_path, $headers );
  270. $api_cache = new stdClass();
  271. $api_cache->result = json_decode( $api_ret, false );
  272. $api_cache->created_at = time();
  273. if ( $cache ) {
  274. $this->data_store->put_data( $this->auth_user, $api_request_type, $api_cache );
  275. }
  276. }
  277. return $api_cache;
  278. }
  279. /**
  280. * Curl GET request.
  281. *
  282. * @since 0.0.3
  283. * @access private
  284. */
  285. private function get_url_contents( $url, $custom_header = [] ) {
  286. $crl = curl_init();
  287. curl_setopt( $crl, CURLOPT_HTTPHEADER, $custom_header );
  288. curl_setopt( $crl, CURLOPT_URL, $url );
  289. curl_setopt( $crl, CURLOPT_RETURNTRANSFER, 1 );
  290. curl_setopt( $crl, CURLOPT_CONNECTTIMEOUT, 5 );
  291. $ret = curl_exec( $crl );
  292. curl_close( $crl );
  293. return $ret;
  294. }
  295. /**
  296. * Curl POST request.
  297. *
  298. * @since 0.0.3
  299. * @access private
  300. */
  301. private function post_url_contents( $url, $fields, $custom_header = [] ) {
  302. $fields_string = '';
  303. foreach( $fields as $key => $value ) {
  304. $fields_string .= $key . '=' . urlencode( $value ) . '&';
  305. }
  306. rtrim( $fields_string, '&' );
  307. $crl = curl_init();
  308. curl_setopt( $crl, CURLOPT_HTTPHEADER, $custom_header );
  309. curl_setopt( $crl, CURLOPT_URL, $url );
  310. if ( ! empty( $fields_string ) ) {
  311. curl_setopt( $crl, CURLOPT_POST, count( $fields ) );
  312. curl_setopt( $crl, CURLOPT_POSTFIELDS, $fields_string );
  313. }
  314. curl_setopt( $crl, CURLOPT_RETURNTRANSFER, 1 );
  315. curl_setopt( $crl, CURLOPT_CONNECTTIMEOUT, 5 );
  316. $ret = curl_exec( $crl );
  317. curl_close( $crl );
  318. return $ret;
  319. }
  320. }
  321. /**
  322. * Flat file data store class.
  323. *
  324. * @since 0.0.3
  325. * @class LctvApiDataStoreFlatFiles
  326. */
  327. class LctvApiDataStoreFlatFiles {
  328. /**
  329. * Constructor.
  330. *
  331. * @since 0.0.6
  332. *
  333. * @throws Exception - If missing credentials (via LctvApiHelpers::ValidateConstants()).
  334. */
  335. public function __construct() {
  336. /** Throw execption if constants pertaining to this class are undefined . */
  337. LctvApiHelpers::ValidateConstants( array( 'LCTVAPI_STORAGE_PATH', ) ) ;
  338. }
  339. /**
  340. * Create storage directory if it does not already exist.
  341. *
  342. * @since 0.0.8
  343. * @access private
  344. *
  345. */
  346. private function createStorageDirectory()
  347. {
  348. if ( file_exists( LCTVAPI_STORAGE_PATH ) ) return ;
  349. $dirs = explode( '/', LCTVAPI_STORAGE_PATH );
  350. $path = '';
  351. foreach( $dirs as $dir )
  352. if ( !is_dir( $path .= "/$dir" ) )
  353. mkdir( $path, 0700 );
  354. }
  355. /**
  356. * Get data.
  357. *
  358. * Data is always an object, and should be returned as an object.
  359. *
  360. * @since 0.0.3
  361. * @access public
  362. *
  363. * @param string $user User name/slug.
  364. * @param string $type Data type.
  365. *
  366. * @return bool|stdClass False on failure, object on success.
  367. */
  368. public function get_data( $user, $type ) {
  369. self::createStorageDirectory();
  370. if ( empty( $user ) || empty( $type ) || ! file_exists( LCTVAPI_STORAGE_PATH . $user . '.' . $type ) ) {
  371. return false;
  372. }
  373. return json_decode( file_get_contents( LCTVAPI_STORAGE_PATH . $user . '.' . $type ), false );
  374. }
  375. /**
  376. * Put/store data.
  377. *
  378. * Data is always an object. False should be returned on failure,
  379. * any other value will be considered a success.
  380. *
  381. * @since 0.0.3
  382. * @access public
  383. *
  384. * @param string $user User name/slug.
  385. * @param string $type Data type.
  386. * @param stdClass $data Data object.
  387. *
  388. * @return bool|int False on failure, number of bytes written on success.
  389. */
  390. public function put_data( $user, $type, $data ) {
  391. self::createStorageDirectory();
  392. if ( empty( $user ) || empty( $type ) || empty( $data ) ) {
  393. return false;
  394. }
  395. return file_put_contents( LCTVAPI_STORAGE_PATH . $user . '.' . $type, json_encode( $data ) );
  396. }
  397. /**
  398. * Delete data.
  399. *
  400. * Data type ($type) may include a '*' wildcard to indicate all data.
  401. *
  402. * @since 0.0.3
  403. * @access public
  404. *
  405. * @param string $user User name/slug.
  406. * @param string $type Data type.
  407. *
  408. * @return bool False on failure, true on success.
  409. */
  410. public function delete_data( $user, $type ) {
  411. self::createStorageDirectory();
  412. if ( empty( $user ) || empty( $type ) ) {
  413. return false;
  414. }
  415. if ( strpos( $type, '*' ) !== false ) {
  416. array_map( 'unlink', glob( LCTVAPI_STORAGE_PATH . $user . '.' . $type ) );
  417. return true;
  418. } else {
  419. return unlink( LCTVAPI_STORAGE_PATH . $user . '.' . $type );
  420. }
  421. }
  422. }
  423. /**
  424. * MySQL Data Store Class.
  425. *
  426. * @since 0.0.6
  427. * @class LctvApiDataStoreMySQL
  428. */
  429. class LctvApiDataStoreMySQL {
  430. /**
  431. * Database object.
  432. *
  433. * @since 0.0.6
  434. * @access private
  435. * @var bool|database object
  436. */
  437. private $db = false;
  438. /**
  439. * Constructor.
  440. *
  441. * Handle database connection.
  442. *
  443. * @since 0.0.6
  444. *
  445. * @throws Exception - If MySQL unsupported.
  446. */
  447. public function __construct() {
  448. /** Bail if no mysqli support. */
  449. if ( ! function_exists( 'mysqli_connect' ) ) {
  450. throw new Exception(MYSQL_UNSUPPORTED_MSG, 1);
  451. }
  452. /** Throw execption if constants pertaining to this class are undefined . */
  453. LctvApiHelpers::ValidateConstants(array(
  454. 'LCTVAPI_DB_HOST',
  455. 'LCTVAPI_DB_USER',
  456. 'LCTVAPI_DB_PASSWORD',
  457. 'LCTVAPI_DB_NAME',
  458. ) );
  459. /** Connect to database. */
  460. $this->db = new mysqli( LCTVAPI_DB_HOST, LCTVAPI_DB_USER, LCTVAPI_DB_PASSWORD, LCTVAPI_DB_NAME );
  461. if ( $this->db->connect_errno ) {
  462. $this->db = false;
  463. return;
  464. }
  465. /** Create cache table if it doesn't exist. */
  466. $this->db->query( "CREATE TABLE IF NOT EXISTS `lctvapi_cache` ( `id` BIGINT(20) NOT NULL auto_increment, `user` VARCHAR(255), `type` VARCHAR(255), `data` LONGTEXT, PRIMARY KEY (`id`), INDEX (`user`), INDEX (`type`) )" );
  467. }
  468. /**
  469. * Get data.
  470. *
  471. * Data is always an object, and should be returned as an object.
  472. *
  473. * @since 0.0.6
  474. * @access public
  475. *
  476. * @param string $user User name/slug.
  477. * @param string $type Data type.
  478. *
  479. * @return bool|stdClass False on failure, object on success.
  480. */
  481. public function get_data( $user, $type ) {
  482. if ( empty( $user ) || empty( $type ) || ! $this->db ) {
  483. return false;
  484. }
  485. $user = $this->db->real_escape_string( $user );
  486. $type = $this->db->real_escape_string( $type );
  487. $result = $this->db->query( "SELECT `data` FROM `lctvapi_cache` WHERE `user` = '$user' AND `type` = '$type'" );
  488. if ( $result->num_rows == 0 ) {
  489. return false;
  490. }
  491. $data = $result->fetch_object();
  492. $result->free();
  493. return json_decode( $data->data, false );
  494. }
  495. /**
  496. * Put/store data.
  497. *
  498. * Data is always an object. False should be returned on failure,
  499. * any other value will be considered a success.
  500. *
  501. * @since 0.0.6
  502. * @access public
  503. *
  504. * @param string $user User name/slug.
  505. * @param string $type Data type.
  506. * @param stdClass $data Data object.
  507. *
  508. * @return bool|int False on failure, id of entry stored on success.
  509. */
  510. public function put_data( $user, $type, $data ) {
  511. if ( empty( $user ) || empty( $type ) || empty( $data ) || ! $this->db ) {
  512. return false;
  513. }
  514. $user = $this->db->real_escape_string( $user );
  515. $type = $this->db->real_escape_string( $type );
  516. $data = $this->db->real_escape_string( json_encode( $data ) );
  517. $id = $this->db->query( "SELECT `id` FROM `lctvapi_cache` WHERE `user` = '$user' AND `type` = '$type'" );
  518. if ( $id->num_rows == 0 ) {
  519. $result = $this->db->query( "INSERT INTO `lctvapi_cache` (`id`, `user`, `type`, `data`) VALUES (NULL, '$user', '$type', '$data')" );
  520. } else {
  521. $result = $this->db->query( "UPDATE `lctvapi_cache` SET `data` = '$data' WHERE `id` = " . $this->db->real_escape_string( $id->fetch_object()->id ) );
  522. }
  523. $id->free();
  524. if ( $result ) {
  525. return strlen( $data );
  526. }
  527. return false;
  528. }
  529. /**
  530. * Delete data.
  531. *
  532. * Data type ($type) may include a '*' wildcard to indicate all data.
  533. *
  534. * @since 0.0.6
  535. * @access public
  536. *
  537. * @param string $user User name/slug.
  538. * @param string $type Data type.
  539. *
  540. * @return bool False on failure, true on success.
  541. */
  542. public function delete_data( $user, $type ) {
  543. if ( empty( $user ) || empty( $type ) || ! $this->db ) {
  544. return false;
  545. }
  546. $user = $this->db->real_escape_string( $user );
  547. $type = $this->db->real_escape_string( $type );
  548. if ( strpos( $type, '*' ) !== false ) {
  549. $result = $this->db->query( "DELETE FROM `lctvapi_cache` WHERE `user` = '$user'" );
  550. } else {
  551. $result = $this->db->query( "DELETE FROM `lctvapi_cache` WHERE `user` = '$user' AND `type` = '$type'" );
  552. }
  553. if ( $result ) {
  554. return true;
  555. }
  556. return false;
  557. }
  558. }