PageRenderTime 55ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/model/project.php

https://github.com/alugo/Goteo
PHP | 2348 lines | 1652 code | 306 blank | 390 comment | 266 complexity | d94bad9556a186ebb76aecce3f486f7b 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\Model {
  21. use Goteo\Core\ACL,
  22. Goteo\Library\Check,
  23. Goteo\Library\Text,
  24. Goteo\Model\User,
  25. Goteo\Model\Image,
  26. Goteo\Model\Message;
  27. class Project extends \Goteo\Core\Model {
  28. public
  29. $id = null,
  30. $dontsave = false,
  31. $owner, // User who created it
  32. $node, // Node this project belongs to
  33. $status,
  34. $progress, // puntuation %
  35. $amount, // Current donated amount
  36. $user, // owner's user information
  37. // Register contract data
  38. $contract_name, // Nombre y apellidos del responsable del proyecto
  39. $contract_nif, // Guardar sin espacios ni puntos ni guiones
  40. $contract_email, // cuenta paypal
  41. $phone, // guardar sin espacios ni puntos
  42. // Para marcar física o jurídica
  43. $contract_entity = false, // false = física (persona) true = jurídica (entidad)
  44. // Para persona física
  45. $contract_birthdate,
  46. // Para entidad jurídica
  47. $entity_office, // cargo del responsable dentro de la entidad
  48. $entity_name, // denomincion social de la entidad
  49. $entity_cif, // CIF de la entidad
  50. // Campos de Domicilio: Igual para persona o entidad
  51. $address,
  52. $zipcode,
  53. $location, // owner's location
  54. $country,
  55. // Domicilio postal
  56. $secondary_address = false, // si es diferente al domicilio fiscal
  57. $post_address = null,
  58. $post_zipcode = null,
  59. $post_location = null,
  60. $post_country = null,
  61. // Edit project description
  62. $name,
  63. $subtitle,
  64. $lang = 'es',
  65. $image,
  66. $gallery = array(), // array de instancias image de project_image
  67. $secGallery = array(), // array de instancias image de project_image (secundarias)
  68. $description,
  69. $motivation,
  70. $video, // video de motivacion
  71. $video_usubs, // universal subtitles para el video de motivacion
  72. $about,
  73. $goal,
  74. $related,
  75. $reward, // nueva sección, solo editable por admines y traductores
  76. $categories = array(),
  77. $media, // video principal
  78. $media_usubs, // universal subtitles para el video principal
  79. $keywords, // por ahora se guarda en texto tal cual
  80. $currently, // Current development status of the project
  81. $project_location, // project execution location
  82. $scope, // ambito de alcance
  83. $translate, // si se puede traducir (bool)
  84. // costs
  85. $costs = array(), // project\cost instances with type
  86. $schedule, // picture of the costs schedule
  87. $resource, // other current resources
  88. // Rewards
  89. $social_rewards = array(), // instances of project\reward for the public (collective type)
  90. $individual_rewards = array(), // instances of project\reward for investors (individual type)
  91. // Collaborations
  92. $supports = array(), // instances of project\support
  93. // Comment
  94. $comment, // Comentario para los admin introducido por el usuario
  95. //Operative purpose properties
  96. $mincost = 0,
  97. $maxcost = 0,
  98. //Obtenido, Días, Cofinanciadores
  99. $invested = 0, //cantidad de inversión
  100. $days = 0, //para 40 desde la publicación o para 80 si no está caducado
  101. $investors = array(), // aportes individuales a este proyecto
  102. $num_investors = 0, // numero de usuarios que han aportado
  103. $round = 0, // para ver si ya está en la fase de los 40 a los 80
  104. $passed = null, // para ver si hemos hecho los eventos de paso a segunda ronda
  105. $willpass = null, // fecha final de primera ronda
  106. $errors = array(), // para los fallos en los datos
  107. $okeys = array(), // para los campos que estan ok
  108. // para puntuacion
  109. $score = 0, //puntos
  110. $max = 0, // maximo de puntos
  111. $messages = array(), // mensajes de los usuarios hilos con hijos
  112. $finishable = false, // llega al progresso mínimo para enviar a revision
  113. $tagmark = null; // banderolo a mostrar
  114. /**
  115. * Sobrecarga de métodos 'getter'.
  116. *
  117. * @param type string $name
  118. * @return type mixed
  119. */
  120. public function __get ($name) {
  121. if($name == "allowpp") {
  122. return Project\Account::getAllowpp($this->id);
  123. }
  124. if($name == "budget") {
  125. return self::calcCosts($this->id);
  126. }
  127. return $this->$name;
  128. }
  129. /**
  130. * Inserta un proyecto con los datos mínimos
  131. *
  132. * @param array $data
  133. * @return boolean
  134. */
  135. public function create ($node = \GOTEO_NODE, &$errors = array()) {
  136. $user = $_SESSION['user']->id;
  137. if (empty($user)) {
  138. return false;
  139. }
  140. // cojemos el número de proyecto de este usuario
  141. $query = self::query("SELECT COUNT(id) as num FROM project WHERE owner = ?", array($user));
  142. if ($now = $query->fetchObject())
  143. $num = $now->num + 1;
  144. else
  145. $num = 1;
  146. // datos del usuario que van por defecto: name->contract_name, location->location
  147. $userProfile = User::get($user);
  148. // datos del userpersonal por defecto a los cammpos del paso 2
  149. $userPersonal = User::getPersonal($user);
  150. $values = array(
  151. ':id' => md5($user.'-'.$num),
  152. ':name' => Text::_("El nuevo proyecto de ").$userProfile->name,
  153. ':lang' => 'es',
  154. ':status' => 1,
  155. ':progress' => 0,
  156. ':owner' => $user,
  157. ':node' => $node,
  158. ':amount' => 0,
  159. ':days' => 0,
  160. ':created' => date('Y-m-d'),
  161. ':contract_name' => ($userPersonal->contract_name) ?
  162. $userPersonal->contract_name :
  163. $userProfile->name,
  164. ':contract_nif' => $userPersonal->contract_nif,
  165. ':phone' => $userPersonal->phone,
  166. ':address' => $userPersonal->address,
  167. ':zipcode' => $userPersonal->zipcode,
  168. ':location' => ($userPersonal->location) ?
  169. $userPersonal->location :
  170. $userProfile->location,
  171. ':country' => ($userPersonal->country) ?
  172. $userPersonal->country :
  173. Check::country(),
  174. ':project_location' => ($userPersonal->location) ?
  175. $userPersonal->location :
  176. $userProfile->location,
  177. );
  178. $campos = array();
  179. foreach (\array_keys($values) as $campo) {
  180. $campos[] = \str_replace(':', '', $campo);
  181. }
  182. $sql = "REPLACE INTO project (" . implode(',', $campos) . ")
  183. VALUES (" . implode(',', \array_keys($values)) . ")";
  184. try {
  185. self::query($sql, $values);
  186. foreach ($campos as $campo) {
  187. $this->$campo = $values[":$campo"];
  188. }
  189. return $this->id;
  190. } catch (\PDOException $e) {
  191. $errors[] = "ERROR al crear un nuevo proyecto<br />$sql<br /><pre>" . print_r($values, 1) . "</pre>";
  192. \trace($this);
  193. die($errors[0]);
  194. return false;
  195. }
  196. }
  197. /*
  198. * Cargamos los datos del proyecto
  199. */
  200. public static function get($id, $lang = null) {
  201. try {
  202. // metemos los datos del proyecto en la instancia
  203. $query = self::query("SELECT * FROM project WHERE id = ?", array($id));
  204. $project = $query->fetchObject(__CLASS__);
  205. if (!$project instanceof \Goteo\Model\Project) {
  206. throw new \Goteo\Core\Error('404', Text::html('fatal-error-project'));
  207. }
  208. // si recibimos lang y no es el idioma original del proyecto, ponemos la traducción y mantenemos para el resto de contenido
  209. if ($lang == $project->lang) {
  210. $lang = null;
  211. } elseif (!empty($lang)) {
  212. $sql = "
  213. SELECT
  214. IFNULL(project_lang.description, project.description) as description,
  215. IFNULL(project_lang.motivation, project.motivation) as motivation,
  216. IFNULL(project_lang.video, project.video) as video,
  217. IFNULL(project_lang.about, project.about) as about,
  218. IFNULL(project_lang.goal, project.goal) as goal,
  219. IFNULL(project_lang.related, project.related) as related,
  220. IFNULL(project_lang.reward, project.reward) as reward,
  221. IFNULL(project_lang.keywords, project.keywords) as keywords,
  222. IFNULL(project_lang.media, project.media) as media,
  223. IFNULL(project_lang.subtitle, project.subtitle) as subtitle
  224. FROM project
  225. LEFT JOIN project_lang
  226. ON project_lang.id = project.id
  227. AND project_lang.lang = :lang
  228. WHERE project.id = :id
  229. ";
  230. $query = self::query($sql, array(':id'=>$id, ':lang'=>$lang));
  231. foreach ($query->fetch(\PDO::FETCH_ASSOC) as $field=>$value) {
  232. $project->$field = $value;
  233. }
  234. }
  235. if (isset($project->media)) {
  236. $project->media = new Project\Media($project->media);
  237. }
  238. if (isset($project->video)) {
  239. $project->video = new Project\Media($project->video);
  240. }
  241. // owner
  242. $project->user = User::get($project->owner, $lang);
  243. // galeria
  244. $project->gallery = Project\Image::getGallery($project->id);
  245. // imágenes por sección
  246. foreach (Project\Image::sections() as $sec => $val) {
  247. if ($sec != '') {
  248. $project->secGallery[$sec] = Project\Image::get($project->id, $sec);
  249. }
  250. }
  251. // categorias
  252. $project->categories = Project\Category::get($id);
  253. // costes y los sumammos
  254. $project->costs = Project\Cost::getAll($id, $lang);
  255. $project->minmax();
  256. // retornos colectivos
  257. $project->social_rewards = Project\Reward::getAll($id, 'social', $lang);
  258. // retornos individuales
  259. $project->individual_rewards = Project\Reward::getAll($id, 'individual', $lang);
  260. // colaboraciones
  261. $project->supports = Project\Support::getAll($id, $lang);
  262. //-----------------------------------------------------------------
  263. // Diferentes verificaciones segun el estado del proyecto
  264. //-----------------------------------------------------------------
  265. $project->investors = Invest::investors($id);
  266. $project->num_investors = Invest::numInvestors($id);
  267. $amount = Invest::invested($id);
  268. if ($project->invested != $amount) {
  269. self::query("UPDATE project SET amount = '{$amount}' WHERE id = ?", array($id));
  270. }
  271. $project->invested = $amount;
  272. $project->amount = $amount;
  273. //mensajes y mensajeros
  274. $messegers = array();
  275. $project->messages = Message::getAll($id, $lang);
  276. $project->num_messages = 0;
  277. foreach ($project->messages as $msg) {
  278. $project->num_messages++;
  279. $project->num_messages+=count($msg->responses);
  280. $messegers[$msg->user] = $msg->user;
  281. }
  282. $project->num_messegers = count($messegers);
  283. $project->setDays();
  284. $project->setTagmark();
  285. // fecha final primera ronda (fecha campaña + 40)
  286. if (!empty($project->published)) {
  287. $ptime = strtotime($project->published);
  288. $project->willpass = date('Y-m-d', \mktime(0, 0, 0, date('m', $ptime), date('d', $ptime)+40, date('Y', $ptime)));
  289. }
  290. //-----------------------------------------------------------------
  291. // Fin de verificaciones
  292. //-----------------------------------------------------------------
  293. return $project;
  294. } catch(\PDOException $e) {
  295. throw new \Goteo\Core\Exception($e->getMessage());
  296. } catch(\Goteo\Core\Error $e) {
  297. throw new \Goteo\Core\Error('404', Text::html('fatal-error-project'));
  298. }
  299. }
  300. /*
  301. * Cargamos los datos mínimos de un proyecto
  302. */
  303. public static function getMini($id) {
  304. try {
  305. // metemos los datos del proyecto en la instancia
  306. $query = self::query("SELECT id, name, owner, comment, lang, status FROM project WHERE id = ?", array($id));
  307. $project = $query->fetchObject(); // stdClass para qno grabar accidentalmente y machacar todo
  308. // owner
  309. $project->user = User::getMini($project->owner);
  310. return $project;
  311. } catch(\PDOException $e) {
  312. throw new \Goteo\Core\Exception($e->getMessage());
  313. }
  314. }
  315. /*
  316. * Cargamos los datos suficientes para pintar un widget de proyecto
  317. */
  318. public static function getMedium($id, $lang = \LANG) {
  319. try {
  320. // metemos los datos del proyecto en la instancia
  321. $query = self::query("SELECT * FROM project WHERE id = ?", array($id));
  322. $project = $query->fetchObject(__CLASS__);
  323. // primero, que no lo grabe
  324. $project->dontsave = true;
  325. // si recibimos lang y no es el idioma original del proyecto, ponemos la traducción y mantenemos para el resto de contenido
  326. if ($lang == $project->lang) {
  327. $lang = null;
  328. } elseif (!empty($lang)) {
  329. $sql = "
  330. SELECT
  331. IFNULL(project_lang.description, project.description) as description,
  332. IFNULL(project_lang.subtitle, project.subtitle) as subtitle
  333. FROM project
  334. LEFT JOIN project_lang
  335. ON project_lang.id = project.id
  336. AND project_lang.lang = :lang
  337. WHERE project.id = :id
  338. ";
  339. $query = self::query($sql, array(':id'=>$id, ':lang'=>$lang));
  340. foreach ($query->fetch(\PDO::FETCH_ASSOC) as $field=>$value) {
  341. $project->$field = $value;
  342. }
  343. }
  344. // owner
  345. $project->user = User::getMini($project->owner);
  346. // imagen
  347. $project->image = Project\Image::getFirst($project->id);
  348. // categorias
  349. $project->categories = Project\Category::getNames($id, 2);
  350. // retornos colectivos
  351. $project->social_rewards = Project\Reward::getAll($id, 'social', $lang);
  352. // retornos individuales
  353. $project->individual_rewards = Project\Reward::getAll($id, 'individual', $lang);
  354. $amount = Invest::invested($id);
  355. $project->invested = $amount;
  356. $project->amount = $amount;
  357. $project->num_investors = Invest::numInvestors($id);
  358. $project->num_messegers = Message::numMessegers($id);
  359. // sacamos rapidamente el presupuesto mínimo y óptimo
  360. $costs = self::calcCosts($id);
  361. $project->mincost = $costs->mincost;
  362. $project->maxcost = $costs->maxcost;
  363. $project->setDays();
  364. $project->setTagmark();
  365. return $project;
  366. } catch(\PDOException $e) {
  367. throw new \Goteo\Core\Exception($e->getMessage());
  368. }
  369. }
  370. /*
  371. * Listado simple de todos los proyectos
  372. */
  373. public static function getAll($node = \GOTEO_NODE) {
  374. $list = array();
  375. $query = static::query("
  376. SELECT
  377. project.id as id,
  378. project.name as name
  379. FROM project
  380. ORDER BY project.name ASC
  381. ", array(':node' => $node));
  382. foreach ($query->fetchAll(\PDO::FETCH_CLASS) as $item) {
  383. $list[$item->id] = $item->name;
  384. }
  385. return $list;
  386. }
  387. /*
  388. * Para calcular los dias y la ronda
  389. */
  390. private function setDays() {
  391. //para proyectos en campaña o posterior
  392. if ($this->status > 2) {
  393. // tiempo de campaña
  394. if ($this->status == 3) {
  395. $days = $this->daysActive();
  396. if ($days > 81) {
  397. $this->round = 0;
  398. $days = 0;
  399. } elseif ($days >= 40) {
  400. $days = 80 - $days;
  401. $this->round = 2;
  402. } else {
  403. $days = 40 - $days;
  404. $this->round = 1;
  405. }
  406. if ($days < 0) {
  407. // no deberia estar en campaña sino financuiado o caducado
  408. $days = 0;
  409. }
  410. } else {
  411. $this->round = 0;
  412. $days = 0;
  413. }
  414. } else {
  415. $days = 0;
  416. }
  417. if ($this->days != $days) {
  418. self::query("UPDATE project SET days = '{$days}' WHERE id = ?", array($this->id));
  419. }
  420. $this->days = $days;
  421. }
  422. /*
  423. * Para ver que tagmark le toca
  424. */
  425. private function setTagmark() {
  426. // a ver que banderolo le toca
  427. // "financiado" al final de de los 80 dias
  428. if ($this->status == 4) :
  429. $this->tagmark = 'gotit';
  430. // "en marcha" cuando llega al optimo en primera o segunda ronda
  431. elseif ($this->status == 3 && $this->amount >= $this->maxcost) :
  432. $this->tagmark = 'onrun';
  433. // "en marcha" y "aun puedes" cuando está en la segunda ronda
  434. elseif ($this->status == 3 && $this->round == 2) :
  435. $this->tagmark = 'onrun-keepiton';
  436. // Obtiene el mínimo durante la primera ronda, "aun puedes seguir aportando"
  437. elseif ($this->status == 3 && $this->round == 1 && $this->amount >= $this->mincost ) :
  438. $this->tagmark = 'keepiton';
  439. // tag de exitoso cuando es retorno cumplido
  440. elseif ($this->status == 5) :
  441. $this->tagmark = 'success';
  442. // tag de caducado
  443. elseif ($this->status == 6) :
  444. $this->tagmark = 'fail';
  445. endif;
  446. }
  447. /*
  448. * Para validar los campos del proyecto que son NOT NULL en la tabla
  449. */
  450. public function validate(&$errors = array()) {
  451. // Estos son errores que no permiten continuar
  452. if (empty($this->id))
  453. $errors[] = Text::_('El proyecto no tiene id');
  454. if (empty($this->lang))
  455. $this->lang = 'es';
  456. if (empty($this->status))
  457. $this->status = 1;
  458. if (empty($this->progress))
  459. $this->progress = 0;
  460. if (empty($this->owner))
  461. $errors[] = Text::_('El proyecto no tiene usuario creador');
  462. if (empty($this->node))
  463. $this->node = 'goteo';
  464. //cualquiera de estos errores hace fallar la validación
  465. if (!empty($errors))
  466. return false;
  467. else
  468. return true;
  469. }
  470. /**
  471. * actualiza en la tabla los datos del proyecto
  472. * @param array $project->errors para guardar los errores de datos del formulario, los errores de proceso se guardan en $project->errors['process']
  473. */
  474. public function save (&$errors = array()) {
  475. if ($this->dontsave) { return false; }
  476. if(!$this->validate($errors)) { return false; }
  477. try {
  478. // fail para pasar por todo antes de devolver false
  479. $fail = false;
  480. // los nif sin guiones, espacios ni puntos
  481. $this->contract_nif = str_replace(array('_', '.', ' ', '-', ',', ')', '('), '', $this->contract_nif);
  482. $this->entity_cif = str_replace(array('_', '.', ' ', '-', ',', ')', '('), '', $this->entity_cif);
  483. // Image
  484. if (is_array($this->image) && !empty($this->image['name'])) {
  485. $image = new Image($this->image);
  486. if ($image->save($errors)) {
  487. $this->gallery[] = $image;
  488. $this->image = $image->id;
  489. /**
  490. * Guarda la relación NM en la tabla 'project_image'.
  491. */
  492. if(!empty($image->id)) {
  493. self::query("REPLACE project_image (project, image) VALUES (:project, :image)", array(':project' => $this->id, ':image' => $image->id));
  494. }
  495. }
  496. }
  497. $fields = array(
  498. 'contract_name',
  499. 'contract_nif',
  500. 'contract_email',
  501. 'contract_entity',
  502. 'contract_birthdate',
  503. 'entity_office',
  504. 'entity_name',
  505. 'entity_cif',
  506. 'phone',
  507. 'address',
  508. 'zipcode',
  509. 'location',
  510. 'country',
  511. 'secondary_address',
  512. 'post_address',
  513. 'post_zipcode',
  514. 'post_location',
  515. 'post_country',
  516. 'name',
  517. 'subtitle',
  518. 'image',
  519. 'description',
  520. 'motivation',
  521. 'video',
  522. 'video_usubs',
  523. 'about',
  524. 'goal',
  525. 'related',
  526. 'reward',
  527. 'keywords',
  528. 'media',
  529. 'media_usubs',
  530. 'currently',
  531. 'project_location',
  532. 'scope',
  533. 'resource',
  534. 'comment'
  535. );
  536. $set = '';
  537. $values = array();
  538. foreach ($fields as $field) {
  539. if ($set != '') $set .= ', ';
  540. $set .= "$field = :$field";
  541. $values[":$field"] = $this->$field;
  542. }
  543. // Solamente marcamos updated cuando se envia a revision desde el superform o el admin
  544. // $set .= ", updated = :updated";
  545. // $values[':updated'] = date('Y-m-d');
  546. $values[':id'] = $this->id;
  547. $sql = "UPDATE project SET " . $set . " WHERE id = :id";
  548. if (!self::query($sql, $values)) {
  549. $errors[] = $sql . '<pre>' . print_r($values, 1) . '</pre>';
  550. $fail = true;
  551. }
  552. // echo "$sql<br />";
  553. // y aquí todas las tablas relacionadas
  554. // cada una con sus save, sus new y sus remove
  555. // quitar las que tiene y no vienen
  556. // añadir las que vienen y no tiene
  557. //categorias
  558. $tiene = Project\Category::get($this->id);
  559. $viene = $this->categories;
  560. $quita = array_diff_assoc($tiene, $viene);
  561. $guarda = array_diff_assoc($viene, $tiene);
  562. foreach ($quita as $key=>$item) {
  563. $category = new Project\Category(
  564. array(
  565. 'id'=>$item,
  566. 'project'=>$this->id)
  567. );
  568. if (!$category->remove($errors))
  569. $fail = true;
  570. }
  571. foreach ($guarda as $key=>$item) {
  572. if (!$item->save($errors))
  573. $fail = true;
  574. }
  575. // recuperamos las que le quedan si ha cambiado alguna
  576. if (!empty($quita) || !empty($guarda))
  577. $this->categories = Project\Category::get($this->id);
  578. //costes
  579. $tiene = Project\Cost::getAll($this->id);
  580. $viene = $this->costs;
  581. $quita = array_diff_key($tiene, $viene);
  582. $guarda = array_diff_key($viene, $tiene);
  583. foreach ($quita as $key=>$item) {
  584. if (!$item->remove($errors)) {
  585. $fail = true;
  586. } else {
  587. unset($tiene[$key]);
  588. }
  589. }
  590. foreach ($guarda as $key=>$item) {
  591. if (!$item->save($errors))
  592. $fail = true;
  593. }
  594. /* Ahora, los que tiene y vienen. Si el contenido es diferente, hay que guardarlo*/
  595. foreach ($tiene as $key => $row) {
  596. // a ver la diferencia con el que viene
  597. if ($row != $viene[$key]) {
  598. if (!$viene[$key]->save($errors))
  599. $fail = true;
  600. }
  601. }
  602. if (!empty($quita) || !empty($guarda))
  603. $this->costs = Project\Cost::getAll($this->id);
  604. // recalculo de minmax
  605. $this->minmax();
  606. //retornos colectivos
  607. $tiene = Project\Reward::getAll($this->id, 'social');
  608. $viene = $this->social_rewards;
  609. $quita = array_diff_key($tiene, $viene);
  610. $guarda = array_diff_key($viene, $tiene);
  611. foreach ($quita as $key=>$item) {
  612. if (!$item->remove($errors)) {
  613. $fail = true;
  614. } else {
  615. unset($tiene[$key]);
  616. }
  617. }
  618. foreach ($guarda as $key=>$item) {
  619. if (!$item->save($errors))
  620. $fail = true;
  621. }
  622. /* Ahora, los que tiene y vienen. Si el contenido es diferente, hay que guardarlo*/
  623. foreach ($tiene as $key => $row) {
  624. // a ver la diferencia con el que viene
  625. if ($row != $viene[$key]) {
  626. if (!$viene[$key]->save($errors))
  627. $fail = true;
  628. }
  629. }
  630. if (!empty($quita) || !empty($guarda))
  631. $this->social_rewards = Project\Reward::getAll($this->id, 'social');
  632. //recompenssas individuales
  633. $tiene = Project\Reward::getAll($this->id, 'individual');
  634. $viene = $this->individual_rewards;
  635. $quita = array_diff_key($tiene, $viene);
  636. $guarda = array_diff_key($viene, $tiene);
  637. foreach ($quita as $key=>$item) {
  638. if (!$item->remove($errors)) {
  639. $fail = true;
  640. } else {
  641. unset($tiene[$key]);
  642. }
  643. }
  644. foreach ($guarda as $key=>$item) {
  645. if (!$item->save($errors))
  646. $fail = true;
  647. }
  648. /* Ahora, los que tiene y vienen. Si el contenido es diferente, hay que guardarlo*/
  649. foreach ($tiene as $key => $row) {
  650. // a ver la diferencia con el que viene
  651. if ($row != $viene[$key]) {
  652. if (!$viene[$key]->save($errors))
  653. $fail = true;
  654. }
  655. }
  656. if (!empty($quita) || !empty($guarda))
  657. $this->individual_rewards = Project\Reward::getAll($this->id, 'individual');
  658. // colaboraciones
  659. $tiene = Project\Support::getAll($this->id);
  660. $viene = $this->supports;
  661. $quita = array_diff_key($tiene, $viene); // quitar los que tiene y no viene
  662. $guarda = array_diff_key($viene, $tiene); // añadir los que viene y no tiene
  663. foreach ($quita as $key=>$item) {
  664. if (!$item->remove($errors)) {
  665. $fail = true;
  666. } else {
  667. unset($tiene[$key]);
  668. }
  669. }
  670. foreach ($guarda as $key=>$item) {
  671. if (!$item->save($errors))
  672. $fail = true;
  673. }
  674. /* Ahora, los que tiene y vienen. Si el contenido es diferente, hay que guardarlo*/
  675. foreach ($tiene as $key => $row) {
  676. // a ver la diferencia con el que viene
  677. if ($row != $viene[$key]) {
  678. if (!$viene[$key]->save($errors))
  679. $fail = true;
  680. }
  681. }
  682. if (!empty($quita) || !empty($guarda))
  683. $this->supports = Project\Support::getAll($this->id);
  684. //listo
  685. return !$fail;
  686. } catch(\PDOException $e) {
  687. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  688. //Text::get('save-project-fail');
  689. return false;
  690. }
  691. }
  692. public function saveLang (&$errors = array()) {
  693. try {
  694. $fields = array(
  695. 'id'=>'id',
  696. 'lang'=>'lang_lang',
  697. 'subtitle'=>'subtitle_lang',
  698. 'description'=>'description_lang',
  699. 'motivation'=>'motivation_lang',
  700. 'video'=>'video_lang',
  701. 'about'=>'about_lang',
  702. 'goal'=>'goal_lang',
  703. 'related'=>'related_lang',
  704. 'reward'=>'reward_lang',
  705. 'keywords'=>'keywords_lang',
  706. 'media'=>'media_lang'
  707. );
  708. $set = '';
  709. $values = array();
  710. foreach ($fields as $field=>$ffield) {
  711. if ($set != '') $set .= ', ';
  712. $set .= "$field = :$field";
  713. if (empty($this->$ffield)) {
  714. $this->$ffield = null;
  715. }
  716. $values[":$field"] = $this->$ffield;
  717. }
  718. $sql = "REPLACE INTO project_lang SET " . $set;
  719. if (self::query($sql, $values)) {
  720. return true;
  721. } else {
  722. $errors[] = $sql . '<pre>' . print_r($values, 1) . '</pre>';
  723. return false;
  724. }
  725. } catch(\PDOException $e) {
  726. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  727. return false;
  728. }
  729. }
  730. /*
  731. * comprueba errores de datos y actualiza la puntuación
  732. */
  733. public function check() {
  734. $errors = &$this->errors;
  735. $okeys = &$this->okeys ;
  736. // reseteamos la puntuación
  737. $this->setScore(0, 0, true);
  738. /***************** Revisión de campos del paso 1, PERFIL *****************/
  739. $score = 0;
  740. // obligatorios: nombre, email, ciudad
  741. if (empty($this->user->name)) {
  742. $errors['userProfile']['name'] = Text::get('validate-user-field-name');
  743. } else {
  744. $okeys['userProfile']['name'] = 'ok';
  745. ++$score;
  746. }
  747. // se supone que tiene email porque sino no puede tener usuario, no?
  748. if (!empty($this->user->email)) {
  749. ++$score;
  750. }
  751. if (empty($this->user->location)) {
  752. $errors['userProfile']['location'] = Text::get('validate-user-field-location');
  753. } else {
  754. $okeys['userProfile']['location'] = 'ok';
  755. ++$score;
  756. }
  757. if(!empty($this->user->avatar) && $this->user->avatar->id != 1) {
  758. $okeys['userProfile']['avatar'] = empty($errors['userProfile']['avatar']) ? 'ok' : null;
  759. $score+=2;
  760. }
  761. if (!empty($this->user->about)) {
  762. $okeys['userProfile']['about'] = 'ok';
  763. ++$score;
  764. // otro +1 si tiene más de 1000 caracteres (pero menos de 2000)
  765. if (\strlen($this->user->about) > 1000 && \strlen($this->user->about) < 2000) {
  766. ++$score;
  767. }
  768. } else {
  769. $errors['userProfile']['about'] = Text::get('validate-user-field-about');
  770. }
  771. if (!empty($this->user->interests)) {
  772. $okeys['userProfile']['interests'] = 'ok';
  773. ++$score;
  774. }
  775. /* Aligerando superform
  776. if (!empty($this->user->keywords)) {
  777. $okeys['userProfile']['keywords'] = 'ok';
  778. ++$score;
  779. }
  780. if (!empty($this->user->contribution)) {
  781. $okeys['userProfile']['contribution'] = 'ok';
  782. ++$score;
  783. }
  784. */
  785. if (empty($this->user->webs)) {
  786. $errors['userProfile']['webs'] = Text::get('validate-project-userProfile-web');
  787. } else {
  788. $okeys['userProfile']['webs'] = 'ok';
  789. ++$score;
  790. if (count($this->user->webs) > 2) ++$score;
  791. $anyerror = false;
  792. foreach ($this->user->webs as $web) {
  793. if (trim(str_replace('http://','',$web->url)) == '') {
  794. $anyerror = !$anyerror ?: true;
  795. $errors['userProfile']['web-'.$web->id.'-url'] = Text::get('validate-user-field-web');
  796. } else {
  797. $okeys['userProfile']['web-'.$web->id.'-url'] = 'ok';
  798. }
  799. }
  800. if ($anyerror) {
  801. unset($okeys['userProfile']['webs']);
  802. $errors['userProfile']['webs'] = Text::get('validate-project-userProfile-any_error');
  803. }
  804. }
  805. if (!empty($this->user->facebook)) {
  806. $okeys['userProfile']['facebook'] = 'ok';
  807. ++$score;
  808. }
  809. if (!empty($this->user->twitter)) {
  810. $okeys['userProfile']['twitter'] = 'ok';
  811. ++$score;
  812. }
  813. if (!empty($this->user->linkedin)) {
  814. $okeys['userProfile']['linkedin'] = 'ok';
  815. }
  816. //puntos
  817. $this->setScore($score, 12);
  818. /***************** FIN Revisión del paso 1, PERFIL *****************/
  819. /***************** Revisión de campos del paso 2,DATOS PERSONALES *****************/
  820. $score = 0;
  821. // obligatorios: todos
  822. if (empty($this->contract_name)) {
  823. $errors['userPersonal']['contract_name'] = Text::get('mandatory-project-field-contract_name');
  824. } else {
  825. $okeys['userPersonal']['contract_name'] = 'ok';
  826. ++$score;
  827. }
  828. if (empty($this->contract_nif)) {
  829. $errors['userPersonal']['contract_nif'] = Text::get('mandatory-project-field-contract_nif');
  830. } elseif (!Check::nif($this->contract_nif) && !Check::vat($this->contract_nif)) {
  831. $errors['userPersonal']['contract_nif'] = Text::get('validate-project-value-contract_nif');
  832. } else {
  833. $okeys['userPersonal']['contract_nif'] = 'ok';
  834. ++$score;
  835. }
  836. if (empty($this->contract_email)) {
  837. $errors['userPersonal']['contract_email'] = Text::get('mandatory-project-field-contract_email');
  838. } elseif (!Check::mail($this->contract_email)) {
  839. $errors['userPersonal']['contract_email'] = Text::get('validate-project-value-contract_email');
  840. } else {
  841. $okeys['userPersonal']['contract_email'] = 'ok';
  842. }
  843. if (empty($this->contract_birthdate)) {
  844. $errors['userPersonal']['contract_birthdate'] = Text::get('mandatory-project-field-contract_birthdate');
  845. } else {
  846. $okeys['userPersonal']['contract_birthdate'] = 'ok';
  847. }
  848. if (empty($this->phone)) {
  849. $errors['userPersonal']['phone'] = Text::get('mandatory-project-field-phone');
  850. } elseif (!Check::phone($this->phone)) {
  851. $errors['userPersonal']['phone'] = Text::get('validate-project-value-phone');
  852. } else {
  853. $okeys['userPersonal']['phone'] = 'ok';
  854. ++$score;
  855. }
  856. if (empty($this->address)) {
  857. $errors['userPersonal']['address'] = Text::get('mandatory-project-field-address');
  858. } else {
  859. $okeys['userPersonal']['address'] = 'ok';
  860. ++$score;
  861. }
  862. if (empty($this->zipcode)) {
  863. $errors['userPersonal']['zipcode'] = Text::get('mandatory-project-field-zipcode');
  864. } else {
  865. $okeys['userPersonal']['zipcode'] = 'ok';
  866. ++$score;
  867. }
  868. if (empty($this->location)) {
  869. $errors['userPersonal']['location'] = Text::get('mandatory-project-field-residence');
  870. } else {
  871. $okeys['userPersonal']['location'] = 'ok';
  872. }
  873. if (empty($this->country)) {
  874. $errors['userPersonal']['country'] = Text::get('mandatory-project-field-country');
  875. } else {
  876. $okeys['userPersonal']['country'] = 'ok';
  877. ++$score;
  878. }
  879. $this->setScore($score, 6);
  880. /***************** FIN Revisión del paso 2, DATOS PERSONALES *****************/
  881. /***************** Revisión de campos del paso 3, DESCRIPCION *****************/
  882. $score = 0;
  883. // obligatorios: nombre, subtitulo, imagen, descripcion, about, motivation, categorias, video, localización
  884. if (empty($this->name)) {
  885. $errors['overview']['name'] = Text::get('mandatory-project-field-name');
  886. } else {
  887. $okeys['overview']['name'] = 'ok';
  888. ++$score;
  889. }
  890. if (!empty($this->subtitle)) {
  891. $okeys['overview']['subtitle'] = 'ok';
  892. }
  893. if (empty($this->gallery) && empty($errors['overview']['image'])) {
  894. $errors['overview']['image'] .= Text::get('mandatory-project-field-image');
  895. } else {
  896. $okeys['overview']['image'] = (empty($errors['overview']['image'])) ? 'ok' : null;
  897. ++$score;
  898. if (count($this->gallery) >= 2) ++$score;
  899. }
  900. if (empty($this->description)) {
  901. $errors['overview']['description'] = Text::get('mandatory-project-field-description');
  902. } elseif (!Check::words($this->description, 80)) {
  903. $errors['overview']['description'] = Text::get('validate-project-field-description');
  904. } else {
  905. $okeys['overview']['description'] = 'ok';
  906. ++$score;
  907. }
  908. if (empty($this->about)) {
  909. $errors['overview']['about'] = Text::get('mandatory-project-field-about');
  910. } else {
  911. $okeys['overview']['about'] = 'ok';
  912. ++$score;
  913. }
  914. if (empty($this->motivation)) {
  915. $errors['overview']['motivation'] = Text::get('mandatory-project-field-motivation');
  916. } else {
  917. $okeys['overview']['motivation'] = 'ok';
  918. ++$score;
  919. }
  920. if (!empty($this->goal)) {
  921. $okeys['overview']['goal'] = 'ok';
  922. ++$score;
  923. }
  924. if (!empty($this->related)) {
  925. $okeys['overview']['related'] = 'ok';
  926. ++$score;
  927. }
  928. if (empty($this->categories)) {
  929. $errors['overview']['categories'] = Text::get('mandatory-project-field-category');
  930. } else {
  931. $okeys['overview']['categories'] = 'ok';
  932. ++$score;
  933. }
  934. if (empty($this->media)) {
  935. $errors['overview']['media'] = Text::get('mandatory-project-field-media');
  936. } else {
  937. $okeys['overview']['media'] = 'ok';
  938. $score+=3;
  939. }
  940. if (empty($this->project_location)) {
  941. $errors['overview']['project_location'] = Text::get('mandatory-project-field-location');
  942. } else {
  943. $okeys['overview']['project_location'] = 'ok';
  944. ++$score;
  945. }
  946. $this->setScore($score, 13);
  947. /***************** FIN Revisión del paso 3, DESCRIPCION *****************/
  948. /***************** Revisión de campos del paso 4, COSTES *****************/
  949. $score = 0; $scoreName = $scoreDesc = $scoreAmount = $scoreDate = 0;
  950. if (count($this->costs) < 2) {
  951. $errors['costs']['costs'] = Text::get('mandatory-project-costs');
  952. } else {
  953. $okeys['costs']['costs'] = 'ok';
  954. ++$score;
  955. }
  956. $anyerror = false;
  957. foreach($this->costs as $cost) {
  958. if (empty($cost->cost)) {
  959. $errors['costs']['cost-'.$cost->id.'-cost'] = Text::get('mandatory-cost-field-name');
  960. $anyerror = !$anyerror ?: true;
  961. } else {
  962. $okeys['costs']['cost-'.$cost->id.'-cost'] = 'ok';
  963. $scoreName = 1;
  964. }
  965. if (empty($cost->type)) {
  966. $errors['costs']['cost-'.$cost->id.'-type'] = Text::get('mandatory-cost-field-type');
  967. $anyerror = !$anyerror ?: true;
  968. } else {
  969. $okeys['costs']['cost-'.$cost->id.'-type'] = 'ok';
  970. }
  971. if (empty($cost->description)) {
  972. $errors['costs']['cost-'.$cost->id.'-description'] = Text::get('mandatory-cost-field-description');
  973. $anyerror = !$anyerror ?: true;
  974. } else {
  975. $okeys['costs']['cost-'.$cost->id.'-description'] = 'ok';
  976. $scoreDesc = 1;
  977. }
  978. if (empty($cost->amount)) {
  979. $errors['costs']['cost-'.$cost->id.'-amount'] = Text::get('mandatory-cost-field-amount');
  980. $anyerror = !$anyerror ?: true;
  981. } else {
  982. $okeys['costs']['cost-'.$cost->id.'-amount'] = 'ok';
  983. $scoreAmount = 1;
  984. }
  985. if ($cost->type == 'task' && (empty($cost->from) || empty($cost->until))) {
  986. $errors['costs']['cost-'.$cost->id.'-dates'] = Text::get('mandatory-cost-field-task_dates');
  987. $anyerror = !$anyerror ?: true;
  988. } elseif ($cost->type == 'task') {
  989. $okeys['costs']['cost-'.$cost->id.'-dates'] = 'ok';
  990. $scoreDate = 1;
  991. }
  992. }
  993. if ($anyerror) {
  994. unset($okeys['costs']['costs']);
  995. $errors['costs']['costs'] = Text::get('validate-project-costs-any_error');
  996. }
  997. $score = $score + $scoreName + $scoreDesc + $scoreAmount + $scoreDate;
  998. $costdif = $this->maxcost - $this->mincost;
  999. $maxdif = $this->mincost * 0.50;
  1000. $scoredif = $this->mincost * 0.35;
  1001. if ($this->mincost == 0) {
  1002. $errors['costs']['total-costs'] = Text::get('mandatory-project-total-costs');
  1003. } elseif ($costdif > $maxdif ) {
  1004. $errors['costs']['total-costs'] = Text::get('validate-project-total-costs');
  1005. } else {
  1006. $okeys['costs']['total-costs'] = 'ok';
  1007. }
  1008. if ($costdif <= $scoredif ) {
  1009. ++$score;
  1010. }
  1011. $this->setScore($score, 6);
  1012. /***************** FIN Revisión del paso 4, COSTES *****************/
  1013. /***************** Revisión de campos del paso 5, RETORNOS *****************/
  1014. $score = 0; $scoreName = $scoreDesc = $scoreAmount = $scoreLicense = 0;
  1015. if (empty($this->social_rewards)) {
  1016. $errors['rewards']['social_rewards'] = Text::get('validate-project-social_rewards');
  1017. } else {
  1018. $okeys['rewards']['social_rewards'] = 'ok';
  1019. if (count($this->social_rewards) >= 2) {
  1020. ++$score;
  1021. }
  1022. }
  1023. if (empty($this->individual_rewards)) {
  1024. $errors['rewards']['individual_rewards'] = Text::get('validate-project-individual_rewards');
  1025. } else {
  1026. $okeys['rewards']['individual_rewards'] = 'ok';
  1027. if (count($this->individual_rewards) >= 3) {
  1028. ++$score;
  1029. }
  1030. }
  1031. $anyerror = false;
  1032. foreach ($this->social_rewards as $social) {
  1033. if (empty($social->reward)) {
  1034. $errors['rewards']['social_reward-'.$social->id.'reward'] = Text::get('mandatory-social_reward-field-name');
  1035. $anyerror = !$anyerror ?: true;
  1036. } else {
  1037. $okeys['rewards']['social_reward-'.$social->id.'reward'] = 'ok';
  1038. $scoreName = 1;
  1039. }
  1040. if (empty($social->description)) {
  1041. $errors['rewards']['social_reward-'.$social->id.'-description'] = Text::get('mandatory-social_reward-field-description');
  1042. $anyerror = !$anyerror ?: true;
  1043. } else {
  1044. $okeys['rewards']['social_reward-'.$social->id.'-description'] = 'ok';
  1045. $scoreDesc = 1;
  1046. }
  1047. if (empty($social->icon)) {
  1048. $errors['rewards']['social_reward-'.$social->id.'-icon'] = Text::get('mandatory-social_reward-field-icon');
  1049. $anyerror = !$anyerror ?: true;
  1050. } else {
  1051. $okeys['rewards']['social_reward-'.$social->id.'-icon'] = 'ok';
  1052. }
  1053. if (!empty($social->license)) {
  1054. $scoreLicense = 1;
  1055. }
  1056. }
  1057. if ($anyerror) {
  1058. unset($okeys['rewards']['social_rewards']);
  1059. $errors['rewards']['social_rewards'] = Text::get('validate-project-social_rewards-any_error');
  1060. }
  1061. $score = $score + $scoreName + $scoreDesc + $scoreLicense;
  1062. $scoreName = $scoreDesc = $scoreAmount = 0;
  1063. $anyerror = false;
  1064. foreach ($this->individual_rewards as $individual) {
  1065. if (empty($individual->reward)) {
  1066. $errors['rewards']['individual_reward-'.$individual->id.'-reward'] = Text::get('mandatory-individual_reward-field-name');
  1067. $anyerror = !$anyerror ?: true;
  1068. } else {
  1069. $okeys['rewards']['individual_reward-'.$individual->id.'-reward'] = 'ok';
  1070. $scoreName = 1;
  1071. }
  1072. if (empty($individual->description)) {
  1073. $errors['rewards']['individual_reward-'.$individual->id.'-description'] = Text::get('mandatory-individual_reward-field-description');
  1074. $anyerror = !$anyerror ?: true;
  1075. } else {
  1076. $okeys['rewards']['individual_reward-'.$individual->id.'-description'] = 'ok';
  1077. $scoreDesc = 1;
  1078. }
  1079. if (empty($individual->amount)) {
  1080. $errors['rewards']['individual_reward-'.$individual->id.'-amount'] = Text::get('mandatory-individual_reward-field-amount');
  1081. $anyerror = !$anyerror ?: true;
  1082. } else {
  1083. $okeys['rewards']['individual_reward-'.$individual->id.'-amount'] = 'ok';
  1084. $scoreAmount = 1;
  1085. }
  1086. if (empty($individual->icon)) {
  1087. $errors['rewards']['individual_reward-'.$individual->id.'-icon'] = Text::get('mandatory-individual_reward-field-icon');
  1088. $anyerror = !$anyerror ?: true;
  1089. } else {
  1090. $okeys['rewards']['individual_reward-'.$individual->id.'-icon'] = 'ok';
  1091. }
  1092. }
  1093. if ($anyerror) {
  1094. unset($okeys['rewards']['individual_rewards']);
  1095. $errors['rewards']['individual_rewards'] = Text::get('validate-project-individual_rewards-any_error');
  1096. }
  1097. $score = $score + $scoreName + $scoreDesc + $scoreAmount;
  1098. $this->setScore($score, 8);
  1099. /***************** FIN Revisión del paso 5, RETORNOS *****************/
  1100. /***************** Revisión de campos del paso 6, COLABORACIONES *****************/
  1101. $scorename = $scoreDesc = 0;
  1102. foreach ($this->supports as $support) {
  1103. if (!empty($support->support)) {
  1104. $okeys['supports']['support-'.$support->id.'-support'] = 'ok';
  1105. $scoreName = 1;
  1106. }
  1107. if (!empty($support->description)) {
  1108. $okeys['supports']['support-'.$support->id.'-description'] = 'ok';
  1109. $scoreDesc = 1;
  1110. }
  1111. }
  1112. $score = $scoreName + $scoreDesc;
  1113. $this->setScore($score, 2);
  1114. /***************** FIN Revisión del paso 6, COLABORACIONES *****************/
  1115. //-------------- Calculo progreso ---------------------//
  1116. $this->setProgress();
  1117. //-------------- Fin calculo progreso ---------------------//
  1118. return true;
  1119. }
  1120. /*
  1121. * reset de puntuación
  1122. */
  1123. public function setScore($score, $max, $reset = false) {
  1124. if ($reset == true) {
  1125. $this->score = $score;
  1126. $this->max = $max;
  1127. } else {
  1128. $this->score += $score;
  1129. $this->max += $max;
  1130. }
  1131. }
  1132. /*
  1133. * actualizar progreso segun score y max
  1134. */
  1135. public function setProgress () {
  1136. // Cálculo del % de progreso
  1137. $progress = 100 * $this->score / $this->max;
  1138. $progress = round($progress, 0);
  1139. if ($progress > 100) $progress = 100;
  1140. if ($progress < 0) $progress = 0;
  1141. if ($this->status == 1 &&
  1142. $progress >= 80 &&
  1143. \array_empty($this->errors)
  1144. ) {
  1145. $this->finishable = true;
  1146. }
  1147. $this->progress = $progress;
  1148. // actualizar el registro
  1149. self::query("UPDATE project SET progress = :progress WHERE id = :id",
  1150. array(':progress'=>$this->progress, ':id'=>$this->id));
  1151. }
  1152. /*
  1153. * Listo para revisión
  1154. */
  1155. public function ready(&$errors = array()) {
  1156. try {
  1157. $this->rebase();
  1158. $sql = "UPDATE project SET status = :status, updated = :updated WHERE id = :id";
  1159. self::query($sql, array(':status'=>2, ':updated'=>date('Y-m-d'), ':id'=>$this->id));
  1160. return true;
  1161. } catch (\PDOException $e) {
  1162. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1163. return false;
  1164. }
  1165. }
  1166. /*
  1167. * Devuelto al estado de edición
  1168. */
  1169. public function enable(&$errors = array()) {
  1170. try {
  1171. $sql = "UPDATE project SET status = :status WHERE id = :id";
  1172. self::query($sql, array(':status'=>1, ':id'=>$this->id));
  1173. return true;
  1174. } catch (\PDOException $e) {
  1175. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1176. return false;
  1177. }
  1178. }
  1179. /*
  1180. * Cambio a estado de publicación
  1181. */
  1182. public function publish(&$errors = array()) {
  1183. try {
  1184. $sql = "UPDATE project SET passed = NULL, status = :status, published = :published WHERE id = :id";
  1185. self::query($sql, array(':status'=>3, ':published'=>date('Y-m-d'), ':id'=>$this->id));
  1186. // borramos mensajes anteriores que sean de colaboraciones
  1187. self::query("DELETE FROM message WHERE id IN (SELECT thread FROM support WHERE project = ?)", array($this->id));
  1188. // creamos los hilos de colaboración en los mensajes
  1189. foreach ($this->supports as $id => $support) {
  1190. $msg = new Message(array(
  1191. 'user' => $this->owner,
  1192. 'project' => $this->id,
  1193. 'date' => date('Y-m-d'),
  1194. 'message' => "{$support->support}: {$support->description}",
  1195. 'blocked' => true
  1196. ));
  1197. if ($msg->save()) {
  1198. // asignado a la colaboracion como thread inicial
  1199. $sql = "UPDATE support SET thread = :message WHERE id = :support";
  1200. self::query($sql, array(':message'=>$msg->id, ':support'=>$support->id));
  1201. }
  1202. unset($msg);
  1203. }
  1204. return true;
  1205. } catch (\PDOException $e) {
  1206. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1207. return false;
  1208. }
  1209. }
  1210. /*
  1211. * Cambio a estado canecelado
  1212. */
  1213. public function cancel(&$errors = array()) {
  1214. try {
  1215. $sql = "UPDATE project SET status = :status, closed = :closed WHERE id = :id";
  1216. self::query($sql, array(':status'=>0, ':closed'=>date('Y-m-d'), ':id'=>$this->id));
  1217. return true;
  1218. } catch (\PDOException $e) {
  1219. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1220. return false;
  1221. }
  1222. }
  1223. /*
  1224. * Cambio a estado caducado
  1225. */
  1226. public function fail(&$errors = array()) {
  1227. try {
  1228. $sql = "UPDATE project SET status = :status, closed = :closed WHERE id = :id";
  1229. self::query($sql, array(':status'=>6, ':closed'=>date('Y-m-d'), ':id'=>$this->id));
  1230. return true;
  1231. } catch (\PDOException $e) {
  1232. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1233. return false;
  1234. }
  1235. }
  1236. /*
  1237. * Cambio a estado Financiado
  1238. */
  1239. public function succeed(&$errors = array()) {
  1240. try {
  1241. $sql = "UPDATE project SET status = :status, success = :success WHERE id = :id";
  1242. self::query($sql, array(':status'=>4, ':success'=>date('Y-m-d'), ':id'=>$this->id));
  1243. return true;
  1244. } catch (\PDOException $e) {
  1245. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1246. return false;
  1247. }
  1248. }
  1249. /*
  1250. * Marcamos la fecha del paso a segunda ronda
  1251. */
  1252. public function passed(&$errors = array()) {
  1253. try {
  1254. $sql = "UPDATE project SET passed = :passed WHERE id = :id";
  1255. self::query($sql, array(':passed'=>date('Y-m-d'), ':id'=>$this->id));
  1256. return true;
  1257. } catch (\PDOException $e) {
  1258. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1259. return false;
  1260. }
  1261. }
  1262. /*
  1263. * Cambio a estado Retorno cumplido
  1264. */
  1265. public function satisfied(&$errors = array()) {
  1266. try {
  1267. $sql = "UPDATE project SET status = :status WHERE id = :id";
  1268. self::query($sql, array(':status'=>5, ':id'=>$this->id));
  1269. return true;
  1270. } catch (\PDOException $e) {
  1271. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1272. return false;
  1273. }
  1274. }
  1275. /*
  1276. * Devuelve a estado financiado (por retorno pendiente) pero no modifica fecha
  1277. */
  1278. public function rollback(&$errors = array()) {
  1279. try {
  1280. $sql = "UPDATE project SET status = :status WHERE id = :id";
  1281. self::query($sql, array(':status'=>4, ':id'=>$this->id));
  1282. return true;
  1283. } catch (\PDOException $e) {
  1284. $errors[] = Text::_('No se ha grabado correctamente. ') . $e->getMessage();
  1285. return false;
  1286. }
  1287. }
  1288. /*
  1289. * Si no se pueden borrar todos los registros, estado cero para que lo borre el cron
  1290. */
  1291. public function delete(&$errors = array()) {
  1292. if ($this->status > 1) {
  1293. $errors[] = Text::_("El proyecto no esta descartado ni en edicion");
  1294. return false;
  1295. }
  1296. self::query("START TRANSACTION");
  1297. try {
  1298. //borrar todos los registros
  1299. self::query("DELETE FROM project_category WHERE project = ?", array($this->id));
  1300. self::query("DELETE FROM cost WHERE project = ?", array($this->id));
  1301. self::query("DELETE FROM reward WHERE project = ?", array($this->id));
  1302. self::query("DELETE FROM support WHERE project = ?", array($this->id));
  1303. self::query("DELETE FROM image WHERE id IN (SELECT image FROM project_image WHERE project = ?)", array($this->id));
  1304. self::query("DELETE FROM project_image WHERE project = ?", array($this->id));
  1305. self::query("DELETE FROM message WHERE project = ?", array($this->id));
  1306. self::query("DELETE FROM project_account WHERE project = ?", array($this->id));
  1307. self::query("DELETE FROM review WHERE project = ?", array($this->id));
  1308. self::query("DELETE FROM project_lang WHERE id = ?", array($this->id));
  1309. self::query("DELETE FROM project WHERE id = ?", array($this->id));
  1310. // y los permisos
  1311. self::query("DELETE FROM acl WHERE url like ?", array('%'.$this->id.'%'));
  1312. // si todo va bien, commit y cambio el id de la instancia
  1313. self::query("COMMIT");
  1314. return true;
  1315. } catch (\PDOException $e) {
  1316. self::query("ROLLBACK");
  1317. $sql = "UPDATE project SET status = :status WHERE id = :id";
  1318. self::query($sql, array(':status'=>0, ':id'=>$this->id));
  1319. $errors[] = "Fallo en la transaccion, el proyecto ha quedado como descartado";
  1320. return false;
  1321. }
  1322. }
  1323. /*
  1324. * Para cambiar el id temporal a idealiza
  1325. * solo si es md5
  1326. */
  1327. public function rebase($newid = null) {
  1328. try {
  1329. if (preg_match('/^[A-Fa-f0-9]{32}$/',$this->id)) {
  1330. // idealizar el nombre
  1331. $newid = self::checkId(self::idealiza($this->name));
  1332. if ($newid == false) return false;
  1333. // actualizar las tablas relacionadas en una transacción
  1334. $fail = false;
  1335. if (self::query("START TRANSACTION")) {
  1336. try {
  1337. self::query("UPDATE project_category SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1338. self::query("UPDATE cost SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1339. self::query("UPDATE reward SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1340. self::query("UPDATE support SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1341. self::query("UPDATE message SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1342. self::query("UPDATE project_image SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1343. self::query("UPDATE project_account SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1344. self::query("UPDATE invest SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1345. self::query("UPDATE review SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1346. self::query("UPDATE project_lang SET id = :newid WHERE id = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1347. self::query("UPDATE blog SET owner = :newid WHERE owner = :id AND type='project'", array(':newid'=>$newid, ':id'=>$this->id));
  1348. self::query("UPDATE project SET id = :newid WHERE id = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1349. // borro los permisos, el dashboard los creará de nuevo
  1350. self::query("DELETE FROM acl WHERE url like ?", array('%'.$this->id.'%'));
  1351. // si todo va bien, commit y cambio el id de la instancia
  1352. self::query("COMMIT");
  1353. $this->id = $newid;
  1354. return true;
  1355. } catch (\PDOException $e) {
  1356. self::query("ROLLBACK");
  1357. return false;
  1358. }
  1359. } else {
  1360. throw new \Goteo\Core\Exception('Fallo al iniciar transaccion rebase. ' . \trace($e));
  1361. }
  1362. } elseif (!empty ($newid)) {
  1363. // echo "Cambiando id proyecto: de {$this->id} a {$newid}<br /><br />";
  1364. $fail = false;
  1365. if (self::query("START TRANSACTION")) {
  1366. try {
  1367. // echo 'en transaccion <br />';
  1368. // acls
  1369. $acls = self::query("SELECT * FROM acl WHERE url like :id", array(':id'=>"%{$this->id}%"));
  1370. foreach ($acls->fetchAll(\PDO::FETCH_OBJ) as $rule) {
  1371. $url = str_replace($this->id, $newid, $rule->url);
  1372. self::query("UPDATE `acl` SET `url` = :url WHERE id = :id", array(':url'=>$url, ':id'=>$rule->id));
  1373. }
  1374. // echo 'acls listos <br />';
  1375. // mails
  1376. $mails = self::query("SELECT * FROM mail WHERE html like :id", array(':id'=>"%{$this->id}%"));
  1377. foreach ($mails->fetchAll(\PDO::FETCH_OBJ) as $mail) {
  1378. $html = str_replace($this->id, $newid, $mail->html);
  1379. self::query("UPDATE `mail` SET `html` = :html WHERE id = :id;", array(':html'=>$html, ':id'=>$mail->id));
  1380. }
  1381. // echo 'mails listos <br />';
  1382. // feed
  1383. $feeds = self::query("SELECT * FROM feed WHERE url like :id", array(':id'=>"%{$this->id}%"));
  1384. foreach ($feeds->fetchAll(\PDO::FETCH_OBJ) as $feed) {
  1385. $title = str_replace($this->id, $newid, $feed->title);
  1386. $html = str_replace($this->id, $newid, $feed->html);
  1387. self::query("UPDATE `feed` SET `title` = :title, `html` = :html WHERE id = :id", array(':title'=>$title, ':html'=>$html, ':id'=>$feed->id));
  1388. }
  1389. // feed
  1390. $feeds2 = self::query("SELECT * FROM feed WHERE target_type = 'project' AND target_id = :id", array(':id'=>$this->id));
  1391. foreach ($feeds2->fetchAll(\PDO::FETCH_OBJ) as $feed2) {
  1392. self::query("UPDATE `feed` SET `target_id` = '{$newid}' WHERE id = '{$feed2->id}';");
  1393. }
  1394. // traductores
  1395. $sql = "UPDATE `user_translate` SET `item` = '{$newid}' WHERE `user_translate`.`type` = 'project' AND `user_translate`.`item` = :id;";
  1396. self::query($sql, array(':id'=>$this->id));
  1397. self::query("UPDATE cost SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1398. self::query("UPDATE message SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1399. self::query("UPDATE project_category SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1400. self::query("UPDATE project_image SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1401. self::query("UPDATE project_lang SET id = :newid WHERE id = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1402. self::query("UPDATE reward SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1403. self::query("UPDATE support SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1404. self::query("UPDATE project_account SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1405. self::query("UPDATE invest SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1406. self::query("UPDATE promote SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1407. self::query("UPDATE patron SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1408. self::query("UPDATE invest SET project = :newid WHERE project = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1409. self::query("UPDATE project SET id = :newid WHERE id = :id", array(':newid'=>$newid, ':id'=>$this->id));
  1410. // si todo va bien, commit y cambio el id de la instancia
  1411. if (!$fail) {
  1412. self::query("COMMIT");
  1413. $this->id = $newid;
  1414. return true;
  1415. } else {
  1416. self::query("ROLLBACK");
  1417. return false;
  1418. }
  1419. } catch (\PDOException $e) {
  1420. self::query("ROLLBACK");
  1421. return false;
  1422. }
  1423. } else {
  1424. throw new Goteo\Core\Exception('Fallo al iniciar transaccion rebase. ' . \trace($e));
  1425. }
  1426. }
  1427. return true;
  1428. } catch (\PDOException $e) {
  1429. throw new \Goteo\Core\Exception('Fallo rebase id temporal. ' . \trace($e));
  1430. }
  1431. }
  1432. /*
  1433. * Para verificar id única
  1434. */
  1435. public static function checkId($id, $num = 1) {
  1436. try
  1437. {
  1438. $query = self::query("SELECT id FROM project WHERE id = :id", array(':id'=>$id));
  1439. $exist = $query->fetchObject();
  1440. // si ya existe, cambiar las últimas letras por un número
  1441. if (!empty($exist->id)) {
  1442. $sufix = (string) $num;
  1443. if ((strlen($id)+strlen($sufix)) > 49)
  1444. $id = substr($id, 0, (strlen($id) - strlen($sufix))) . $sufix;
  1445. else
  1446. $id = $id . $sufix;
  1447. $num++;
  1448. $id = self::checkId($id, $num);
  1449. }
  1450. return $id;
  1451. }
  1452. catch (\PDOException $e) {
  1453. throw new Goteo\Core\Exception(Text::_('Fallo al verificar id única para el proyecto. ') . $e->getMessage());
  1454. }
  1455. }
  1456. /*
  1457. * Para actualizar el minimo/optimo de costes
  1458. */
  1459. public function minmax() {
  1460. $this->mincost = 0;
  1461. $this->maxcost = 0;
  1462. foreach ($this->costs as $item) {
  1463. if ($item->required == 1) {
  1464. $this->mincost += $item->amount;
  1465. $this->maxcost += $item->amount;
  1466. }
  1467. else {
  1468. $this->maxcost += $item->amount;
  1469. }
  1470. }
  1471. }
  1472. /**
  1473. * Metodo que devuelve los días que lleva de publicación
  1474. *
  1475. * @return numeric days active from published
  1476. */
  1477. public function daysActive() {
  1478. // días desde el published
  1479. $sql = "
  1480. SELECT DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(CONCAT(published, DATE_FORMAT(now(), ' %H:%i:%s')))), '%j') as days
  1481. FROM project
  1482. WHERE id = ?";
  1483. $query = self::query($sql, array($this->id));
  1484. $past = $query->fetchObject();
  1485. return $past->days - 1;
  1486. }
  1487. /**
  1488. * Metodo que devuelve los días que quedan para finalizar la ronda actual
  1489. *
  1490. * @return numeric days remaining to go
  1491. */
  1492. public function daysRemain($id) {
  1493. // primero, días desde el published
  1494. $sql = "
  1495. SELECT DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(published)), '%j') as days
  1496. FROM project
  1497. WHERE id = ?";
  1498. $query = self::query($sql, array($id));
  1499. $days = $query->fetchColumn(0);
  1500. $days--;
  1501. if ($days > 40) {
  1502. $rest = 80 - $days; //en segunda ronda
  1503. } else {
  1504. $rest = 40 - $days; // en primera ronda
  1505. }
  1506. return $rest;
  1507. }
  1508. /*
  1509. * Lista de proyectos de un usuario
  1510. */
  1511. public static function ofmine($owner, $published = false)
  1512. {
  1513. $projects = array();
  1514. $sql = "SELECT * FROM project WHERE owner = ?";
  1515. if ($published) {
  1516. $sql .= " AND status > 2";
  1517. } /* else {
  1518. $sql .= " AND status > 0";
  1519. } */
  1520. $sql .= " ORDER BY created DESC";
  1521. $query = self::query($sql, array($owner));
  1522. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $proj) {
  1523. $projects[] = self::getMedium($proj->id);
  1524. }
  1525. return $projects;
  1526. }
  1527. /*
  1528. * Lista de proyectos publicados
  1529. */
  1530. public static function published($type = 'all', $limit = null, $mini = false)
  1531. {
  1532. $values = array();
  1533. // si es un nodo, filtrado
  1534. if (\NODE_ID != \GOTEO_NODE) {
  1535. $sqlFilter = "AND project.node = :node";
  1536. $values[':node'] = NODE_ID;
  1537. } else {
  1538. $sqlFilter = "";
  1539. }
  1540. // segun el tipo (ver controller/discover.php)
  1541. switch ($type) {
  1542. case 'popular':
  1543. // de los que estan en campaña,
  1544. // los que tienen más usuarios entre cofinanciadores y mensajeros
  1545. $sql = "SELECT project.id as id,
  1546. project.name as name,
  1547. (SELECT COUNT(DISTINCT(invest.user))
  1548. FROM invest
  1549. WHERE invest.project = project.id
  1550. AND invest.status IN ('0', '1')
  1551. )
  1552. +
  1553. (SELECT COUNT(DISTINCT(message.user))
  1554. FROM message
  1555. WHERE message.project = project.id
  1556. ) as followers
  1557. FROM project
  1558. WHERE project.status= 3
  1559. $sqlFilter
  1560. HAVING followers > 20
  1561. ORDER BY followers DESC";
  1562. break;
  1563. case 'outdate':
  1564. // los que les quedan 15 dias o menos
  1565. $sql = "SELECT id,
  1566. name
  1567. FROM project
  1568. WHERE days <= 15
  1569. AND days > 0
  1570. AND status = 3
  1571. $sqlFilter
  1572. ORDER BY days ASC";
  1573. // Quitamos lo de "si ya han conseguido el minimo"
  1574. /*
  1575. ,
  1576. (SELECT SUM(amount)
  1577. FROM cost
  1578. WHERE project = project.id
  1579. AND required = 1
  1580. ) as `mincost`,
  1581. (SELECT SUM(amount)
  1582. FROM invest
  1583. WHERE project = project.id
  1584. AND (invest.status = 0
  1585. OR invest.status = 1
  1586. OR invest.status = 3
  1587. OR invest.status = 4)
  1588. ) as `getamount` */
  1589. // HAVING (getamount < mincost OR getamount IS NULL)
  1590. break;
  1591. case 'recent':
  1592. // los que llevan menos tiempo desde el published, hasta 15 dias
  1593. // Cambio de criterio: Los últimos 9
  1594. //, DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(published)), '%e') as day
  1595. // HAVING day <= 15 AND day IS NOT NULL
  1596. $limit = 9;
  1597. $sql = "SELECT
  1598. project.id as id,
  1599. project.name as name
  1600. FROM project
  1601. WHERE project.status = 3
  1602. AND project.passed IS NULL
  1603. $sqlFilter
  1604. ORDER BY published DESC";
  1605. break;
  1606. case 'success':
  1607. // los que han conseguido el mínimo
  1608. $sql = "SELECT
  1609. id,
  1610. name,
  1611. (SELECT SUM(amount)
  1612. FROM cost
  1613. WHERE project = project.id
  1614. AND required = 1
  1615. ) as `mincost`,
  1616. (SELECT SUM(amount)
  1617. FROM invest
  1618. WHERE project = project.id
  1619. AND invest.status IN ('0', '1', '3', '4')
  1620. ) as `getamount`
  1621. FROM project
  1622. WHERE status IN ('3', '4', '5')
  1623. $sqlFilter
  1624. HAVING getamount >= mincost
  1625. ORDER BY published DESC";
  1626. break;
  1627. case 'almost-fulfilled':
  1628. // para gestión de retornos
  1629. $sql = "SELECT id, name FROM project WHERE status IN ('4','5') $sqlFilter ORDER BY name ASC";
  1630. break;
  1631. case 'fulfilled':
  1632. // retorno cumplido
  1633. $sql = "SELECT id, name FROM project WHERE status IN ('5') $sqlFilter ORDER BY name ASC";
  1634. break;
  1635. case 'available':
  1636. // ni edicion ni revision ni cancelados, estan disponibles para verse publicamente
  1637. $sql = "SELECT id, name FROM project WHERE status > 2 AND status < 6 $sqlFilter ORDER BY name ASC";
  1638. break;
  1639. case 'archive':
  1640. // caducados, financiados o casos de exito
  1641. $sql = "SELECT id, name FROM project WHERE status = 6 $sqlFilter ORDER BY closed DESC";
  1642. break;
  1643. case 'others':
  1644. // todos los que estan 'en campaña', en otro nodo
  1645. if (!empty($sqlFilter)) $sqlFilter = \str_replace('=', '!=', $sqlFilter);
  1646. // cambio de criterio, en otros nodos no filtramos por followers,
  1647. // mostramos todos los que estan en campaña (los nuevos primero)
  1648. // limitamos a 40
  1649. /*
  1650. $sql = "SELECT project.id as id,
  1651. (SELECT COUNT(DISTINCT(invest.user))
  1652. FROM invest
  1653. WHERE invest.project = project.id
  1654. AND invest.status IN ('0', '1')
  1655. )
  1656. +
  1657. (SELECT COUNT(DISTINCT(message.user))
  1658. FROM message
  1659. WHERE message.project = project.id
  1660. ) as followers
  1661. FROM project
  1662. WHERE project.status= 3
  1663. $sqlFilter
  1664. HAVING followers > 20
  1665. ORDER BY followers DESC";
  1666. */
  1667. $limit = 40;
  1668. $sql = "SELECT
  1669. project.id as id,
  1670. project.name as name
  1671. FROM project
  1672. WHERE project.status = 3
  1673. $sqlFilter
  1674. ORDER BY published DESC";
  1675. break;
  1676. default:
  1677. // todos los que estan 'en campaña', en cualquier nodo
  1678. $sql = "SELECT id, name FROM project WHERE status = 3 ORDER BY name ASC";
  1679. }
  1680. // Limite
  1681. if (!empty($limit) && \is_numeric($limit)) {
  1682. $sql .= " LIMIT $limit";
  1683. }
  1684. $projects = array();
  1685. $query = self::query($sql, $values);
  1686. foreach ($query->fetchAll(\PDO::FETCH_ASSOC) as $proj) {
  1687. if ($mini) {
  1688. $projects[$proj['id']] = $proj['name'];
  1689. } else {
  1690. $projects[] = self::getMedium($proj['id']);
  1691. }
  1692. }
  1693. return $projects;
  1694. }
  1695. //
  1696. /**
  1697. * Lista de proyectos en campaña y/o financiados
  1698. * para crear aporte manual
  1699. * para gestión de contratos
  1700. *
  1701. * @param bool $campaignonly solo saca proyectos en proceso de campaña (parece que esto no se usa...)
  1702. * @param bool $mini devuelve array asociativo id => nombre, para cuando no se necesita toda la instancia
  1703. * @return array de instancias de proyecto // array asociativo (si recibe mini = true)
  1704. */
  1705. public static function active($campaignonly = false, $mini = false)
  1706. {
  1707. $projects = array();
  1708. if ($campaignonly) {
  1709. $sqlFilter = " WHERE project.status = 3";
  1710. } else {
  1711. $sqlFilter = " WHERE project.status = 3 OR project.status = 4";
  1712. }
  1713. $sql = "SELECT id, name FROM project {$sqlFilter} ORDER BY name ASC";
  1714. $query = self::query($sql);
  1715. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $proj) {
  1716. if ($mini) {
  1717. $projects[$proj->id] = $proj->name;
  1718. } else {
  1719. $projects[] = self::get($proj->id);
  1720. }
  1721. }
  1722. return $projects;
  1723. }
  1724. /**
  1725. * Lista de proyectos para ser revisados por el cron/daily
  1726. * en campaña
  1727. * o financiados hace más de dos meses y con retornos/recompensas pendientes
  1728. *
  1729. * solo carga datos necesarios para cron/daily
  1730. *
  1731. * @return array de instancias parciales de proyecto
  1732. */
  1733. public static function review()
  1734. {
  1735. $projects = array();
  1736. $sql = "SELECT
  1737. id, status,
  1738. DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(published)), '%j') as dias
  1739. FROM project
  1740. WHERE status IN ('3', '4')
  1741. HAVING status = 3 OR (status = 4 AND dias > 138)
  1742. ORDER BY days ASC";
  1743. $query = self::query($sql);
  1744. foreach ($query->fetchAll(\PDO::FETCH_CLASS, __CLASS__) as $proj) {
  1745. $the_proj = self::getMedium($proj->id);
  1746. $the_proj->percent = floor(($the_proj->invested / $the_proj->mincost) * 100);
  1747. $the_proj->days = (int) $proj->dias - 1;
  1748. $the_proj->patrons = Patron::numRecos($proj->id);
  1749. $projects[] = $the_proj;
  1750. }
  1751. return $projects;
  1752. }
  1753. /*
  1754. * Lista de proyectos en campaña (para ser revisados por el cron/execute)
  1755. */
  1756. public static function getActive()
  1757. {
  1758. $projects = array();
  1759. $sql = "
  1760. SELECT project.id as id
  1761. FROM project
  1762. WHERE project.status = 3
  1763. AND (
  1764. (DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(published)), '%j') >= 35
  1765. AND (passed IS NULL OR passed = '0000-00-00')
  1766. )
  1767. OR
  1768. (DATE_FORMAT(from_unixtime(unix_timestamp(now()) - unix_timestamp(published)), '%j') >= 75
  1769. AND (success IS NULL OR success = '0000-00-00')
  1770. )
  1771. )
  1772. ORDER BY name ASC
  1773. ";
  1774. $query = self::query($sql);
  1775. foreach ($query->fetchAll(\PDO::FETCH_OBJ) as $proj) {
  1776. $projects[] = self::get($proj->id);
  1777. }
  1778. return $projects;
  1779. }
  1780. /**
  1781. * Saca una lista completa de proyectos
  1782. *
  1783. * @param string node id
  1784. * @return array of project instances
  1785. */
  1786. public static function getList($filters = array(), $node = null) {
  1787. $projects = array();
  1788. $values = array();
  1789. // los filtros
  1790. $sqlFilter = "";
  1791. if (!empty($filters['multistatus'])) {
  1792. $sqlFilter .= " AND status IN ({$filters['multistatus']})";
  1793. }
  1794. if ($filters['status'] > -1) {
  1795. $sqlFilter .= " AND status = :status";
  1796. $values[':status'] = $filters['status'];
  1797. } elseif ($filters['status'] == -2) {
  1798. $sqlFilter .= " AND (status = 1 AND id NOT REGEXP '[0-9a-f]{5,40}')";
  1799. } else {
  1800. $sqlFilter .= " AND (status > 1 OR (status = 1 AND id NOT REGEXP '[0-9a-f]{5,40}') )";
  1801. }
  1802. if (!empty($filters['owner'])) {
  1803. $sqlFilter .= " AND owner = :owner";
  1804. $values[':owner'] = $filters['owner'];
  1805. }
  1806. if (!empty($filters['name'])) {
  1807. $sqlFilter .= " AND owner IN (SELECT id FROM user WHERE (name LIKE :user OR email LIKE :user))";
  1808. $values[':user'] = "%{$filters['name']}%";
  1809. }
  1810. if (!empty($filters['proj_name'])) {
  1811. $sqlFilter .= " AND name LIKE :name";
  1812. $values[':name'] = "%{$filters['proj_name']}%";
  1813. }
  1814. if (!empty($filters['category'])) {
  1815. $sqlFilter .= " AND id IN (
  1816. SELECT project
  1817. FROM project_category
  1818. WHERE category = :category
  1819. )";
  1820. $values[':category'] = $filters['category'];
  1821. }
  1822. //el Order
  1823. if (!empty($filters['order'])) {
  1824. switch ($filters['order']) {
  1825. case 'updated':
  1826. $sqlOrder .= " ORDER BY updated DESC";
  1827. break;
  1828. case 'name':
  1829. $sqlOrder .= " ORDER BY name ASC";
  1830. break;
  1831. default:
  1832. $sqlOrder .= " ORDER BY {$filters['order']}";
  1833. break;
  1834. }
  1835. }
  1836. // la select
  1837. $sql = "SELECT
  1838. id,
  1839. id REGEXP '[0-9a-f]{5,40}' as draft
  1840. FROM project
  1841. WHERE id != ''
  1842. $sqlFilter
  1843. $sqlOrder
  1844. LIMIT 999
  1845. ";
  1846. $query = self::query($sql, $values);
  1847. foreach ($query->fetchAll(\PDO::FETCH_ASSOC) as $proj) {
  1848. $the_proj = self::getMedium($proj['id']);
  1849. $the_proj->draft = $proj['draft'];
  1850. $projects[] = $the_proj;
  1851. }
  1852. return $projects;
  1853. }
  1854. /**
  1855. * Saca una lista de proyectos disponibles para traducir
  1856. *
  1857. * @param array filters
  1858. * @param string node id
  1859. * @return array of project instances
  1860. */
  1861. public static function getTranslates($filters = array(), $node = \GOTEO_NODE) {
  1862. $projects = array();
  1863. $values = array(':node' => $node);
  1864. $sqlFilter = "";
  1865. if (!empty($filters['owner'])) {
  1866. $sqlFilter .= " AND owner = :owner";
  1867. $values[':owner'] = $filters['owner'];
  1868. }
  1869. if (!empty($filters['translator'])) {
  1870. $sqlFilter .= " AND id IN (
  1871. SELECT item
  1872. FROM user_translate
  1873. WHERE user = :translator
  1874. AND type = 'project'
  1875. )";
  1876. $values[':translator'] = $filters['translator'];
  1877. }
  1878. $sql = "SELECT
  1879. id
  1880. FROM project
  1881. WHERE translate = 1
  1882. AND node = :node
  1883. $sqlFilter
  1884. ORDER BY name ASC
  1885. ";
  1886. $query = self::query($sql, $values);
  1887. foreach ($query->fetchAll(\PDO::FETCH_ASSOC) as $proj) {
  1888. $projects[] = self::getMini($proj['id']);
  1889. }
  1890. return $projects;
  1891. }
  1892. /**
  1893. * Metodo para obtener cofinanciadores agregados por usuario
  1894. * y sin convocadores
  1895. */
  1896. public function agregateInvestors () {
  1897. $investors = array();
  1898. foreach($this->investors as $investor) {
  1899. if (!empty($investor->campaign)) continue;
  1900. $investors[$investor->user] = (object) array(
  1901. 'user' => $investor->user,
  1902. 'name' => $investor->name,
  1903. 'avatar' => $investor->avatar,
  1904. 'projects' => $investor->projects,
  1905. 'worth' => $investor->worth,
  1906. 'amount' => $investors[$investor->user]->amount + $investor->amount,
  1907. 'date' => !empty($investors[$investor->user]->date) ?$investors[$investor->user]->date : $investor->date
  1908. );
  1909. }
  1910. return $investors;
  1911. }
  1912. /*
  1913. Método para calcular el mínimo y óptimo de un proyecto
  1914. */
  1915. public static function calcCosts($id) {
  1916. $cost_query = self::query("SELECT
  1917. (SELECT SUM(amount)
  1918. FROM cost
  1919. WHERE project = project.id
  1920. AND required = 1
  1921. ) as `mincost`,
  1922. (SELECT SUM(amount)
  1923. FROM cost
  1924. WHERE project = project.id
  1925. ) as `maxcost`
  1926. FROM project
  1927. WHERE id =?", array($id));
  1928. $costs = $cost_query->fetchObject();
  1929. return $costs;
  1930. }
  1931. /*
  1932. * Para saber si ha conseguido el mínimo
  1933. */
  1934. public static function isSuccessful($id) {
  1935. $sql = "SELECT
  1936. id,
  1937. (SELECT SUM(amount)
  1938. FROM cost
  1939. WHERE project = project.id
  1940. AND required = 1
  1941. ) as `mincost`,
  1942. (SELECT SUM(amount)
  1943. FROM invest
  1944. WHERE project = project.id
  1945. AND invest.status IN ('0', '1', '3', '4')
  1946. ) as `getamount`
  1947. FROM project
  1948. WHERE project.id = ?
  1949. HAVING getamount >= mincost
  1950. LIMIT 1
  1951. ";
  1952. $query = self::query($sql, array($id));
  1953. return ($query->fetchColumn() == $id);
  1954. }
  1955. /*
  1956. * Para saber si un usuario es el impulsor
  1957. */
  1958. public static function isMine($id, $user) {
  1959. $sql = "SELECT id, owner FROM project WHERE id = :id AND owner = :owner";
  1960. $values = array(
  1961. ':id' => $id,
  1962. ':owner' => $user
  1963. );
  1964. $query = static::query($sql, $values);
  1965. $mine = $query->fetchObject();
  1966. if ($mine->owner == $user && $mine->id == $id) {
  1967. return true;
  1968. } else {
  1969. return false;
  1970. }
  1971. }
  1972. /*
  1973. * Estados de desarrollo del propyecto
  1974. */
  1975. public static function currentStatus () {
  1976. return array(
  1977. 1=>Text::get('overview-field-options-currently_inicial'),
  1978. 2=>Text::get('overview-field-options-currently_medio'),
  1979. 3=>Text::get('overview-field-options-currently_avanzado'),
  1980. 4=>Text::get('overview-field-options-currently_finalizado'));
  1981. }
  1982. /*
  1983. * Ámbito de alcance de un proyecto
  1984. */
  1985. public static function scope () {
  1986. return array(
  1987. 1=>Text::get('overview-field-options-scope_local'),
  1988. 2=>Text::get('overview-field-options-scope_regional'),
  1989. 3=>Text::get('overview-field-options-scope_nacional'),
  1990. 4=>Text::get('overview-field-options-scope_global'));
  1991. }
  1992. /*
  1993. * Estados de publicación de un proyecto
  1994. */
  1995. public static function status () {
  1996. return array(
  1997. 0=>Text::get('form-project_status-cancelled'),
  1998. 1=>Text::get('form-project_status-edit'),
  1999. 2=>Text::get('form-project_status-review'),
  2000. 3=>Text::get('form-project_status-campaing'),
  2001. 4=>Text::get('form-project_status-success'),
  2002. 5=>Text::get('form-project_status-fulfilled'),
  2003. 6=>Text::get('form-project_status-expired'));
  2004. }
  2005. /*
  2006. * Estados de proceso de campaña
  2007. */
  2008. public static function procStatus () {
  2009. return array(
  2010. 'first' => Text::_('En primera ronda'),
  2011. 'second' => Text::_('En segunda ronda'),
  2012. 'completed' => Text::_('Campaña completada')
  2013. );
  2014. }
  2015. /*
  2016. * Siguiente etapa en la vida del proyeto
  2017. */
  2018. public static function waitfor () {
  2019. return array(
  2020. 0=>Text::get('form-project_waitfor-cancel'),
  2021. 1=>Text::get('form-project_waitfor-edit'),
  2022. 2=>Text::get('form-project_waitfor-review'),
  2023. 3=>Text::get('form-project_waitfor-campaing'),
  2024. 4=>Text::get('form-project_waitfor-success'),
  2025. 5=>Text::get('form-project_waitfor-fulfilled'),
  2026. 6=>Text::get('form-project_waitfor-expired'));
  2027. }
  2028. public static function blankErrors() {
  2029. // para guardar los fallos en los datos
  2030. $errors = array(
  2031. 'userProfile' => array(), // Errores en el paso 1
  2032. 'userPersonal' => array(), // Errores en el paso 2
  2033. 'overview' => array(), // Errores en el paso 3
  2034. 'costs' => array(), // Errores en el paso 4
  2035. 'rewards' => array(), // Errores en el paso 5
  2036. 'supports' => array() // Errores en el paso 6
  2037. );
  2038. return $errors;
  2039. }
  2040. }
  2041. }