1
0

BridgeAbstract.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. require_once(__DIR__ . '/BridgeInterface.php');
  3. abstract class BridgeAbstract implements BridgeInterface {
  4. const NAME = 'Unnamed bridge';
  5. const URI = '';
  6. const DESCRIPTION = 'No description provided';
  7. const MAINTAINER = 'No maintainer';
  8. const PARAMETERS = array();
  9. public $useProxy = true;
  10. protected $cache;
  11. protected $items = array();
  12. protected $inputs = array();
  13. protected $queriedContext = '';
  14. protected function returnError($message, $code){
  15. throw new \HttpException($message, $code);
  16. }
  17. protected function returnClientError($message){
  18. $this->returnError($message, 400);
  19. }
  20. protected function returnServerError($message){
  21. $this->returnError($message, 500);
  22. }
  23. /**
  24. * Return items stored in the bridge
  25. * @return mixed
  26. */
  27. public function getItems(){
  28. return $this->items;
  29. }
  30. protected function validateTextValue($value, $pattern = null){
  31. if(!is_null($pattern)){
  32. $filteredValue = filter_var($value
  33. , FILTER_VALIDATE_REGEXP
  34. , array('options' => array(
  35. 'regexp' => '/^' . $pattern . '$/'
  36. ))
  37. );
  38. } else {
  39. $filteredValue = filter_var($value);
  40. }
  41. if($filteredValue === false)
  42. return null;
  43. return $filteredValue;
  44. }
  45. protected function validateNumberValue($value){
  46. $filteredValue = filter_var($value, FILTER_VALIDATE_INT);
  47. if($filteredValue === false && !empty($value))
  48. return null;
  49. return $filteredValue;
  50. }
  51. protected function validateCheckboxValue($value){
  52. $filteredValue = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
  53. if(is_null($filteredValue))
  54. return null;
  55. return $filteredValue;
  56. }
  57. protected function validateListValue($value, $expectedValues){
  58. $filteredValue = filter_var($value);
  59. if($filteredValue === false)
  60. return null;
  61. if(!in_array($filteredValue, $expectedValues)){ // Check sub-values?
  62. foreach($expectedValues as $subName => $subValue){
  63. if(is_array($subValue) && in_array($filteredValue, $subValue))
  64. return $filteredValue;
  65. }
  66. return null;
  67. }
  68. return $filteredValue;
  69. }
  70. protected function validateData(&$data){
  71. if(!is_array($data))
  72. return false;
  73. foreach($data as $name => $value){
  74. $registered = false;
  75. foreach(static::PARAMETERS as $context => $set){
  76. if(array_key_exists($name, $set)){
  77. $registered = true;
  78. if(!isset($set[$name]['type'])){
  79. $set[$name]['type'] = 'text';
  80. }
  81. switch($set[$name]['type']){
  82. case 'number':
  83. $data[$name] = $this->validateNumberValue($value);
  84. break;
  85. case 'checkbox':
  86. $data[$name] = $this->validateCheckboxValue($value);
  87. break;
  88. case 'list':
  89. $data[$name] = $this->validateListValue($value, $set[$name]['values']);
  90. break;
  91. default:
  92. case 'text':
  93. if(isset($set[$name]['pattern'])){
  94. $data[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
  95. } else {
  96. $data[$name] = $this->validateTextValue($value);
  97. }
  98. break;
  99. }
  100. if(is_null($data[$name])){
  101. echo 'Parameter \'' . $name . '\' is invalid!' . PHP_EOL;
  102. return false;
  103. }
  104. }
  105. }
  106. if(!$registered)
  107. return false;
  108. }
  109. return true;
  110. }
  111. protected function setInputs(array $inputs, $queriedContext){
  112. // Import and assign all inputs to their context
  113. foreach($inputs as $name => $value){
  114. foreach(static::PARAMETERS as $context => $set){
  115. if(array_key_exists($name, static::PARAMETERS[$context])){
  116. $this->inputs[$context][$name]['value'] = $value;
  117. }
  118. }
  119. }
  120. // Apply default values to missing data
  121. $contexts = array($queriedContext);
  122. if(array_key_exists('global', static::PARAMETERS)){
  123. $contexts[] = 'global';
  124. }
  125. foreach($contexts as $context){
  126. foreach(static::PARAMETERS[$context] as $name => $properties){
  127. if(isset($this->inputs[$context][$name]['value'])){
  128. continue;
  129. }
  130. $type = isset($properties['type']) ? $properties['type'] : 'text';
  131. switch($type){
  132. case 'checkbox':
  133. if(!isset($properties['defaultValue'])){
  134. $this->inputs[$context][$name]['value'] = false;
  135. } else {
  136. $this->inputs[$context][$name]['value'] = $properties['defaultValue'];
  137. }
  138. break;
  139. case 'list':
  140. if(!isset($properties['defaultValue'])){
  141. $firstItem = reset($properties['values']);
  142. if(is_array($firstItem)){
  143. $firstItem = reset($firstItem);
  144. }
  145. $this->inputs[$context][$name]['value'] = $firstItem;
  146. } else {
  147. $this->inputs[$context][$name]['value'] = $properties['defaultValue'];
  148. }
  149. break;
  150. default:
  151. if(isset($properties['defaultValue'])){
  152. $this->inputs[$context][$name]['value'] = $properties['defaultValue'];
  153. }
  154. break;
  155. }
  156. }
  157. }
  158. // Copy global parameter values to the guessed context
  159. if(array_key_exists('global', static::PARAMETERS)){
  160. foreach(static::PARAMETERS['global'] as $name => $properties){
  161. if(isset($inputs[$name])){
  162. $value = $inputs[$name];
  163. } elseif (isset($properties['value'])){
  164. $value = $properties['value'];
  165. } else {
  166. continue;
  167. }
  168. $this->inputs[$queriedContext][$name]['value'] = $value;
  169. }
  170. }
  171. // Only keep guessed context parameters values
  172. if(isset($this->inputs[$queriedContext])){
  173. $this->inputs = array($queriedContext => $this->inputs[$queriedContext]);
  174. } else {
  175. $this->inputs = array();
  176. }
  177. }
  178. protected function getQueriedContext(array $inputs){
  179. $queriedContexts = array();
  180. foreach(static::PARAMETERS as $context => $set){
  181. $queriedContexts[$context] = null;
  182. foreach($set as $id => $properties){
  183. if(isset($inputs[$id]) && !empty($inputs[$id])){
  184. $queriedContexts[$context] = true;
  185. } elseif(isset($properties['required'])
  186. && $properties['required'] === true){
  187. $queriedContexts[$context] = false;
  188. break;
  189. }
  190. }
  191. }
  192. if(array_key_exists('global', static::PARAMETERS)
  193. && $queriedContexts['global'] === false){
  194. return null;
  195. }
  196. unset($queriedContexts['global']);
  197. switch(array_sum($queriedContexts)){
  198. case 0:
  199. foreach($queriedContexts as $context => $queried){
  200. if (is_null($queried)){
  201. return $context;
  202. }
  203. }
  204. return null;
  205. case 1: return array_search(true, $queriedContexts);
  206. default: return false;
  207. }
  208. }
  209. /**
  210. * Defined datas with parameters depending choose bridge
  211. * Note : you can define a cache with "setCache"
  212. * @param array array with expected bridge paramters
  213. */
  214. public function setDatas(array $inputs){
  215. if(!is_null($this->cache)){
  216. $this->cache->prepare($inputs);
  217. $time = $this->cache->getTime();
  218. if($time !== false && (time() - $this->getCacheDuration() < $time)){
  219. $this->items = $this->cache->loadData();
  220. return;
  221. }
  222. }
  223. if(empty(static::PARAMETERS)){
  224. if(!empty($inputs)){
  225. $this->returnClientError('Invalid parameters value(s)');
  226. }
  227. $this->collectData();
  228. if(!is_null($this->cache)){
  229. $this->cache->saveData($this->getItems());
  230. }
  231. return;
  232. }
  233. if(!$this->validateData($inputs)){
  234. $this->returnClientError('Invalid parameters value(s)');
  235. }
  236. // Guess the paramter context from input data
  237. $this->queriedContext = $this->getQueriedContext($inputs);
  238. if(is_null($this->queriedContext)){
  239. $this->returnClientError('Required parameter(s) missing');
  240. } elseif($this->queriedContext === false){
  241. $this->returnClientError('Mixed context parameters');
  242. }
  243. $this->setInputs($inputs, $this->queriedContext);
  244. $this->collectData();
  245. if(!is_null($this->cache)){
  246. $this->cache->saveData($this->getItems());
  247. }
  248. }
  249. function getInput($input){
  250. if(!isset($this->inputs[$this->queriedContext][$input]['value'])){
  251. return null;
  252. }
  253. return $this->inputs[$this->queriedContext][$input]['value'];
  254. }
  255. public function getName(){
  256. return static::NAME;
  257. }
  258. public function getURI(){
  259. return static::URI;
  260. }
  261. public function getCacheDuration(){
  262. return 3600;
  263. }
  264. public function setCache(\CacheAbstract $cache){
  265. $this->cache = $cache;
  266. }
  267. public function debugMessage($text){
  268. if(!file_exists('DEBUG')) {
  269. return;
  270. }
  271. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
  272. $calling = $backtrace[2];
  273. $message = $calling['file'] . ':'
  274. . $calling['line'] . ' class '
  275. . get_class($this) . '->'
  276. . $calling['function'] . ' - '
  277. . $text;
  278. error_log($message);
  279. }
  280. protected function getContents($url
  281. , $use_include_path = false
  282. , $context = null
  283. , $offset = 0
  284. , $maxlen = null){
  285. $contextOptions = array(
  286. 'http' => array(
  287. 'user_agent' => ini_get('user_agent')
  288. )
  289. );
  290. if(defined('PROXY_URL') && $this->useProxy){
  291. $contextOptions['http']['proxy'] = PROXY_URL;
  292. $contextOptions['http']['request_fulluri'] = true;
  293. if(is_null($context)){
  294. $context = stream_context_create($contextOptions);
  295. } else {
  296. $prevContext=$context;
  297. if(!stream_context_set_option($context, $contextOptions)){
  298. $context = $prevContext;
  299. }
  300. }
  301. }
  302. if(is_null($maxlen)){
  303. $content = @file_get_contents($url, $use_include_path, $context, $offset);
  304. } else {
  305. $content = @file_get_contents($url, $use_include_path, $context, $offset, $maxlen);
  306. }
  307. if($content === false)
  308. $this->debugMessage('Cant\'t download ' . $url);
  309. return $content;
  310. }
  311. protected function getSimpleHTMLDOM($url
  312. , $use_include_path = false
  313. , $context = null
  314. , $offset = 0
  315. , $maxLen = null
  316. , $lowercase = true
  317. , $forceTagsClosed = true
  318. , $target_charset = DEFAULT_TARGET_CHARSET
  319. , $stripRN = true
  320. , $defaultBRText = DEFAULT_BR_TEXT
  321. , $defaultSpanText = DEFAULT_SPAN_TEXT){
  322. $content = $this->getContents($url, $use_include_path, $context, $offset, $maxLen);
  323. return str_get_html($content
  324. , $lowercase
  325. , $forceTagsClosed
  326. , $target_charset
  327. , $stripRN
  328. , $defaultBRText
  329. , $defaultSpanText);
  330. }
  331. /**
  332. * Maintain locally cached versions of pages to avoid multiple downloads.
  333. * @param url url to cache
  334. * @param duration duration of the cache file in seconds (default: 24h/86400s)
  335. * @return content of the file as string
  336. */
  337. public function getSimpleHTMLDOMCached($url
  338. , $duration = 86400
  339. , $use_include_path = false
  340. , $context = null
  341. , $offset = 0
  342. , $maxLen = null
  343. , $lowercase = true
  344. , $forceTagsClosed = true
  345. , $target_charset = DEFAULT_TARGET_CHARSET
  346. , $stripRN = true
  347. , $defaultBRText = DEFAULT_BR_TEXT
  348. , $defaultSpanText = DEFAULT_SPAN_TEXT){
  349. $this->debugMessage('Caching url ' . $url . ', duration ' . $duration);
  350. $filepath = __DIR__ . '/../cache/pages/' . sha1($url) . '.cache';
  351. $this->debugMessage('Cache file ' . $filepath);
  352. if(file_exists($filepath) && filectime($filepath) < time() - $duration){
  353. unlink ($filepath);
  354. $this->debugMessage('Cached file deleted: ' . $filepath);
  355. }
  356. if(file_exists($filepath)){
  357. $this->debugMessage('Loading cached file ' . $filepath);
  358. touch($filepath);
  359. $content = file_get_contents($filepath);
  360. } else {
  361. $this->debugMessage('Caching ' . $url . ' to ' . $filepath);
  362. $dir = substr($filepath, 0, strrpos($filepath, '/'));
  363. if(!is_dir($dir)){
  364. $this->debugMessage('Creating directory ' . $dir);
  365. mkdir($dir, 0777, true);
  366. }
  367. $content = $this->getContents($url, $use_include_path, $context, $offset, $maxLen);
  368. if($content !== false){
  369. file_put_contents($filepath, $content);
  370. }
  371. }
  372. return str_get_html($content
  373. , $lowercase
  374. , $forceTagsClosed
  375. , $target_charset
  376. , $stripRN
  377. , $defaultBRText
  378. , $defaultSpanText);
  379. }
  380. }