PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/library/feed.php

https://github.com/alugo/Goteo
PHP | 583 lines | 403 code | 71 blank | 109 comment | 39 complexity | 21353ea0102d425198a9f97acdc8f5a6 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /*
  3. * Copyright (C) 2012 Platoniq y Fundaci贸n Fuentes Abiertas (see README for details)
  4. * This file is part of Goteo.
  5. *
  6. * Goteo is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Goteo is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Goteo. If not, see <http://www.gnu.org/licenses/agpl.txt>.
  18. *
  19. */
  20. namespace Goteo\Library {
  21. use Goteo\Core\Model,
  22. Goteo\Model\Blog\Post,
  23. Goteo\Library\Text;
  24. /*
  25. * Clase para loguear eventos
  26. */
  27. class Feed {
  28. public
  29. $id,
  30. $title, // titulo entrada o nombre usuario
  31. $url = null, // enlace del titulo
  32. $image = null, // enlace del titulo
  33. $scope = 'admin', // ambito del evento (public, admin, private)
  34. $type = 'system', // tipo de evento ($public_types , $admin_types, $private_types)
  35. $timeago, // el hace tanto
  36. $date, // fecha y hora del evento
  37. $html, // contenido del evento en codigo html
  38. $unique = false, // si es un evento unique, no lo grabamos si ya hay un evento con esa url
  39. $unique_issue = false, // si se encuentra con que esta duplicando el feed
  40. $text, // id del texto dinamico
  41. $params, // (array serializado en bd) parametros para el texto dinamico
  42. $target_type, // tipo de objetivo del evento (user, project, call, node, etc..) normalmente project
  43. $target_id; // id registro del objetivo (normalmente varchar(50))
  44. static public function _admin_types() {
  45. return array(
  46. 'all' => array(
  47. 'label' => Text::_('Todo'),
  48. 'color' => 'light-blue'
  49. ),
  50. 'admin' => array(
  51. 'label' => Text::_('Administrador'),
  52. 'color' => 'red'
  53. ),
  54. 'user' => array(
  55. 'label' => Text::_('Usuario'),
  56. 'color' => 'blue'
  57. ),
  58. 'project' => array(
  59. 'label' => Text::_('Proyecto'),
  60. 'color' => 'light-blue'
  61. ),
  62. 'call' => array(
  63. 'label' => Text::_('Convocatoria'),
  64. 'color' => 'light-blue'
  65. ),
  66. 'money' => array(
  67. 'label' => Text::_('Transferencias'),
  68. 'color' => 'violet'
  69. ),
  70. 'system' => array(
  71. 'label' => Text::_('Sistema'),
  72. 'color' => 'grey'
  73. )
  74. );
  75. }
  76. static public function _color() {
  77. return array(
  78. 'user' => 'blue',
  79. 'project' => 'light-blue',
  80. 'call' => 'light-blue',
  81. 'blog' => 'grey',
  82. 'news' => 'grey',
  83. 'money' => 'violet',
  84. 'drop' => 'violet',
  85. 'relevant' => 'red',
  86. 'comment' => 'green',
  87. 'update-comment' => 'grey',
  88. 'message' => 'green',
  89. 'system' => 'grey',
  90. 'update' => 'grey'
  91. );
  92. }
  93. static public function _page() {
  94. return array(
  95. 'user' => '/user/profile/',
  96. 'project' => '/project/',
  97. 'call' => '/call/',
  98. 'drop' => SITE_URL,
  99. 'blog' => '/blog/',
  100. 'news' => '/news/',
  101. 'relevant' => '',
  102. 'comment' => '/blog/',
  103. 'update-comment' => '/project/',
  104. 'message' => '/project/',
  105. 'system' => '/admin/',
  106. 'update' => '/project/'
  107. );
  108. }
  109. /**
  110. * Metodo que rellena instancia
  111. * No usamos el __construct para no joder el fetch_CLASS
  112. */
  113. public function populate($title, $url, $html, $image = null) {
  114. $this->title = $title;
  115. $this->url = $url;
  116. $this->html = $html;
  117. $this->image = $image;
  118. }
  119. /**
  120. * Metodo que establece el elemento al que afecta el evento
  121. *
  122. * Sufridor del evento: tipo (tabla) & id registro
  123. *
  124. * @param $id string normalmente varchar(50)
  125. * @param $type string (project, user, node, call, etc...)
  126. */
  127. public function setTarget ($id, $type = 'project') {
  128. $this->target_id = $id;
  129. $this->target_type = $type;
  130. }
  131. public function doAdmin ($type = 'system') {
  132. $this->doEvent('admin', $type);
  133. }
  134. public function doPublic ($type = 'goteo') {
  135. $this->doEvent('public', $type);
  136. }
  137. public function doPrivate ($type = 'info') {
  138. $this->doEvent('private', $type);
  139. }
  140. private function doEvent ($scope = 'admin', $type = 'system') {
  141. $this->scope = $scope;
  142. $this->type = $type;
  143. $this->add();
  144. }
  145. /**
  146. * Metodo para sacar los eventos
  147. *
  148. * @param string $type tipo de evento (public: columnas goteo, proyectos, comunidad; admin: categorias de filtro)
  149. * @param string $scope ambito de eventos (public | admin)
  150. * @param numeric $limit limite de elementos
  151. * @return array list of items
  152. */
  153. public static function getAll($type = 'all', $scope = 'public', $limit = '99', $node = null) {
  154. $list = array();
  155. try {
  156. $values = array(':scope' => $scope);
  157. $sqlType = '';
  158. if ($type != 'all') {
  159. $sqlType = " AND feed.type = :type";
  160. $values[':type'] = $type;
  161. } else {
  162. // acciones del web service ultra secreto
  163. $sqlType = " AND feed.type != 'usws'";
  164. }
  165. $sqlNode = '';
  166. if (!empty($node) && $node != \GOTEO_NODE) {
  167. /* segun el objetivo del feed sea:
  168. * proyectos del nodo
  169. * usuarios del nodo
  170. * convocatorias destacadas por el nodo (aunque inactivas)
  171. * el propio nodo
  172. * el blog
  173. */
  174. $sqlNode = " AND (
  175. (feed.target_type = 'project' AND feed.target_id IN (
  176. SELECT id FROM project WHERE node = :node
  177. )
  178. )
  179. OR (feed.target_type = 'user' AND feed.target_id IN (
  180. SELECT id FROM user WHERE node = :node
  181. )
  182. )
  183. OR (feed.target_type = 'call' AND feed.target_id IN (
  184. SELECT `call` FROM campaign WHERE node = :node
  185. )
  186. )
  187. OR (feed.target_type = 'node' AND feed.target_id = :node)
  188. OR (feed.target_type = 'blog')
  189. )";
  190. $values[':node'] = $node;
  191. }
  192. $sql = "SELECT
  193. feed.id as id,
  194. feed.title as title,
  195. feed.url as url,
  196. feed.image as image,
  197. DATE_FORMAT(feed.datetime, '%H:%i %d|%m|%Y') as date,
  198. feed.datetime as timer,
  199. feed.html as html
  200. FROM feed
  201. WHERE feed.scope = :scope
  202. $sqlType
  203. $sqlNode
  204. ORDER BY datetime DESC
  205. LIMIT $limit
  206. ";
  207. $query = Model::query($sql, $values);
  208. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $item) {
  209. // si es la columan goteo, vamos a cambiar el html por el del post traducido
  210. if ($type == 'goteo') {
  211. // primero sacamos la id del post de la url
  212. $matches = array();
  213. \preg_match('(\d+)', $item->url, $matches);
  214. if (!empty($matches[0])) {
  215. // luego hacemos get del post
  216. $post = Post::get($matches[0], LANG);
  217. if ($post->owner_type == 'node' && $post->owner_id != \GOTEO_NODE) {
  218. if (!\Goteo\Core\NodeSys::isActive($post->owner_id)) {
  219. continue;
  220. }
  221. }
  222. // y substituimos el $item->html por el $post->html
  223. $item->html = Text::recorta($post->text, 250);
  224. $item->title = $post->title;
  225. $item->image = $post->image->id;
  226. // arreglo la fecha de publicaci贸n
  227. $parts = explode(' ', $item->timer);
  228. $item->timer = $post->date . ' ' . $parts[1];
  229. }
  230. }
  231. //hace tanto
  232. $item->timeago = self::time_ago($item->timer);
  233. $list[] = $item;
  234. }
  235. return $list;
  236. } catch (\PDOException $e) {
  237. throw new Exception(Text::_('No se ha guardado correctamente. ') . $e->getMessage() . "<br />$sql<br /><pre>" . print_r($values, 1) . "</pre>");
  238. }
  239. }
  240. /**
  241. * Metodo para sacar los eventos de novedades de proyecto (solo)
  242. *
  243. * @param string $limit limite de elementos
  244. * @return array list of items
  245. */
  246. public static function getProjUpdates($limit = '99') {
  247. $list = array();
  248. try {
  249. $sql = "SELECT
  250. feed.id as id,
  251. feed.title as title,
  252. feed.url as url,
  253. feed.image as image,
  254. DATE_FORMAT(feed.datetime, '%H:%i %d|%m|%Y') as date,
  255. feed.datetime as timer,
  256. feed.html as html
  257. FROM feed
  258. WHERE feed.scope = 'public'
  259. AND feed.type = 'projects'
  260. AND feed.url LIKE '%updates%'
  261. ORDER BY datetime DESC
  262. LIMIT $limit
  263. ";
  264. $query = Model::query($sql, $values);
  265. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $item) {
  266. //hace tanto
  267. $item->timeago = self::time_ago($item->timer);
  268. $list[] = $item;
  269. }
  270. return $list;
  271. } catch (\PDOException $e) {
  272. throw new Exception(Text::_('No se ha guardado correctamente. ') . $e->getMessage() . "<br />$sql<br /><pre>" . print_r($values, 1) . "</pre>");
  273. }
  274. }
  275. /**
  276. * Metodo para sacar los eventos relacionados con un usuario
  277. *
  278. * @param string $id id del usuario
  279. * @param string $filter para tipos de eventos que queremos obtener
  280. * @return array list of items (como getAll)
  281. */
  282. public static function getUserItems($id, $filter = 'private') {
  283. $list = array();
  284. try {
  285. $values = array();
  286. $wheres = array();
  287. if (!empty($filter)) {
  288. switch ($filter) {
  289. case 'private':
  290. // eventos que afecten al usuario
  291. $wheres[] = "feed.target_type = 'user'";
  292. $wheres[] = "feed.target_id = :target_id";
  293. $values[':target_id'] = $id;
  294. break;
  295. case 'supported':
  296. // eventos del proyectos que cofinancio (o he intentado cofinanciar)
  297. $wheres[] = "feed.target_type = 'project'";
  298. $wheres[] = "feed.target_id IN (
  299. SELECT DISTINCT(invest.project) FROM invest WHERE invest.user = :id
  300. )";
  301. $values[':id'] = $id;
  302. break;
  303. case 'comented':
  304. // eventos de proyectos en los que comento pero que no cofinancio
  305. $wheres[] = "feed.target_type = 'project'";
  306. $wheres[] = "( feed.target_id IN (
  307. SELECT DISTINCT(message.project) FROM message WHERE message.user = :id
  308. ) OR feed.target_id IN (
  309. SELECT DISTINCT(blog.owner)
  310. FROM comment
  311. INNER JOIN post
  312. ON post.id = comment.post
  313. INNER JOIN blog
  314. ON blog.id = post.blog
  315. AND blog.type = 'project'
  316. WHERE comment.user = :id
  317. )
  318. )";
  319. $wheres[] = "feed.target_id NOT IN (
  320. SELECT DISTINCT(invest.project) FROM invest WHERE invest.user = :id
  321. )";
  322. $values[':id'] = $id;
  323. break;
  324. }
  325. }
  326. $sql = "SELECT
  327. feed.id as id,
  328. feed.title as title,
  329. feed.url as url,
  330. feed.image as image,
  331. DATE_FORMAT(feed.datetime, '%H:%i %d|%m|%Y') as date,
  332. feed.datetime as timer,
  333. feed.html as html
  334. FROM feed
  335. WHERE " . implode(' AND ', $wheres) . "
  336. ORDER BY datetime DESC
  337. LIMIT 99
  338. ";
  339. $query = Model::query($sql, $values);
  340. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $item) {
  341. //hace tanto
  342. $item->timeago = self::time_ago($item->timer);
  343. $list[] = $item;
  344. }
  345. return $list;
  346. } catch (\PDOException $e) {
  347. return array();
  348. @\mail(\GOTEO_MAIL, 'ERROR SQL en Feed::getItems', Text::_('No se ha guardado correctamente. ') . $e->getMessage() . "<br />$sql<br /><pre>" . print_r($values, 1) . "</pre>");
  349. }
  350. }
  351. /**
  352. * Metodo para grabar eventos
  353. *
  354. * Los datos del evento estan en el objeto
  355. *
  356. *
  357. * @param array $errors
  358. *
  359. * @access public
  360. * @return boolean true | false as success
  361. *
  362. */
  363. public function add() {
  364. if (empty($this->html)) {
  365. @mail(\GOTEO_MAIL,
  366. 'Evento feed sin html: ' . SITE_URL,
  367. "Feed sin contenido html<hr /><pre>" . print_r($this, 1) . "</pre>");
  368. return false;
  369. }
  370. // we dont want to show the actions made by root user
  371. if ($this->scope == 'public' && $_SESSION['user']->id == 'root') {
  372. return false;
  373. }
  374. // primero, verificar si es unique, no duplicarlo
  375. if ($this->unique === true) {
  376. $query = Model::query("SELECT id FROM feed WHERE url = :url AND scope = :scope AND type = :type",
  377. array(
  378. ':url' => $this->url,
  379. ':scope' => $this->scope,
  380. ':type' => $this->type
  381. ));
  382. if ($query->fetchColumn(0) != false) {
  383. $this->unique_issue = true;
  384. return true;
  385. }
  386. }
  387. try {
  388. $values = array(
  389. ':title' => $this->title,
  390. ':url' => $this->url,
  391. ':image' => $this->image,
  392. ':scope' => $this->scope,
  393. ':type' => $this->type,
  394. ':html' => $this->html,
  395. ':target_type' => $this->target_type,
  396. ':target_id' => $this->target_id
  397. );
  398. $sql = "INSERT INTO feed
  399. (id, title, url, scope, type, html, image, target_type, target_id)
  400. VALUES
  401. ('', :title, :url, :scope, :type, :html, :image, :target_type, :target_id)
  402. ";
  403. if (Model::query($sql, $values)) {
  404. return true;
  405. } else {
  406. @mail(\GOTEO_MAIL,
  407. 'Fallo al hacer evento feed: ' . SITE_URL,
  408. "Ha fallado Feed<br /> {$sql} con <pre>" . print_r($values, 1) . "</pre><hr /><pre>" . print_r($this, 1) . "</pre>");
  409. return false;
  410. }
  411. } catch(\PDOException $e) {
  412. @mail(\GOTEO_MAIL,
  413. 'PDO Exception evento feed: ' . SITE_URL,
  414. "Ha fallado Feed PDO Exception<br /> {$sql} con " . $e->getMessage() . "<hr /><pre>" . print_r($this, 1) . "</pre>");
  415. return false;
  416. }
  417. }
  418. /**
  419. * Metodo para transformar un TIMESTAMP en un "hace tanto"
  420. *
  421. * Los periodos vienen de un texto tipo singular-plural_sg-pl_id-sg-pl_...
  422. * en mismo orden y cantidad que los per_id
  423. */
  424. public static function time_ago($date,$granularity=1) {
  425. $per_id = array('sec', 'min', 'hour', 'day', 'week', 'month', 'year', 'dec');
  426. $per_txt = array();
  427. foreach (\explode('_', Text::get('feed-timeago-periods')) as $key=>$grptxt) {
  428. $per_txt[$per_id[$key]] = \explode('-', $grptxt);
  429. }
  430. $justnow = Text::get('feed-timeago-justnow');
  431. $retval = '';
  432. $date = strtotime($date);
  433. $ahora = time();
  434. $difference = $ahora - $date;
  435. $periods = array('dec' => 315360000,
  436. 'year' => 31536000,
  437. 'month' => 2628000,
  438. 'week' => 604800,
  439. 'day' => 86400,
  440. 'hour' => 3600,
  441. 'min' => 60,
  442. 'sec' => 1);
  443. foreach ($periods as $key => $value) {
  444. if ($difference >= $value) {
  445. $time = floor($difference/$value);
  446. $difference %= $value;
  447. $retval .= ($retval ? ' ' : '').$time.' ';
  448. $retval .= (($time > 1) ? $per_txt[$key][1] : $per_txt[$key][0]);
  449. $granularity--;
  450. }
  451. if ($granularity == '0') { break; }
  452. }
  453. return empty($retval) ? $justnow : $retval;
  454. }
  455. /**
  456. * Genera codigo html para enlace o texto dentro de feed
  457. *
  458. */
  459. public static function item ($type = 'system', $label = 'label', $id = null) {
  460. $page = static::_page();
  461. $color = static::_color();
  462. // si llega id es un enlace
  463. if (isset($id)) {
  464. return '<a href="'.$page[$type].$id.'" class="'.$color[$type].'" target="_blank">'.$label.'</a>';
  465. } else {
  466. return '<span class="'.$color[$type].'">'.$label.'</span>';
  467. }
  468. }
  469. /**
  470. * Genera codigo html para feed p煤blico
  471. *
  472. * segun tenga imagen, ebnlace, titulo, tipo de enlace
  473. *
  474. */
  475. public static function subItem ($item) {
  476. $pub_timeago = Text::get('feed-timeago-published', $item->timeago);
  477. $content = '<div class="subitem">';
  478. // si enlace -> t铆tulo como texto del enlace
  479. if (!empty($item->url)) {
  480. // si imagen -> segun enlace:
  481. if (!empty($item->image)) {
  482. if (substr($item->url, 0, 5) == '/user') {
  483. $content .= '<div class="content-avatar">
  484. <a href="'.$item->url.'" class="avatar"><img src="'.SRC_URL.'/image/'.$item->image.'/32/32/1" /></a>
  485. <a href="'.$item->url.'" class="username">'.$item->title.'</a>
  486. <span class="datepub">'.$pub_timeago.'</span>
  487. </div>';
  488. } else {
  489. $content .= '<div class="content-image">
  490. <a href="'.$item->url.'" class="image"><img src="'.SRC_URL.'/image/'.$item->image.'/90/60/1" /></a>
  491. <a href="'.$item->url.'" class="project light-blue">'.$item->title.'</a>
  492. <span class="datepub">'.$pub_timeago.'</span>
  493. </div>';
  494. }
  495. } else {
  496. // solo titulo con enlace
  497. $content .= '<div class="content-title">
  498. <h5 class="light-blue"><a href="'.$item->url.'" class="project light-blue">'.$item->title.'</a></h5>
  499. <span class="datepub">'.$pub_timeago.'</span>
  500. </div>';
  501. }
  502. } else {
  503. // solo el timeago
  504. $content .= '<span class="datepub">'.$pub_timeago.'</span>';
  505. }
  506. // y lo que venga en el html
  507. $content .= '<div class="content-pub">'.$item->html.'</div>';
  508. $content .= '</div>';
  509. return $content;
  510. }
  511. }
  512. }