You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

621 lines
18 KiB

10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
  1. <?php
  2. namespace Modl;
  3. use Respect\Validation\Validator;
  4. use Movim\Picture;
  5. class Postn extends Model
  6. {
  7. public $origin; // Where the post is comming from (jid or server)
  8. public $node; // microblog or pubsub
  9. public $nodeid; // the ID if the item
  10. public $aname; // author name
  11. public $aid; // author id
  12. public $aemail; // author email
  13. public $title; //
  14. public $content; // The content
  15. public $contentraw; // The raw content
  16. public $contentcleaned; // The cleanned content
  17. public $commentorigin;
  18. public $commentnodeid;
  19. public $published;
  20. public $updated;
  21. public $delay;
  22. public $picture; // Tell if the post contain embeded pictures
  23. public $lat;
  24. public $lon;
  25. public $links;
  26. public $reply;
  27. public $hash;
  28. private $youtube;
  29. public $open;
  30. public $logo;
  31. private $openlink;
  32. public function __construct()
  33. {
  34. $this->hash = md5(openssl_random_pseudo_bytes(5));
  35. $this->_struct = '
  36. {
  37. "origin" :
  38. {"type":"string", "size":64, "key":true },
  39. "node" :
  40. {"type":"string", "size":96, "key":true },
  41. "nodeid" :
  42. {"type":"string", "size":96, "key":true },
  43. "aname" :
  44. {"type":"string", "size":128 },
  45. "aid" :
  46. {"type":"string", "size":64 },
  47. "aemail" :
  48. {"type":"string", "size":64 },
  49. "title" :
  50. {"type":"text" },
  51. "content" :
  52. {"type":"text" },
  53. "contentraw" :
  54. {"type":"text" },
  55. "contentcleaned" :
  56. {"type":"text" },
  57. "commentorigin" :
  58. {"type":"string", "size":64 },
  59. "commentnodeid" :
  60. {"type":"string", "size":96 },
  61. "open" :
  62. {"type":"bool"},
  63. "published" :
  64. {"type":"date" },
  65. "updated" :
  66. {"type":"date" },
  67. "delay" :
  68. {"type":"date" },
  69. "reply" :
  70. {"type":"text" },
  71. "lat" :
  72. {"type":"string", "size":32 },
  73. "lon" :
  74. {"type":"string", "size":32 },
  75. "links" :
  76. {"type":"text" },
  77. "picture" :
  78. {"type":"text" },
  79. "hash" :
  80. {"type":"string", "size":128, "mandatory":true }
  81. }';
  82. parent::__construct();
  83. }
  84. private function getContent($contents)
  85. {
  86. $content = '';
  87. foreach($contents as $c) {
  88. switch($c->attributes()->type) {
  89. case 'html':
  90. case 'xhtml':
  91. $dom = new \DOMDocument('1.0', 'utf-8');
  92. $import = @dom_import_simplexml($c->children());
  93. if($import == null) {
  94. $import = dom_import_simplexml($c);
  95. }
  96. $element = $dom->importNode($import, true);
  97. $dom->appendChild($element);
  98. return (string)$dom->saveHTML();
  99. break;
  100. case 'text':
  101. if(trim($c) != '') {
  102. $this->__set('contentraw', trim($c));
  103. }
  104. break;
  105. default :
  106. $content = (string)$c;
  107. break;
  108. }
  109. }
  110. return $content;
  111. }
  112. private function getTitle($titles)
  113. {
  114. $title = '';
  115. foreach($titles as $t) {
  116. switch($t->attributes()->type) {
  117. case 'html':
  118. case 'xhtml':
  119. $title = strip_tags((string)$t->children()->asXML());
  120. break;
  121. case 'text':
  122. if(trim($t) != '') {
  123. $title = trim($t);
  124. }
  125. break;
  126. default :
  127. $title = (string)$t;
  128. break;
  129. }
  130. }
  131. return $title;
  132. }
  133. public function set($item, $from, $delay = false, $node = false)
  134. {
  135. if($item->item)
  136. $entry = $item->item;
  137. else
  138. $entry = $item;
  139. if($from != '')
  140. $this->__set('origin', $from);
  141. if($node)
  142. $this->__set('node', $node);
  143. else
  144. $this->__set('node', (string)$item->attributes()->node);
  145. $this->__set('nodeid', (string)$entry->attributes()->id);
  146. if($entry->entry->id)
  147. $this->__set('nodeid', (string)$entry->entry->id);
  148. // Get some informations on the author
  149. if($entry->entry->author->name)
  150. $this->__set('aname', (string)$entry->entry->author->name);
  151. if($entry->entry->author->uri)
  152. $this->__set('aid', substr((string)$entry->entry->author->uri, 5));
  153. if($entry->entry->author->email)
  154. $this->__set('aemail', (string)$entry->entry->author->email);
  155. // Non standard support
  156. if($entry->entry->source && $entry->entry->source->author->name)
  157. $this->__set('aname', (string)$entry->entry->source->author->name);
  158. if($entry->entry->source && $entry->entry->source->author->uri)
  159. $this->__set('aid', substr((string)$entry->entry->source->author->uri, 5));
  160. $this->__set('title', $this->getTitle($entry->entry->title));
  161. // Content
  162. if($entry->entry->summary && (string)$entry->entry->summary != '')
  163. $summary = '<p class="summary">'.(string)$entry->entry->summary.'</p>';
  164. else
  165. $summary = '';
  166. if($entry->entry && $entry->entry->content) {
  167. $content = $this->getContent($entry->entry->content);
  168. } elseif($summary == '')
  169. $content = __('');
  170. else
  171. $content = '';
  172. $content = $summary.$content;
  173. if($entry->entry->updated)
  174. $this->__set('updated', (string)$entry->entry->updated);
  175. else
  176. $this->__set('updated', gmdate(SQL::SQL_DATE));
  177. if($entry->entry->published)
  178. $this->__set('published', (string)$entry->entry->published);
  179. elseif($entry->entry->updated)
  180. $this->__set('published', (string)$entry->entry->updated);
  181. else
  182. $this->__set('published', gmdate(SQL::SQL_DATE));
  183. if($delay)
  184. $this->__set('delay', $delay);
  185. // Tags parsing
  186. if($entry->entry->category) {
  187. $td = new \Modl\TagDAO;
  188. $td->delete($this->__get('nodeid'));
  189. if($entry->entry->category->count() == 1
  190. && isset($entry->entry->category->attributes()->term)) {
  191. $tag = new \Modl\Tag;
  192. $tag->nodeid = $this->__get('nodeid');
  193. $tag->tag = strtolower((string)$entry->entry->category->attributes()->term);
  194. $td->set($tag);
  195. } else {
  196. foreach($entry->entry->category as $cat) {
  197. $tag = new \Modl\Tag;
  198. $tag->nodeid = $this->__get('nodeid');
  199. $tag->tag = strtolower((string)$cat->attributes()->term);
  200. $td->set($tag);
  201. }
  202. }
  203. }
  204. if(!isset($this->commentorigin))
  205. $this->__set('commentorigin', $this->origin);
  206. $this->__set('content', trim($content));
  207. $this->contentcleaned = purifyHTML(html_entity_decode($this->content));
  208. if($entry->entry->geoloc) {
  209. if($entry->entry->geoloc->lat != 0)
  210. $this->__set('lat', (string)$entry->entry->geoloc->lat);
  211. if($entry->entry->geoloc->lon != 0)
  212. $this->__set('lon', (string)$entry->entry->geoloc->lon);
  213. }
  214. // We fill empty aid
  215. if($this->isMicroblog() && empty($this->aid)) {
  216. $this->__set('aid', $this->origin);
  217. }
  218. // We check if this is a reply
  219. if($entry->entry->{'in-reply-to'}) {
  220. $href = (string)$entry->entry->{'in-reply-to'}->attributes()->href;
  221. $arr = explode(';', $href);
  222. $reply = [
  223. 'origin' => substr($arr[0], 5, -1),
  224. 'node' => substr($arr[1], 5),
  225. 'nodeid' => substr($arr[2], 5)
  226. ];
  227. $this->__set('reply', serialize($reply));
  228. }
  229. $extra = false;
  230. // We try to extract a picture
  231. $xml = \simplexml_load_string('<div>'.$this->contentcleaned.'</div>');
  232. if($xml) {
  233. $results = $xml->xpath('//img/@src');
  234. $check = new \Movim\Task\CheckSmallPicture;
  235. if(is_array($results) && !empty($results)) {
  236. $extra = (string)$results[0];
  237. return $check->run($extra)
  238. ->then(function($small) use($extra, $entry) {
  239. if($small) $this->picture = $extra;
  240. $this->setAttachments($entry->entry->link, $extra);
  241. });
  242. } else {
  243. $results = $xml->xpath('//video/@poster');
  244. if(is_array($results) && !empty($results)) {
  245. $extra = (string)$results[0];
  246. return $check->run($extra)
  247. ->then(function($small) use($extra, $entry) {
  248. $this->picture = $extra;
  249. $this->setAttachments($entry->entry->link, $extra);
  250. });
  251. }
  252. }
  253. $results = $xml->xpath('//a');
  254. if(is_array($results) && !empty($results)) {
  255. foreach($results as $link) {
  256. $link->addAttribute('target', '_blank');
  257. }
  258. }
  259. }
  260. $this->setAttachments($entry->entry->link, $extra);
  261. return new \React\Promise\Promise(function($resolve) {
  262. $resolve(true);
  263. });
  264. }
  265. private function typeIsPicture($type)
  266. {
  267. return in_array($type, ['image/jpeg', 'image/png', 'image/jpg', 'image/gif']);
  268. }
  269. private function setAttachments($links, $extra = false)
  270. {
  271. $l = [];
  272. foreach($links as $attachment) {
  273. $enc = [];
  274. $enc = (array)$attachment->attributes();
  275. $enc = $enc['@attributes'];
  276. array_push($l, $enc);
  277. if($this->picture == null
  278. && isset($enc['type'])
  279. && $this->typeIsPicture($enc['type'])
  280. /*&& isSmallPicture($enc['href'])*/) {
  281. $this->picture = $enc['href'];
  282. }
  283. if($enc['rel'] == 'alternate'
  284. && Validator::url()->validate($enc['href'])) $this->open = true;
  285. if((string)$attachment->attributes()->title == 'comments') {
  286. $substr = explode('?',substr((string)$attachment->attributes()->href, 5));
  287. $this->commentorigin = reset($substr);
  288. $this->commentnodeid = substr((string)$substr[1], 36);
  289. }
  290. }
  291. if($extra) {
  292. array_push(
  293. $l,
  294. [
  295. 'rel' => 'enclosure',
  296. 'href' => $extra,
  297. 'type' => 'picture'
  298. ]);
  299. }
  300. if(!empty($l)) {
  301. $this->links = serialize($l);
  302. }
  303. }
  304. public function getAttachments()
  305. {
  306. $attachments = null;
  307. $this->openlink = null;
  308. if(isset($this->links)) {
  309. $links = unserialize($this->links);
  310. $attachments = [
  311. 'pictures' => [],
  312. 'files' => [],
  313. 'links' => []
  314. ];
  315. foreach($links as $l) {
  316. // If the href is not a valid URL we skip
  317. if(!Validator::url()->validate($l['href'])) continue;
  318. // Prepare the switch
  319. $rel = isset($l['rel']) ? $l['rel'] : null;
  320. switch($rel) {
  321. case 'enclosure':
  322. if($this->typeIsPicture($l['type'])) {
  323. array_push($attachments['pictures'], $l);
  324. } elseif($l['type'] != 'picture') {
  325. array_push($attachments['files'], $l);
  326. }
  327. break;
  328. case 'related':
  329. if(preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $l['href'], $match)) {
  330. $this->youtube = $match[1];
  331. }
  332. array_push(
  333. $attachments['links'],
  334. [
  335. 'href' => $l['href'],
  336. 'url' => parse_url($l['href']),
  337. 'rel' => 'related'
  338. ]
  339. );
  340. break;
  341. case 'alternate':
  342. default:
  343. $this->openlink = $l['href'];
  344. if(!$this->isMicroblog()) {
  345. array_push(
  346. $attachments['links'],
  347. [
  348. 'href' => $l['href'],
  349. 'url' => parse_url($l['href']),
  350. 'rel' => 'alternate'
  351. ]
  352. );
  353. }
  354. break;
  355. }
  356. }
  357. }
  358. if(empty($attachments['pictures'])) unset($attachments['pictures']);
  359. if(empty($attachments['files'])) unset($attachments['files']);
  360. if(empty($attachments['links'])) unset($attachments['links']);
  361. return $attachments;
  362. }
  363. public function getAttachment()
  364. {
  365. $attachments = $this->getAttachments();
  366. foreach($attachments as $key => $group) {
  367. foreach($group as $attachment) {
  368. if(in_array($attachment['rel'], ['enclosure', 'related'])) {
  369. return $attachment;
  370. }
  371. }
  372. }
  373. return false;
  374. }
  375. public function getPicture()
  376. {
  377. return $this->picture;
  378. }
  379. public function getYoutube()
  380. {
  381. return $this->youtube;
  382. }
  383. public function getPlace()
  384. {
  385. return (isset($this->lat, $this->lon) && $this->lat != '' && $this->lon != '');
  386. }
  387. public function getLogo()
  388. {
  389. $p = new Picture;
  390. return $p->get($this->origin.$this->node, 120);
  391. }
  392. public function getUUID()
  393. {
  394. if(substr($this->nodeid, 10) == 'urn:uuid:') {
  395. return $this->nodeid;
  396. } else {
  397. return 'urn:uuid:'.generateUUID($this->nodeid);
  398. }
  399. }
  400. public function getRef()
  401. {
  402. return 'xmpp:'.$this->origin.'?;node='.$this->node.';item='.$this->nodeid;
  403. }
  404. public function isMine()
  405. {
  406. $user = new \User;
  407. return ($this->aid == $user->getLogin()
  408. || $this->origin == $user->getLogin());
  409. }
  410. public function isPublic() {
  411. return ($this->open);
  412. }
  413. public function isMicroblog()
  414. {
  415. return ($this->node == "urn:xmpp:microblog:0");
  416. }
  417. public function isEditable()
  418. {
  419. return ($this->contentraw != null || $this->links != null);
  420. }
  421. public function isShort()
  422. {
  423. return (strlen($this->contentcleaned) < 700);
  424. }
  425. public function isNSFW()
  426. {
  427. return (current(explode('.', $this->origin)) == 'nsfw');
  428. }
  429. public function isReply()
  430. {
  431. return isset($this->reply);
  432. }
  433. public function isRTL()
  434. {
  435. return isRTL($this->contentraw);
  436. }
  437. public function getReply()
  438. {
  439. if(!$this->reply) return;
  440. $reply = unserialize($this->reply);
  441. $pd = new \Modl\PostnDAO;
  442. return $pd->get($reply['origin'], $reply['node'], $reply['nodeid']);
  443. }
  444. public function getPublicUrl()
  445. {
  446. return $this->openlink;
  447. }
  448. public function countComments()
  449. {
  450. $pd = new \Modl\PostnDAO;
  451. return $pd->countComments($this->commentorigin, $this->commentnodeid);
  452. }
  453. public function getTags()
  454. {
  455. $td = new \Modl\TagDAO;
  456. $tags = $td->getTags($this->nodeid);
  457. if(is_array($tags)) {
  458. return array_map(function($tag) { return $tag->tag; }, $tags);
  459. }
  460. }
  461. public function getTagsImploded()
  462. {
  463. $tags = $this->getTags();
  464. if(is_array($tags)) {
  465. return implode(', ', $tags);
  466. }
  467. }
  468. public function getNext()
  469. {
  470. $pd = new PostnDAO;
  471. return $pd->getNext($this->origin, $this->node, $this->nodeid);
  472. }
  473. public function getPrevious()
  474. {
  475. $pd = new PostnDAO;
  476. return $pd->getPrevious($this->origin, $this->node, $this->nodeid);
  477. }
  478. }
  479. class ContactPostn extends Postn
  480. {
  481. public $jid;
  482. public $fn;
  483. public $name;
  484. public $privacy;
  485. public $phototype;
  486. public $photobin;
  487. public $nickname;
  488. function getContact()
  489. {
  490. $c = new Contact;
  491. $c->jid = $this->aid;
  492. $c->fn = $this->fn;
  493. $c->name = $this->name;
  494. $c->nickname = $this->nickname;
  495. $c->phototype = $this->phototype;
  496. $c->photobin = $this->photobin;
  497. return $c;
  498. }
  499. public function isRecycled()
  500. {
  501. return ($this->getContact()->jid
  502. && $this->node == 'urn:xmpp:microblog:0'
  503. && (strtolower($this->origin) != strtolower($this->getContact()->jid)));
  504. }
  505. public function isEditable()
  506. {
  507. return (
  508. ($this->contentraw != null || $this->links != null)
  509. && !$this->isRecycled());
  510. }
  511. }