LCTVAPI.php 14 KB

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