3 * FusionForge FRS: Release Class
5 * Copyright 2002, Tim Perdue/GForge, LLC
6 * Copyright 2009, Roland Mas
7 * Copyright (C) 2012 Alain Peyrat - Alcatel-Lucent
8 * Copyright 2014,2016, Franck Villaume - TrivialDev
10 * This file is part of FusionForge. FusionForge is free software;
11 * you can redistribute it and/or modify it under the terms of the
12 * GNU General Public License as published by the Free Software
13 * Foundation; either version 2 of the Licence, or (at your option)
16 * FusionForge is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 require_once $gfcommon.'include/FFObject.class.php';
27 require_once $gfcommon.'frs/FRSFile.class.php';
30 * get_frs_releases - get all FRS releases for a specific package
32 * @param FRSPackage $package
35 function get_frs_releases($package) {
37 $res = db_query_params('SELECT * FROM frs_release WHERE package_id=$1',
38 array($package->getID()));
39 if (db_numrows($res) > 0) {
40 while($arr = db_fetch_array($res)) {
41 $rs[] = new FRSRelease($package, $arr['release_id'], $arr);
48 * Factory method which creates a FRSRelease from an release id
50 * @param int $release_id The release id
51 * @param array $data The result array, if it's passed in
52 * @return object|bool FRSRelease object
54 function frsrelease_get_object($release_id, $data = array()) {
55 global $FRSRELEASE_OBJ;
56 if (!isset($FRSRELEASE_OBJ['_'.$release_id.'_'])) {
58 //the db result handle was passed in
60 $res = db_query_params('SELECT * FROM frs_release WHERE release_id=$1',
62 if (db_numrows($res)<1 ) {
63 $FRSRELEASE_OBJ['_'.$release_id.'_'] = false;
66 $data = db_fetch_array($res);
68 $FRSPackage = frspackage_get_object($data['package_id']);
69 $FRSRELEASE_OBJ['_'.$release_id.'_'] = new FRSRelease($FRSPackage, $data['release_id'], $data);
71 return $FRSRELEASE_OBJ['_'.$release_id.'_'];
74 class FRSRelease extends FFObject {
77 * Associative array of data from db.
79 * @var array $data_array.
86 * @var object FRSPackage.
90 var $files_count = null;
91 var $send_notice = true;
94 * cached return value of getVotes
95 * @var int|bool $votes
100 * cached return value of getVoters
101 * @var int|bool $voters
106 * @param object $FRSPackage The FRSPackage object to which this release is associated.
107 * @param int|bool $release_id The release_id.
108 * @param array $arr The associative array of data.
110 function __construct(&$FRSPackage, $release_id = false, $arr = array()) {
111 if (!$FRSPackage || !is_object($FRSPackage)) {
112 $this->setError(_('Invalid FRS Package Object'));
115 if ($FRSPackage->isError()) {
116 $this->setError('FRSRelease: '.$FRSPackage->getErrorMessage());
120 $this->FRSPackage =& $FRSPackage;
123 parent::__construct($release_id, 'FRSRelease');
124 if (!$arr || !is_array($arr)) {
125 $this->fetchData($release_id);
127 $this->data_array =& $arr;
128 if ($this->data_array['package_id'] != $this->FRSPackage->getID()) {
129 $this->setError('FRSPackage_id in db result does not match FRSPackage Object');
130 $this->data_array = null;
134 parent::__construct();
139 * create - create a new release in the database.
141 * @param string $name The name of the release.
142 * @param string $notes The release notes for the release.
143 * @param string $changes The change log for the release.
144 * @param int $preformatted Whether the notes/log are preformatted with \n chars (1) true (0) false.
145 * @param int|bool $release_date The unix date of the release.
146 * @param int $status_id
147 * @param array $importData Array of data to change creator, time of creation, bypass permission check and do not send notification like:
148 * array('user' => 127, 'time' => 1234556789, 'nopermcheck' => 1, 'nonotice' => 1)
149 * @return bool success.
151 function create($name, $notes, $changes, $preformatted, $release_date = false, $status_id = 1, $importData = array()) {
152 if (strlen($name) < 3) {
153 $this->setError(_('FRSRelease Name Must Be At Least 3 Characters'));
163 if (isset($importData['user'])) {
164 $userid = $importData['user'];
166 $userid = user_getid();
169 if (!isset($importData['nopermcheck']) || (isset($importData['nopermcheck']) && !$importData['nopermcheck'])) {
170 if (!forge_check_perm_for_user(user_get_object($userid), 'frs', $this->FRSPackage->getID(), 'release')) {
171 $this->setPermissionDeniedError();
176 if (!$release_date || !isset($importData['time'])) {
177 $release_date = time();
179 if (isset($importData['time'])) {
180 $release_date = $importData['time'];
183 $res = db_query_params('SELECT * FROM frs_release WHERE package_id=$1 AND name=$2',
184 array ($this->FRSPackage->getID(),
185 htmlspecialchars($name)));
186 if (db_numrows($res)) {
187 $this->setError(_('Error Adding Release: ')._('Name Already Exists'));
192 $result = db_query_params('INSERT INTO frs_release(package_id,notes,changes,preformatted,name,release_date,released_by,status_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
193 array($this->FRSPackage->getID(),
194 htmlspecialchars($notes),
195 htmlspecialchars($changes),
197 htmlspecialchars($name),
202 $this->setError(_('Error Adding Release: ').db_error());
206 $this->release_id=db_insertid($result,'frs_release','release_id');
207 if (!$this->fetchData($this->release_id)) {
211 $newdirlocation = forge_get_config('upload_dir').'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$this->getFileName();
212 if (!is_dir($newdirlocation)) {
213 @mkdir($newdirlocation);
216 if ($status_id == 1) {
224 * fetchData - re-fetch the data for this Release from the database.
226 * @param int $release_id The release_id.
227 * @return bool success.
229 function fetchData($release_id) {
230 $res = db_query_params('SELECT * FROM frs_release WHERE release_id=$1 AND package_id=$2',
231 array($release_id, $this->FRSPackage->getID()));
232 if (!$res || db_numrows($res) < 1) {
233 $this->setError(_('Invalid release_id'));
236 $this->data_array = db_fetch_array($res);
237 db_free_result($res);
242 * getFRSPackage - get the FRSPackage object this release is associated with.
244 * @return object The FRSPackage object.
246 function &getFRSPackage() {
247 return $this->FRSPackage;
251 * getID - get this release_id.
253 * @return int The id of this release.
256 return $this->data_array['release_id'];
260 * getName - get the name of this release.
262 * @return string The name of this release.
265 return $this->data_array['name'];
269 * getFileName - get the filename of this release.
271 * @return string The filename of this release.
273 function getFileName() {
274 return util_secure_filename($this->data_array['name']);
278 * getStatus - get the status of this release.
280 * @return int The status.
282 function getStatus() {
283 return $this->data_array['status_id'];
287 * getStatusName - get the status name of this release based on his status_id.
289 * @return string The status name.
291 function getStatusName() {
292 $res = db_query_params('SELECT * FROM frs_status', array());
293 while ($arr = db_fetch_array($res)) {
294 if ($arr['status_id'] == $this->getStatus()) {
302 * getNotes - get the release notes of this release.
304 * @return string The release notes.
306 function getNotes() {
307 return $this->data_array['notes'];
311 * getChanges - get the changelog of this release.
313 * @return string The changelog.
315 function getChanges() {
316 return $this->data_array['changes'];
320 * getPreformatted - get the preformatted option of this release.
322 * @return bool preserve_formatting.
324 function getPreformatted() {
325 return $this->data_array['preformatted'];
329 * getReleaseDate - get the releasedate of this release.
331 * @return int The release date in unix time.
333 function getReleaseDate() {
334 return $this->data_array['release_date'];
338 * setSendNotice - sets if the notice email should be send
339 * @param $value true/false
341 function setSendNotice($value) {
342 $this->send_notice = $value;
346 * getSendNotice - get if the notice email should be send
349 function getSendNotice() {
350 return $this->send_notice;
354 * sendNotice - the logic to send an email notice for a release.
356 * @return bool success.
358 function sendNotice() {
359 $arr =& $this->FRSPackage->getMonitorIDs();
360 $project_adresses = $this->FRSPackage->Group->getFRSEmailAddress();
362 $subject = sprintf(_('[%1$s Release] %2$s'),
363 $this->FRSPackage->Group->getUnixName(),
364 $this->FRSPackage->getName());
365 $text = sprintf(_('Project %1$s (%2$s) has released a new version of package “%3$s”.'),
366 $this->FRSPackage->Group->getPublicName(),
367 $this->FRSPackage->Group->getUnixName(),
368 $this->FRSPackage->getName())
370 . _('Release Notes')._(':')
374 . _('Change Log')._(':')
376 . $this->getChanges()
378 . _('You can download it by following this link')._(':')
380 . util_make_url('/frs/?group_id='.$this->FRSPackage->Group->getID().'&release_id='.$this->getID())
382 . sprintf(_('You receive this email because you requested to be notified when new '
383 . 'versions of this package were released. If you don\'t wish to be '
384 . 'notified in the future, please login to %s and click this link:'),
385 forge_get_config('forge_name'))
387 . util_make_url('/frs/monitor.php?filemodule_id='.$this->FRSPackage->getID()."&group_id=".$this->FRSPackage->Group->getID()."&stop=1");
388 if (count($arr) || strlen($project_adresses) > 0) {
389 util_handle_message(array_unique($arr), $subject, $text, $project_adresses);
394 * newFRSFile - generates a FRSFile (allows overloading by subclasses)
396 * @param string FRS file identifier
397 * @param array fetched data from the DB
398 * @return FRSFile new FRSFile object.
400 protected function newFRSFile($file_id, $data) {
401 return new FRSFile($this, $file_id, $data);
405 * getFiles - gets all the file objects for files in this release.
407 * @return array Array of FRSFile Objects.
409 function &getFiles() {
410 if (!is_array($this->release_files) || count($this->release_files) < 1) {
411 $this->release_files = array();
412 $res = db_query_params('SELECT * FROM frs_file_vw WHERE release_id=$1',
413 array($this->getID())) ;
414 while ($arr = db_fetch_array($res)) {
415 $this->release_files[$arr['file_id']] = $this->newFRSFile($arr['file_id'], $arr);
418 return $this->release_files;
421 function hasFiles() {
422 if ($this->files_count != null) {
423 return $this->files_count;
425 $res = db_query_params('select count(file_id) as files_count from frs_file where release_id = $1', array($this->getID()));
426 if (db_numrows($res) >= 1) {
427 $row = db_fetch_array($res);
428 $this->files_count = $row['files_count'];
430 return $this->files_count;
434 * delete - delete this release and all its related data.
436 * @param bool $sure I'm Sure.
437 * @param bool $really_sure I'm REALLY sure.
438 * @return bool true/false;
440 function delete($sure, $really_sure) {
441 if (!$sure || !$really_sure) {
442 $this->setMissingParamsError(_('Please tick all checkboxes.'));
445 if (!forge_check_perm('frs', $this->FRSPackage->getID(), 'release')) {
446 $this->setPermissionDeniedError();
449 $f =& $this->getFiles();
451 while($file = current($f)) {
452 if (!is_object($file) || $file->isError() || !$file->delete()) {
453 $this->setError(_('File Error')._(': ').$file->getName()._(': ').$file->getErrorMessage());
458 $dir=forge_get_config('upload_dir').'/'.
459 $this->FRSPackage->Group->getUnixName() . '/' .
460 $this->FRSPackage->getFileName().'/'.
461 $this->getFileName().'/';
463 // double-check we're not trying to remove root dir
464 if (util_is_root_dir($dir)) {
465 $this->setError(_('Release delete error')._(': ')._('trying to delete root dir'));
468 $this->FRSPackage->deleteReleaseFilesAsZip($this->getID());
471 db_query_params('DELETE FROM frs_release WHERE release_id=$1 AND package_id=$2',
472 array ($this->getID(),
473 $this->FRSPackage->getID()));
478 * update - update a new release in the database.
480 * @param int The status of this release from the frs_status table.
481 * @param string The name of the release.
482 * @param string The release notes for the release.
483 * @param string The change log for the release.
484 * @param int Whether the notes/log are preformatted with \n chars (1) true (0) false.
485 * @param int The unix date of the release.
486 * @return bool success.
488 function update($status, $name, $notes, $changes, $preformatted, $release_date) {
489 if (strlen($name) < 3) {
490 $this->setError(_('FRSRelease Name Must Be At Least 3 Characters'));
494 if (!forge_check_perm('frs', $this->FRSPackage->getID(), 'release')) {
495 $this->setPermissionDeniedError();
505 if($this->getName() != htmlspecialchars($name)) {
506 $res = db_query_params ('SELECT * FROM frs_release WHERE package_id=$1 AND name=$2',
507 array ($this->FRSPackage->getID(),
508 htmlspecialchars($name))) ;
509 if (db_numrows($res)) {
510 $this->setError(_('Error On Update')._(': ')._('Name Already Exists'));
515 $res = db_query_params('UPDATE frs_release SET name=$1,status_id=$2,notes=$3,
516 changes=$4,preformatted=$5,release_date=$6,released_by=$7
517 WHERE package_id=$8 AND release_id=$9',
518 array (htmlspecialchars($name),
520 htmlspecialchars($notes),
521 htmlspecialchars($changes),
525 $this->FRSPackage->getID(),
528 if (!$res || db_affected_rows($res) < 1) {
529 $this->setError(_('Error On Update')._(': ').db_error());
534 $oldfilename = $this->getFileName();
535 if(!$this->fetchData($this->getID())){
536 $this->setError(_('Error Updating Release')._(': ')._("Couldn't fetch data"));
540 $newfilename = $this->getFileName();
541 $olddirlocation = forge_get_config('upload_dir').'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$oldfilename;
542 $newdirlocation = forge_get_config('upload_dir').'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$newfilename;
544 if (($oldfilename != $newfilename) && is_dir($olddirlocation)) {
545 if (is_dir($newdirlocation)) {
546 $this->setError(_('Error Updating Release')._(': ')._('Directory Already Exists'));
550 if(!rename($olddirlocation, $newdirlocation)) {
551 $this->setError(_('Error Updating Release')._(': ')._("Couldn't rename dir"));
558 if ($this->hasFiles()) {
559 $this->FRSPackage->createReleaseFilesAsZip($this->getID());
561 if ($this->getSendNotice()) {
567 function isLinkedRoadmapRelease($roadmap_release) {
568 $res = db_query_params('SELECT roadmap_id FROM frs_release_tracker_roadmap_link WHERE release_id = $1 and roadmap_release = $2',
569 array($this->getID(), $roadmap_release));
573 return util_result_column_to_array($res);
576 function deleteLinkedRoadmap($roadmap_id, $roadmap_release) {
578 $res = db_query_params('DELETE FROM frs_release_tracker_roadmap_link where roadmap_id = $1 and release_id = $2 and roadmap_release = $3',
579 array($roadmap_id, $this->getID(), $roadmap_release));
581 $this->setError(_('Error Delete Linked Roadmap')._(': ').db_error());
589 function addLinkedRoadmap($roadmap_id, $roadmap_release) {
591 $res = db_query_params('INSERT INTO frs_release_tracker_roadmap_link (roadmap_id, release_id, roadmap_release) VALUES ($1, $2, $3)',
592 array($roadmap_id, $this->getID(), $roadmap_release));
594 $this->setError(_('Error Adding Linked Roadmap')._(': ').db_error());
602 function getLinkedRoadmaps() {
604 $res = db_query_params('SELECT roadmap_id, roadmap_release FROM frs_release_tracker_roadmap_link WHERE release_id = $1',
605 array($this->getID()));
609 while ($arr = db_fetch_array($res)) {
610 $roadmaps[$arr[0]][] = $arr[1];
615 function getPermalink() {
616 return '/frs/r_follow.php/'.$this->getID();
620 * castVote - Vote on this frs release item or retract the vote
621 * @param bool $value true to cast, false to retract
622 * @return bool success (false sets error message)
624 function castVote($value = true) {
625 if (!($uid = user_getid()) || $uid == 100) {
626 $this->setMissingParamsError(_('User ID not passed'));
629 if (!$this->canVote()) {
630 $this->setPermissionDeniedError();
633 $has_vote = $this->hasVote($uid);
634 if ($has_vote == $value) {
635 /* nothing changed */
639 $res = db_query_params('INSERT INTO frs_release_votes (release_id, user_id) VALUES ($1, $2)',
640 array($this->getID(), $uid));
642 $res = db_query_params('DELETE FROM frs_release_votes WHERE release_id = $1 AND user_id = $2',
643 array($this->getID(), $uid));
646 $this->setError(db_error());
653 * hasVote - Check if a user has voted on this frs release item
655 * @param int|bool $uid user ID (default: current user)
656 * @return bool true if a vote exists
658 function hasVote($uid = false) {
662 if (!$uid || $uid == 100) {
665 $res = db_query_params('SELECT * FROM frs_release_votes WHERE release_id = $1 AND user_id = $2',
666 array($this->getID(), $uid));
667 return (db_numrows($res) == 1);
671 * getVotes - get number of valid cast and potential votes
673 * @return array|bool (votes, voters, percent)
675 function getVotes() {
676 if ($this->votes !== false) {
680 $voters = $this->getVoters();
681 unset($voters[0]); /* just in case */
682 unset($voters[100]); /* need users */
683 if (($numvoters = count($voters)) < 1) {
684 $this->votes = array(0, 0, 0);
688 $res = db_query_params('SELECT COUNT(*) AS count FROM frs_release_votes WHERE release_id = $1 AND user_id = ANY($2)',
689 array($this->getID(), db_int_array_to_any_clause($voters)));
690 $db_count = db_fetch_array($res);
691 $numvotes = $db_count['count'];
693 /* check for invalid values */
694 if ($numvotes < 0 || $numvoters < $numvotes) {
695 $this->votes = array(-1, -1, 0);
697 $this->votes = array($numvotes, $numvoters,
698 (int)($numvotes * 100 / $numvoters + 0.5));
704 * canVote - check whether the current user can vote on
705 * items in this frs release
707 * @return bool true if they can
710 if (in_array(user_getid(), $this->getVoters())) {
717 * getVoters - get IDs of users that may vote on this frs_release
719 * @return array list of user IDs
721 function getVoters() {
722 if ($this->voters !== false) {
723 return $this->voters;
726 $this->voters = array();
727 if (($engine = RBACEngine::getInstance())
728 && ($voters = $engine->getUsersByAllowedAction('frs', $this->getID(), 'read'))
729 && (count($voters) > 0)) {
730 foreach ($voters as $voter) {
731 $voter_id = $voter->getID();
732 $this->voters[$voter_id] = $voter_id;
735 return $this->voters;