Indefero

Indefero Git Source Tree

Root/src/IDF/Project.php

1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of InDefero, an open source project management application.
6# Copyright (C) 2008-2011 Céondo Ltd and contributors.
7#
8# InDefero is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# InDefero is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * Base definition of a project.
26 *
27 * The issue management system can be used to manage several projects
28 * at the same time.
29 */
30class IDF_Project extends Pluf_Model
31{
32 public $_model = __CLASS__;
33 public $_extra_cache = array();
34 protected $_pconf = null;
35 /**
36 * Check if the project as one restricted tab.
37 *
38 * This is the cached information.
39 *
40 * @see self::isRestricted
41 */
42 protected $_isRestricted = null;
43
44 function init()
45 {
46 $this->_pconf = null;
47 $this->_extra_cache = array();
48 $this->_a['table'] = 'idf_projects';
49 $this->_a['model'] = __CLASS__;
50 $this->_a['cols'] = array(
51 // It is mandatory to have an "id" column.
52 'id' =>
53 array(
54 'type' => 'Pluf_DB_Field_Sequence',
55 'blank' => true,
56 ),
57 'name' =>
58 array(
59 'type' => 'Pluf_DB_Field_Varchar',
60 'blank' => false,
61 'size' => 250,
62 'verbose' => __('name'),
63 ),
64 'shortname' =>
65 array(
66 'type' => 'Pluf_DB_Field_Varchar',
67 'blank' => false,
68 'size' => 50,
69 'verbose' => __('short name'),
70 'help_text' => __('Used in the URL to access the project, must be short with only letters and numbers.'),
71 'unique' => true,
72 ),
73 'shortdesc' =>
74 array(
75 'type' => 'Pluf_DB_Field_Varchar',
76 'blank' => false,
77 'size' => 255,
78 'verbose' => __('short description'),
79 'help_text' => __('A one line description of the project.'),
80 ),
81 'description' =>
82 array(
83 'type' => 'Pluf_DB_Field_Text',
84 'blank' => false,
85 'size' => 250,
86 'verbose' => __('description'),
87 'help_text' => __('The description can be extended using the Markdown syntax.'),
88 ),
89 'tags' =>
90 array(
91 'type' => 'Pluf_DB_Field_Manytomany',
92 'blank' => true,
93 'model' => 'IDF_Tag',
94 'verbose' => __('labels'),
95 ),
96 'private' =>
97 array(
98 'type' => 'Pluf_DB_Field_Integer',
99 'blank' => false,
100 'verbose' => __('private'),
101 'default' => 0,
102 ),
103 'current_activity' =>
104 array(
105 'type' => 'Pluf_DB_Field_Foreignkey',
106 'model' => 'IDF_ProjectActivity',
107 'blank' => true,
108 'verbose' => __('current project activity'),
109 ),
110 );
111 $activityTable = $this->_con->pfx.'idf_projectactivities';
112 $tagTable = $this->_con->pfx.'idf_project_idf_tag_assoc';
113 $this->_a['views'] = array(
114 'join_activities_and_tags' =>
115 array(
116 'join' => 'LEFT JOIN '.$activityTable.' ON current_activity='.$activityTable.'.id '
117 .'LEFT JOIN '.$tagTable.' ON idf_project_id='.$this->getSqlTable().'.id',
118 'select' => $this->getSelect().', date, value',
119 'group' => $this->getSqlTable().'.id',
120 'props' => array(
121 'date' => 'current_activity_date',
122 'value' => 'current_activity_value'
123 ),
124 ),
125 );
126 }
127
128
129 /**
130 * String representation of the abstract.
131 */
132 function __toString()
133 {
134 return $this->name;
135 }
136
137 /**
138 * String ready for indexation.
139 */
140 function _toIndex()
141 {
142 return '';
143 }
144
145
146 function preSave($create=false)
147 {
148 if ($this->id == '') {
149 $this->creation_dtime = gmdate('Y-m-d H:i:s');
150 }
151 $this->modif_dtime = gmdate('Y-m-d H:i:s');
152 }
153
154 public static function getOr404($shortname)
155 {
156 $sql = new Pluf_SQL('shortname=%s', array(trim($shortname)));
157 $projects = Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen()));
158 if ($projects->count() != 1) {
159 throw new Pluf_HTTP_Error404(sprintf(__('Project "%s" not found.'),
160 $shortname));
161 }
162 return $projects[0];
163 }
164
165 /**
166 * Returns the number of open/closed issues.
167 *
168 * @param string Status ('open'), 'closed'
169 * @param IDF_Tag Subfilter with a label (null)
170 * @return int Count
171 */
172 public function getIssueCountByOwner($status='open')
173 {
174 switch ($status) {
175 case 'open':
176 $tags = implode(',', $this->getTagIdsByStatus('open'));
177 break;
178 case 'closed':
179 default:
180 $tags = implode(',', $this->getTagIdsByStatus('closed'));
181 break;
182 }
183 $sqlIssueTable = Pluf::factory('IDF_Issue')->getSqlTable();
184 $query = "SELECT uid AS id,COUNT(uid) AS nb
185FROM (
186 SELECT COALESCE(owner, -1) AS uid
187 FROM $sqlIssueTable
188 WHERE status IN ($tags)
189 ) AS ff
190GROUP BY uid";
191
192 $db = Pluf::db();
193 $dbData = $db->select($query);
194 $ownerStatistics = array();
195 foreach ($dbData as $k => $v) {
196 $key = ($v['id'] === '-1') ? null : $v['id'];
197 $ownerStatistics[$key] = (int)$v['nb'];
198 }
199
200 arsort($ownerStatistics);
201
202 return $ownerStatistics;
203 }
204
205 /**
206 * Returns the number of open/closed issues.
207 *
208 * @param string Status ('open'), 'closed'
209 * @param IDF_Tag Subfilter with a label (null)
210 * @param array Restrict further to a list of ids
211 * @return int Count
212 */
213 public function getIssueCountByStatus($status='open', $label=null, $ids=array())
214 {
215 switch ($status) {
216 case 'open':
217 $key = 'labels_issue_open';
218 $default = IDF_Form_IssueTrackingConf::init_open;
219 break;
220 case 'closed':
221 default:
222 $key = 'labels_issue_closed';
223 $default = IDF_Form_IssueTrackingConf::init_closed;
224 break;
225 }
226 $tags = array();
227 foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
228 $tags[] = (int)$tag->id;
229 }
230 if (count($tags) == 0) return array();
231 $sql = new Pluf_SQL(sprintf('project=%%s AND status IN (%s)', implode(', ', $tags)), array($this->id));
232 if (!is_null($label)) {
233 $sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
234 $sql->SAnd($sql2);
235 }
236 if (count($ids) > 0) {
237 $sql2 = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
238 $sql->SAnd($sql2);
239 }
240 $params = array('filter' => $sql->gen());
241 if (!is_null($label)) { $params['view'] = 'join_tags'; }
242 $gissue = new IDF_Issue();
243 return $gissue->getCount($params);
244 }
245
246 /**
247 * Get the tags for a specific list of issues.
248 *
249 * @param string Status ('open') or 'closed'
250 * @param array A list of issue ids
251 * @return array An array of tag objects
252 */
253 public function getTagsByIssues($issue_ids=array())
254 {
255 // make the below query always a valid one
256 if (count($issue_ids) == 0) $issue_ids[] = 0;
257
258 $assocTable = $this->_con->pfx.'idf_issue_idf_tag_assoc';
259 $query = sprintf(
260 'SELECT DISTINCT idf_tag_id FROM %s '.
261 'WHERE idf_issue_id IN (%s) '.
262 'GROUP BY idf_tag_id',
263 $assocTable, implode(',', $issue_ids)
264 );
265
266 $db = Pluf::db();
267 $dbData = $db->select($query);
268 $ids = array(0);
269 foreach ($dbData as $data) {
270 $ids[] = $data['idf_tag_id'];
271 }
272
273 $sql = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
274 $model = new IDF_Tag();
275 return $model->getList(array('filter' => $sql->gen()));
276 }
277
278 /**
279 * Get the open/closed tag ids as they are often used when doing
280 * listings.
281 *
282 * As this can be often used, the info are cached.
283 *
284 * @param string Status ('open') or 'closed'
285 * @param bool Force cache refresh (false)
286 * @return array Ids of the open/closed tags
287 */
288 public function getTagIdsByStatus($status='open', $cache_refresh=false)
289 {
290 if (!$cache_refresh
291 and isset($this->_extra_cache['getTagIdsByStatus-'.$status])) {
292 return $this->_extra_cache['getTagIdsByStatus-'.$status];
293 }
294 switch ($status) {
295 case 'open':
296 $key = 'labels_issue_open';
297 $default = IDF_Form_IssueTrackingConf::init_open;
298 break;
299 case 'closed':
300 default:
301 $key = 'labels_issue_closed';
302 $default = IDF_Form_IssueTrackingConf::init_closed;
303 break;
304 }
305 $tags = array();
306 foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
307 $tags[] = (int) $tag->id;
308 }
309 $this->_extra_cache['getTagIdsByStatus-'.$status] = $tags;
310 return $tags;
311 }
312
313 /**
314 * Convert the definition of tags in the configuration into the
315 * corresponding list of tags.
316 *
317 * @param string Configuration key where the tag is.
318 * @param string Default config if nothing in the db.
319 * @param string Default class.
320 * @return array List of tags
321 */
322 public function getTagsFromConfig($cfg_key, $default, $dclass='Other')
323 {
324 $conf = $this->getConf();
325 $tags = array();
326 foreach (preg_split("/\015\012|\015|\012/", $conf->getVal($cfg_key, $default), -1, PREG_SPLIT_NO_EMPTY) as $s) {
327 $_s = explode('=', $s, 2);
328 $v = trim($_s[0]);
329 $_v = explode(':', $v, 2);
330 if (count($_v) > 1) {
331 $class = trim($_v[0]);
332 $name = trim($_v[1]);
333 } else {
334 $name = trim($_s[0]);
335 $class = $dclass;
336 }
337 $tags[] = IDF_Tag::add($name, $this, $class);
338 }
339 return $tags;
340 }
341
342 /**
343 * Returns a list of relations which are available in this project as
344 * associative array. Each key-value pair marks a set of orthogonal
345 * relations. To ease processing, each of these pairs is included twice
346 * in the array, once as key1 => key2 and once as key2 => key1.
347 *
348 * @return array List of relation names
349 */
350 public function getRelationsFromConfig()
351 {
352 $conf = $this->getConf();
353 $rel = $conf->getVal('issue_relations', IDF_Form_IssueTrackingConf::init_relations);
354 $relations = array();
355 foreach (preg_split("/\015\012|\015|\012/", $rel, -1, PREG_SPLIT_NO_EMPTY) as $s) {
356 $verbs = preg_split("/\s*,\s*/", $s, 2);
357 if (count($verbs) == 1)
358 $relations += array($verbs[0] => $verbs[0]);
359 else
360 $relations += array($verbs[0] => $verbs[1], $verbs[1] => $verbs[0]);
361 }
362 return $relations;
363 }
364
365 /**
366 * Return membership data.
367 *
368 * The array has 3 keys: 'members', 'owners' and 'authorized'.
369 *
370 * The list of users is only taken using the row level permission
371 * table. That is, if you set a user as administrator, he will
372 * have the member and owner rights but will not appear in the
373 * lists.
374 *
375 * @param string Format ('objects'), 'string'.
376 * @return mixed Array of Pluf_User or newline separated list of logins.
377 */
378 public function getMembershipData($fmt='objects')
379 {
380 $mperm = Pluf_Permission::getFromString('IDF.project-member');
381 $operm = Pluf_Permission::getFromString('IDF.project-owner');
382 $aperm = Pluf_Permission::getFromString('IDF.project-authorized-user');
383 $grow = new Pluf_RowPermission();
384 $db =& Pluf::db();
385 $false = Pluf_DB_BooleanToDb(false, $db);
386 $sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
387 array('IDF_Project', $this->id, 'Pluf_User', $operm->id));
388 $owners = new Pluf_Template_ContextVars(array());
389 foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
390 if ($fmt == 'objects') {
391 $owners[] = Pluf::factory('Pluf_User', $row->owner_id);
392 } else {
393 $owners[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
394 }
395 }
396 $sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
397 array('IDF_Project', $this->id, 'Pluf_User', $mperm->id));
398 $members = new Pluf_Template_ContextVars(array());
399 foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
400 if ($fmt == 'objects') {
401 $members[] = Pluf::factory('Pluf_User', $row->owner_id);
402 } else {
403 $members[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
404 }
405 }
406 $authorized = new Pluf_Template_ContextVars(array());
407 if ($aperm != false) {
408 $sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
409 array('IDF_Project', $this->id, 'Pluf_User', $aperm->id));
410 foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
411 if ($fmt == 'objects') {
412 $authorized[] = Pluf::factory('Pluf_User', $row->owner_id);
413 } else {
414 $authorized[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
415 }
416 }
417 }
418 if ($fmt == 'objects') {
419 return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized));
420 } else {
421 return array('members' => implode("\n", (array) $members),
422 'owners' => implode("\n", (array) $owners),
423 'authorized' => implode("\n", (array) $authorized),
424 );
425 }
426 }
427
428 /**
429 * Generate the tag clouds.
430 *
431 * Return an array of tags sorted by class, then name. Each tag
432 * get the extra property 'nb_use' for the number of use in the
433 * project.
434 *
435 * @param string ('issues') 'closed_issues', 'wiki' or 'downloads'
436 * @return ArrayObject of IDF_Tag
437 */
438 public function getTagCloud($what='issues')
439 {
440 $tag_t = Pluf::factory('IDF_Tag')->getSqlTable();
441 if ($what == 'issues' or $what == 'closed_issues') {
442 $what_t = Pluf::factory('IDF_Issue')->getSqlTable();
443 $asso_t = $this->_con->pfx.'idf_issue_idf_tag_assoc';
444 if ($what == 'issues') {
445 $ostatus = $this->getTagIdsByStatus('open');
446 } else {
447 $ostatus = $this->getTagIdsByStatus('closed');
448 }
449 if (count($ostatus) == 0) $ostatus[] = 0;
450 $sql = sprintf('SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
451 'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
452 'LEFT JOIN '.$what_t.' ON idf_issue_id='.$what_t.'.id '."\n".
453 'WHERE idf_tag_id IS NOT NULL AND '.$what_t.'.status IN (%s) AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC',
454 implode(', ', $ostatus));
455 } elseif ($what == 'wiki') {
456 $dep_ids = IDF_Views_Wiki::getDeprecatedPagesIds($this);
457 $extra = '';
458 if (count($dep_ids)) {
459 $extra = ' AND idf_wiki_page_id NOT IN ('.implode(', ', $dep_ids).') ';
460 }
461 $what_t = Pluf::factory('IDF_Wiki_Page')->getSqlTable();
462 $asso_t = $this->_con->pfx.'idf_tag_idf_wiki_page_assoc';
463 $sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
464 'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
465 'LEFT JOIN '.$what_t.' ON idf_wiki_page_id='.$what_t.'.id '."\n".
466 'WHERE idf_tag_id IS NOT NULL '.$extra.' AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC';
467 } elseif ($what == 'downloads') {
468 $dep_ids = IDF_Views_Download::getDeprecatedFilesIds($this);
469 $extra = '';
470 if (count($dep_ids)) {
471 $extra = ' AND idf_upload_id NOT IN ('.implode(', ', $dep_ids).') ';
472 }
473 $what_t = Pluf::factory('IDF_Upload')->getSqlTable();
474 $asso_t = $this->_con->pfx.'idf_tag_idf_upload_assoc';
475 $sql = 'SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
476 'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
477 'LEFT JOIN '.$what_t.' ON idf_upload_id='.$what_t.'.id '."\n".
478 'WHERE idf_tag_id IS NOT NULL '.$extra.' AND '.$what_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id, '.$tag_t.'.class, '.$tag_t.'.name ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC';
479 }
480 $tags = array();
481 foreach ($this->_con->select($sql) as $idc) {
482 $tag = new IDF_Tag($idc['id']);
483 $tag->nb_use = $idc['nb_use'];
484 // group by class
485 if (!array_key_exists($tag->class, $tags)) {
486 $tags[$tag->class] = array();
487 }
488 $tags[$tag->class][] = $tag;
489 }
490 return new Pluf_Template_ContextVars($tags);
491 }
492
493 /**
494 * Get the repository size.
495 *
496 * @param bool Force to skip the cache (false)
497 * @return int Size in byte or -1 if not available
498 */
499 public function getRepositorySize($force=false)
500 {
501 $last_eval = $this->getConf()->getVal('repository_size_check_date', 0);
502 if (Pluf::f('idf_no_size_check', false) or
503 (!$force and $last_eval > time()-172800)) {
504 return $this->getConf()->getVal('repository_size', -1);
505 }
506 $this->getConf()->setVal('repository_size_check_date', time());
507 $scm = IDF_Scm::get($this);
508 $this->getConf()->setVal('repository_size', $scm->getRepositorySize());
509 return $this->getConf()->getVal('repository_size', -1);
510 }
511
512 /**
513 * Get the access url to the repository.
514 *
515 * This will return the right url based on the user.
516 *
517 * @param Pluf_User The user (null)
518 * @param string A specific commit to access
519 */
520 public function getSourceAccessUrl($user=null, $commit=null)
521 {
522 $right = $this->getConf()->getVal('source_access_rights', 'all');
523 if (($user == null or $user->isAnonymous())
524 and $right == 'all' and !$this->private) {
525 return $this->getRemoteAccessUrl($commit);
526 }
527 return $this->getWriteRemoteAccessUrl($user, $commit);
528 }
529
530
531 /**
532 * Get the remote access url to the repository.
533 *
534 * This will always return the anonymous access url.
535 *
536 * @param string A specific commit to access
537 */
538 public function getRemoteAccessUrl($commit=null)
539 {
540 $conf = $this->getConf();
541 $scm = $conf->getVal('scm', 'git');
542 $scms = Pluf::f('allowed_scm');
543 Pluf::loadClass($scms[$scm]);
544 return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
545 $this, $commit);
546 }
547
548 /**
549 * Get the remote write access url to the repository.
550 *
551 * Some SCM have a remote access URL to write which is not the
552 * same as the one to read. For example, you do a checkout with
553 * git-daemon and push with SSH.
554 *
555 * @param string A specific commit to access
556 */
557 public function getWriteRemoteAccessUrl($user,$commit=null)
558 {
559 $conf = $this->getConf();
560 $scm = $conf->getVal('scm', 'git');
561 $scms = Pluf::f('allowed_scm');
562 return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
563 $this, $user, $commit);
564 }
565
566 /**
567 * Get the web hook key.
568 *
569 * The goal is to get something predictable but from which one
570 * cannot reverse find the secret key.
571 */
572 public function getWebHookKey()
573 {
574 return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
575 }
576
577 /**
578 * Get the root name of the project scm
579 *
580 * @return string SCM root
581 */
582 public function getScmRoot()
583 {
584 $conf = $this->getConf();
585 $roots = array(
586 'git' => 'master',
587 'svn' => 'HEAD',
588 'mercurial' => 'tip',
589 'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
590 );
591 $scm = $conf->getVal('scm', 'git');
592 return $roots[$scm];
593 }
594
595 /**
596 * Check that the object belongs to the project or rise a 404
597 * error.
598 *
599 * By convention, all the objects belonging to a project have the
600 * 'project' property set, so this is easy to check.
601 *
602 * @param Pluf_Model
603 */
604 public function inOr404($obj)
605 {
606 if ($obj->project != $this->id) {
607 throw new Pluf_HTTP_Error404();
608 }
609 }
610
611 /**
612 * Utility function to get a configuration object.
613 *
614 * @return IDF_Conf
615 */
616 public function getConf()
617 {
618 if ($this->_pconf == null) {
619 $this->_pconf = new IDF_Conf();
620 $this->_pconf->setProject($this);
621 }
622 return $this->_pconf;
623 }
624
625 /**
626 * Magic overload that falls back to the values of the internal configuration
627 * if no getter / caller matched
628 *
629 * @param string $key
630 */
631 public function __get($key)
632 {
633 try {
634 return parent::__get($key);
635 }
636 catch (Exception $e) {
637 return $this->getConf()->getVal($key);
638 }
639 }
640
641 /**
642 * Get simple statistics about the project.
643 *
644 * This returns an associative array with number of tickets,
645 * number of downloads, etc.
646 *
647 * @return array Stats
648 */
649 public function getStats()
650 {
651 $stats = array();
652 $stats['total'] = 0;
653 $what = array('downloads' => 'IDF_Upload',
654 'reviews' => 'IDF_Review',
655 'issues' => 'IDF_Issue',
656 'docpages' => 'IDF_Wiki_Page',
657 'commits' => 'IDF_Commit',
658 );
659 foreach ($what as $key=>$m) {
660 $i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id));
661 $stats[$key] = $i;
662 $stats['total'] += $i;
663 }
664 /**
665 * [signal]
666 *
667 * IDF_Project::getStats
668 *
669 * [sender]
670 *
671 * IDF_Project
672 *
673 * [description]
674 *
675 * This signal allows an application to update the statistics
676 * array of a project. For example to add the on disk size
677 * of the repository if available.
678 *
679 * [parameters]
680 *
681 * array('project' => $project,
682 * 'stats' => $stats)
683 *
684 */
685 $params = array('project' => $this,
686 'stats' => $stats);
687 Pluf_Signal::send('IDF_Project::getStats',
688 'IDF_Project', $params);
689 return $stats;
690 }
691
692 /**
693 * Needs to be called when you update the memberships of a
694 * project.
695 *
696 * This will allow a plugin to, for example, update some access
697 * rights to a repository.
698 */
699 public function membershipsUpdated()
700 {
701 /**
702 * [signal]
703 *
704 * IDF_Project::membershipsUpdated
705 *
706 * [sender]
707 *
708 * IDF_Project
709 *
710 * [description]
711 *
712 * This signal allows an application to update the some access
713 * rights to a repository when the project memberships is
714 * updated.
715 *
716 * [parameters]
717 *
718 * array('project' => $project)
719 *
720 */
721 $params = array('project' => $this);
722 Pluf_Signal::send('IDF_Project::membershipsUpdated',
723 'IDF_Project', $params);
724 }
725
726 /**
727 * Needs to be called when you create a project.
728 *
729 * We cannot put it into the postSave call as the configuration of
730 * the project is not defined at that time.
731 */
732 function created()
733 {
734 /**
735 * [signal]
736 *
737 * IDF_Project::created
738 *
739 * [sender]
740 *
741 * IDF_Project
742 *
743 * [description]
744 *
745 * This signal allows an application to perform special
746 * operations at the creation of a project.
747 *
748 * [parameters]
749 *
750 * array('project' => $project)
751 *
752 */
753 $params = array('project' => $this);
754 Pluf_Signal::send('IDF_Project::created',
755 'IDF_Project', $params);
756 }
757
758 /**
759 * The delete() call do not like circular references and the
760 * IDF_Tag is creating some. We predelete to solve these issues.
761 */
762 public function preDelete()
763 {
764 /**
765 * [signal]
766 *
767 * IDF_Project::preDelete
768 *
769 * [sender]
770 *
771 * IDF_Project
772 *
773 * [description]
774 *
775 * This signal allows an application to perform special
776 * operations at the deletion of a project.
777 *
778 * [parameters]
779 *
780 * array('project' => $project)
781 *
782 */
783 $params = array('project' => $this);
784 Pluf_Signal::send('IDF_Project::preDelete',
785 'IDF_Project', $params);
786 $what = array('IDF_Upload', 'IDF_Review', 'IDF_Issue',
787 'IDF_Wiki_Page', 'IDF_Wiki_Resource',
788 'IDF_Commit', 'IDF_Tag',
789 );
790 foreach ($what as $m) {
791 foreach (Pluf::factory($m)->getList(array('filter' => 'project='.(int)$this->id)) as $item) {
792 $item->delete();
793 }
794 }
795 }
796
797 /**
798 * Check if the project has one restricted tab.
799 *
800 * @return bool
801 */
802 public function isRestricted()
803 {
804 if ($this->_isRestricted !== null) {
805 return $this->_isRestricted;
806 }
807 if ($this->private) {
808 $this->_isRestricted = true;
809 return true;
810 }
811 $tabs = array(
812 'source_access_rights',
813 'issues_access_rights',
814 'downloads_access_rights',
815 'wiki_access_rights',
816 'review_access_rights'
817 );
818 $conf = $this->getConf();
819 foreach ($tabs as $tab) {
820 if (!in_array($conf->getVal($tab, 'all'),
821 array('all', 'none'))) {
822 $this->_isRestricted = true;
823 return true;
824 }
825 }
826 $this->_isRestricted = false;
827 return false;
828 }
829
830 /**
831 * Returns an associative array of email addresses to notify about changes
832 * in a certain tab like 'issues', 'source', and so on.
833 *
834 * @param string $tab
835 * @return array Key is the email address, value is the preferred language setting
836 */
837 public function getNotificationRecipientsForTab($tab)
838 {
839 if (!in_array($tab, array('source', 'issues', 'downloads', 'wiki', 'review'))) {
840 throw new Exception(sprintf('unknown tab %s', $tab));
841 }
842
843 $conf = $this->getConf();
844 $recipients = array();
845 $membership_data = $this->getMembershipData();
846
847 if ($conf->getVal($tab.'_notification_owners_enabled', false)) {
848 foreach ($membership_data['owners'] as $owner) {
849 $recipients[$owner->email] = $owner->language;
850 }
851 }
852
853 if ($conf->getVal($tab.'_notification_members_enabled', false)) {
854 foreach ($membership_data['members'] as $member) {
855 $recipients[$member->email] = $member->language;
856 }
857 }
858
859 if ($conf->getVal($tab.'_notification_email_enabled', false)) {
860 $addresses = preg_split('/\s*,\s*/',
861 $conf->getVal($tab.'_notification_email', ''),
862 -1, PREG_SPLIT_NO_EMPTY);
863
864 // we use a default language setting for this plain list of
865 // addresses, but we ensure that we do not overwrite an existing
866 // address which might come with a proper setting already
867 $languages = Pluf::f('languages', array('en'));
868 foreach ($addresses as $address) {
869 if (array_key_exists($address, $recipients))
870 continue;
871 $recipients[$address] = $languages[0];
872 }
873 }
874
875 return $recipients;
876 }
877}
878

Archive Download this file