3 * FusionForge file release system
5 * Copyright 2002, Tim Perdue/GForge, LLC
6 * Copyright 2009, Roland Mas
7 * Copyright (C) 2011-2012 Alain Peyrat - Alcatel-Lucent
8 * Copyright 2011, Franck Villaume - Capgemini
9 * Copyright 2012-2014, Franck Villaume - TrivialDev
11 * This file is part of FusionForge. FusionForge is free software;
12 * you can redistribute it and/or modify it under the terms of the
13 * GNU General Public License as published by the Free Software
14 * Foundation; either version 2 of the Licence, or (at your option)
17 * FusionForge is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License along
23 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 require_once $gfcommon.'include/FFError.class.php';
28 require_once $gfcommon.'frs/FRSRelease.class.php';
29 require_once $gfcommon.'include/MonitorElement.class.php';
31 $FRSPACKAGE_OBJ = array();
34 * get_frs_packages - get all FRS packages for a specific project
39 function get_frs_packages($Group) {
41 $res = db_query_params('SELECT * FROM frs_package WHERE group_id=$1',
42 array($Group->getID()));
43 if (db_numrows($res) > 0) {
44 while($arr = db_fetch_array($res)) {
45 $ps[] = new FRSPackage($Group, $arr['package_id'], $arr);
52 * Gets a FRSPackage object from the given package id
54 * @param array $package_id the DB handle if passed in (optional)
56 * @return object the FRSPackage object
58 function frspackage_get_object($package_id, $data = false) {
59 global $FRSPACKAGE_OBJ;
60 if (!isset($FRSPACKAGE_OBJ['_'.$package_id.'_'])) {
62 //the db result handle was passed in
64 $res = db_query_params('SELECT * FROM frs_package WHERE package_id=$1',
66 if (db_numrows($res) < 1) {
69 $data = db_fetch_array($res);
71 $Group = group_get_object($data['group_id']);
72 $FRSPACKAGE_OBJ['_'.$package_id.'_'] = new FRSPackage($Group, $data['package_id'], $data);
74 return $FRSPACKAGE_OBJ['_'.$package_id.'_'];
78 * frspackage_get_groupid - get the project id from a package id
80 * @param int $package_id the package id
81 * @return int the project id
83 function frspackage_get_groupid($package_id) {
84 $res = db_query_params('SELECT group_id FROM frs_package WHERE package_id=$1',
86 if (!$res || db_numrows($res) < 1) {
89 $arr = db_fetch_array($res);
90 return $arr['group_id'];
93 class FRSPackage extends FFError {
96 * Associative array of data from db.
98 * @var array $data_array.
101 var $package_releases;
106 * @var object $Group.
112 * @param bool $package_id
115 function __construct(&$Group, $package_id = false, $arr = false) {
116 parent::__construct();
117 if (!$Group || !is_object($Group)) {
118 $this->setError(_('Invalid Project'));
121 if ($Group->isError()) {
122 $this->setError('FRSPackage: '.$Group->getErrorMessage());
125 $this->Group =& $Group;
128 if (!$arr || !is_array($arr)) {
129 $this->fetchData($package_id);
131 $this->data_array =& $arr;
132 if ($this->data_array['group_id'] != $this->Group->getID()) {
133 $this->setError(_('group_id in db result does not match Group Object'));
134 $this->data_array = null;
137 // Add an is_public check here
144 * create - create a new FRSPackage in the database.
146 * @param string $name Name of the package
147 * @param int $status Status ID. Default is 1 => Active
148 * @return bool success.
150 function create($name, $status = 1) {
151 if (strlen($name) < 3) {
152 $this->setError(_('FRSPackage Name Must Be At Least 3 Characters'));
155 if (!util_is_valid_filename($name)) {
156 $this->setError(_('Package Name can only be alphanumeric'));
158 if (!forge_check_perm('frs_admin', $this->Group->getID(), 'admin')) {
159 $this->setPermissionDeniedError();
163 $res = db_query_params('SELECT * FROM frs_package WHERE group_id = $1 AND name = $2',
164 array($this->Group->getID(),
165 htmlspecialchars($name)));
166 if (db_numrows($res)) {
167 $this->setError(_('Error Adding Package')._(': ')._('Name Already Exists'));
172 $result = db_query_params('INSERT INTO frs_package(group_id, name, status_id) VALUES ($1, $2, $3)',
173 array($this->Group->getID(),
174 htmlspecialchars($name),
177 $this->setError(_('Error Adding Package')._(': ').db_error());
181 $this->package_id = db_insertid($result, 'frs_package', 'package_id');
182 if (!$this->fetchData($this->package_id)) {
187 //make groupdir if it doesn't exist
188 $groupdir = forge_get_config('upload_dir').'/'.$this->Group->getUnixName();
189 if (!is_dir($groupdir)) {
193 $newdirlocation = $groupdir.'/'.$this->getFileName();
194 if (!is_dir($newdirlocation)) {
195 @mkdir($newdirlocation);
198 // this 2 should normally silently fail (because it's called with the apache user) but if it's root calling the create() method, then the owner and group for the directory should be changed
199 @chown($newdirlocation, forge_get_config('apache_user'));
200 @chgrp($newdirlocation, forge_get_config('apache_group'));
203 $this->Group->normalizeAllRoles();
204 $this->sendNotice(true);
211 * fetchData - re-fetch the data for this Package from the database.
213 * @param int $package_id The package_id.
214 * @return bool success.
216 function fetchData($package_id) {
217 $res = db_query_params('SELECT * FROM frs_package WHERE package_id = $1 AND group_id = $2',
218 array($package_id, $this->Group->getID()));
219 if (!$res || db_numrows($res) < 1) {
220 $this->setError(_('Invalid package_id'));
223 $this->data_array = db_fetch_array($res);
224 db_free_result($res);
229 * getGroup - get the Group object this FRSPackage is associated with.
231 * @return object The Group object.
233 function &getGroup() {
238 * getID - get this package_id.
240 * @return int The id of this package.
243 return $this->data_array['package_id'];
247 * getName - get the name of this package.
249 * @return string The name of this package.
252 return $this->data_array['name'];
256 * getFileName - get the filename of this package.
258 * @return string The name of this package.
260 function getFileName() {
261 return util_secure_filename($this->data_array['name']);
265 * getStatus - get the status of this package.
267 * @return int The status.
269 function getStatus() {
270 return $this->data_array['status_id'];
274 * getStatusName - get the status name of this package based on his status_id.
276 * @return string The status name.
278 function getStatusName() {
279 $res = db_query_params('SELECT * FROM frs_status', array());
280 while ($arr = db_fetch_array($res)) {
281 if ($arr['status_id'] == $this->getStatus()) {
289 * isPublic - whether non-group-members can view.
291 * @return bool is_public.
293 function isPublic() {
294 $ra = RoleAnonymous::getInstance();
295 return $ra->hasPermission('frs', $this->getID(), 'read');
298 function getPublicLabel() {
299 if ($this->isPublic()) {
306 * setMonitor - Add the current user to the list of people monitoring this package.
308 * @return bool success.
310 function setMonitor() {
311 if (!session_loggedin()) {
312 $this->setError(_('You can only monitor if you are logged in.'));
315 $MonitorElementObject = new MonitorElement('frspackage');
316 if (!$MonitorElementObject->enableMonitoringByUserId($this->getID(), user_getid())) {
317 $this->setError($MonitorElementObject->getErrorMessage());
324 * stopMonitor - Remove the current user from the list of people monitoring this package.
326 * @return bool success.
328 function stopMonitor() {
329 if (!session_loggedin()) {
330 $this->setError(_('You can only monitor if you are logged in.'));
333 $MonitorElementObject = new MonitorElement('frspackage');
334 if (!$MonitorElementObject->disableMonitoringByUserId($this->getID(), user_getid())) {
335 $this->setError($MonitorElementObject->getErrorMessage());
341 function clearMonitor() {
342 $MonitorElementObject = new MonitorElement('frspackage');
343 if (!$MonitorElementObject->clearMonitor($this->getID())) {
344 $this->setError($MonitorElementObject->getErrorMessage());
351 * getMonitorCount - Get the count of people monitoring this package
353 * @return int the count
355 function getMonitorCount() {
356 $MonitorElementObject = new MonitorElement('frspackage');
357 $getMonitorCounterInteger = $MonitorElementObject->getMonitorCounterInteger($this->getID());
358 if ($getMonitorCounterInteger !== false) {
359 return $getMonitorCounterInteger;
361 $this->setError($MonitorElementObject->getErrorMessage());
366 * isMonitoring - Is the current user in the list of people monitoring this package.
368 * @return bool is_monitoring.
370 function isMonitoring() {
371 if (!session_loggedin()) {
374 return $this->isMonitoredBy(user_getid());
377 function isMonitoredBy($userid = 'ALL') {
378 $MonitorElementObject = new MonitorElement('frspackage');
379 if ( $userid == 'ALL' ) {
380 return $MonitorElementObject->isMonitoredByAny($this->getID());
382 return $MonitorElementObject->isMonitoredByUserId($this->getID(), $userid);
387 * getMonitorIDs - Return an array of user_id's of the list of people monitoring this package.
389 * @return array The array of user_id's.
391 function getMonitorIDs() {
392 $MonitorElementObject = new MonitorElement('frspackage');
393 return $MonitorElementObject->getMonitorUsersIdsInArray($this->getID());
397 * update - update an FRSPackage in the database.
399 * @param string $name The name of this package.
400 * @param int $status The status_id of this package from frs_status table.
401 * @return bool success.
403 function update($name, $status) {
404 if (strlen($name) < 3) {
405 $this->setError(_('FRSPackage Name Must Be At Least 3 Characters'));
409 if (!forge_check_perm('frs', $this->getID(), 'admin')) {
410 $this->setPermissionDeniedError();
413 if($this->getName() != htmlspecialchars($name)) {
414 $res = db_query_params ('SELECT * FROM frs_package WHERE group_id=$1 AND name=$2',
415 array ($this->Group->getID(),
416 htmlspecialchars($name))) ;
417 if (db_numrows($res)) {
418 $this->setError(_('Error Updating Package')._(': ')._('Name Already Exists'));
423 $res = db_query_params('UPDATE frs_package SET name=$1, status_id=$2 WHERE group_id=$3 AND package_id=$4',
424 array (htmlspecialchars($name),
426 $this->Group->getID(),
428 if (!$res || db_affected_rows($res) < 1) {
429 $this->setError(_('Error On Update')._(': ').db_error());
434 $olddirname = $this->getFileName();
435 if(!$this->fetchData($this->getID())){
436 $this->setError(_('Error Updating Package')._(': ')._("Couldn't fetch data"));
440 $newdirname = $this->getFileName();
441 $olddirlocation = forge_get_config('upload_dir').'/'.$this->Group->getUnixName().'/'.$olddirname;
442 $newdirlocation = forge_get_config('upload_dir').'/'.$this->Group->getUnixName().'/'.$newdirname;
444 if($olddirname != $newdirname) {
445 if(is_dir($newdirlocation)) {
446 $this->setError(_('Error Updating Package')._(': ')._('Directory Already Exists'));
450 if(!@rename($olddirlocation,$newdirlocation)) {
451 $this->setError(_('Error Updating Package')._(': ')._("Couldn't rename dir"));
458 $this->createReleaseFilesAsZip($this->getNewestReleaseID());
464 * getReleases - gets Release objects for all the releases in this package.
466 * @param bool $include_hidden
467 * @return array Array of FRSRelease Objects.
469 function &getReleases($include_hidden = true) {
470 if (!is_array($this->package_releases) || count($this->package_releases) < 1) {
471 $this->package_releases=array();
472 $res = db_query_params('SELECT * FROM frs_release WHERE package_id=$1 ORDER BY release_date DESC',
473 array($this->getID()));
474 while ($arr = db_fetch_array($res)) {
475 if ($include_hidden) {
476 $this->package_releases[] = $this->newFRSRelease($arr['release_id'], $arr);
478 if ($arr['status_id'] == 1) {
479 $this->package_releases[] = $this->newFRSRelease($arr['release_id'], $arr);
484 return $this->package_releases;
488 * newFRSRelease - generates a FRSRelease (allows overloading by subclasses)
490 * @param string FRS release identifier
491 * @param array fetched data from the DB
492 * @return FRSRelease new FRSFile object.
494 protected function newFRSRelease($release_id, $data) {
495 return new FRSRelease($this,$release_id, $data);
499 * delete - delete this package and all its related data.
501 * @param bool $sure I'm Sure.
502 * @param bool $really_sure I'm REALLY sure.
505 function delete($sure, $really_sure) {
506 if (!$sure || !$really_sure) {
507 $this->setMissingParamsError(_('Please tick all checkboxes.'));
510 if (!forge_check_perm('frs', $this->getID(), 'admin')) {
511 $this->setPermissionDeniedError();
514 $r =& $this->getReleases();
515 for ($i = 0; $i<count($r); $i++) {
516 if (!is_object($r[$i]) || $r[$i]->isError() || !$r[$i]->delete($sure, $really_sure)) {
517 $this->setError(_('Release Error')._(': ').$r[$i]->getName()._(': ').$r[$i]->getErrorMessage());
521 $dir = forge_get_config('upload_dir').'/'.
522 $this->Group->getUnixName() . '/' .
523 $this->getFileName().'/';
525 // double-check we're not trying to remove root dir
526 if (util_is_root_dir($dir)) {
527 $this->setError(_('Package delete error: trying to delete root dir'));
534 $this->clearMonitor();
535 db_query_params('DELETE FROM frs_package WHERE package_id=$1 AND group_id=$2',
536 array ($this->getID(),
537 $this->Group->getID()));
542 * getNewestReleaseID - return the newest release_id of a package
543 * The newest release is the release with the highest ID
545 * @return int release id
547 public function getNewestReleaseID() {
548 $result = db_query_params('SELECT MAX(release_id) AS release_id FROM frs_release WHERE package_id = $1',
549 array($this->getID()));
551 if ($result && db_numrows($result) == 1) {
552 $row = db_fetch_array($result);
553 return $row['release_id'];
555 $this->setError(_('No valid max release id'));
560 public function getReleaseZipPath($release) {
561 return forge_get_config('upload_dir').'/'.$this->Group->getUnixName().'/'.$this->getFileName().'/'.$this->getReleaseZipName($release);
564 public function getReleaseZipName($release) {
565 $frsr = frsrelease_get_object($release);
566 return $this->getFileName().'-'.$frsr->getName().'.zip';
569 public function getNewestReleaseZipName() {
570 return $this->getFileName().'-latest.zip';
574 * createReleaseFilesAsZip - create the Zip Archive of the release
576 * @param int release id.
577 * @return bool true on success even if the php ZipArchive does not exist
579 public function createReleaseFilesAsZip($release_id) {
580 if ($release_id && class_exists('ZipArchive')) {
581 $zip = new ZipArchive();
582 $zipPath = $this->getReleaseZipPath($release_id);
583 $release = frsrelease_get_object($release_id);
584 $filesPath = forge_get_config('upload_dir').'/'.$this->Group->getUnixName().'/'.$this->getFileName().'/'.$release->getFileName();
585 if (!$zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
586 $this->setError(_('Cannot open the file archive')._(': ').$zipPath.'.');
589 $files = $release->getFiles();
590 foreach ($files as $f) {
591 $filePath = $filesPath.'/'.$f->getName();
592 if ($zip->addFile($filePath, $f->getName()) !== true) {
593 $this->setError(_('Cannot add file to the file archive')._(': ').$zipPath.'.');
597 if ($zip->close() !== true) {
598 $this->setError(_('Cannot close the file archive')._(': ').$zipPath.'.');
605 public function deleteReleaseFilesAsZip($release_id) {
606 if (file_exists($this->getReleaseZipPath($release_id))) {
607 unlink($this->getReleaseZipPath($release_id));
613 * sendNotice - Notifies of package actions
615 * @param bool true = new package (default value)
618 function sendNotice($new = true) {
619 $BCC = $this->Group->getFRSEmailAddress();
620 if ($this->isMonitoredBy('ALL')) {
621 $BCC .= $this->getMonitoredUserEmailAddress();
623 if (strlen($BCC) > 0) {
624 $session = session_get_user();
626 $status = _('New Package');
628 $status = _('Updated Package').' '._('by').' ' . $session->getRealName();
630 $subject = '['.$this->Group->getPublicName().'] '.$status.' - '.$this->getName();
631 $body = _('Project')._(': ').$this->Group->getPublicName()."\n";
632 $body .= _('Package')._(': ').$this->getName()."\n";
633 $body .= "\n\n-------------------------------------------------------\n".
634 _('For more info, visit')._(':').
635 "\n\n" . util_make_url('/frs/?group_id='.$this->Group->getID());
637 $BCCarray = explode(',',$BCC);
638 foreach ($BCCarray as $dest_email) {
639 util_send_message($dest_email, $subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('FRS'));
646 * getMonitoredUserEmailAddress - get the email addresses of users who monitor this file
648 * @return string The list of emails comma separated
650 function getMonitoredUserEmailAddress() {
651 $MonitorElementObject = new MonitorElement('frspackage');
652 return $MonitorElementObject->getAllEmailsInCommatSeparated($this->getID());
658 // c-file-style: "bsd"