2008-07-25 08:26:05 +00:00
< ? php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
2008-07-25 08:28:35 +00:00
# Copyright (C) 2008 Céondo Ltd and contributors.
2008-07-25 08:26:05 +00:00
#
2008-07-25 08:28:35 +00:00
# InDefero is free software; you can redistribute it and/or modify
2008-07-25 08:26:05 +00:00
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
2008-07-25 08:28:35 +00:00
# InDefero is distributed in the hope that it will be useful,
2008-07-25 08:26:05 +00:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Base definition of a project .
*
* The issue management system can be used to manage several projects
* at the same time .
*/
class IDF_Project extends Pluf_Model
{
public $_model = __CLASS__ ;
2008-07-28 18:31:23 +00:00
public $_extra_cache = array ();
2008-09-02 13:51:57 +00:00
protected $_pconf = null ;
2008-07-25 08:26:05 +00:00
function init ()
{
2008-09-02 13:51:57 +00:00
$this -> _pconf = null ;
2008-07-28 18:31:23 +00:00
$this -> _extra_cache = array ();
2008-07-25 08:26:05 +00:00
$this -> _a [ 'table' ] = 'idf_projects' ;
$this -> _a [ 'model' ] = __CLASS__ ;
$this -> _a [ 'cols' ] = array (
// It is mandatory to have an "id" column.
'id' =>
array (
'type' => 'Pluf_DB_Field_Sequence' ,
'blank' => true ,
),
'name' =>
array (
'type' => 'Pluf_DB_Field_Varchar' ,
'blank' => false ,
'size' => 250 ,
'verbose' => __ ( 'name' ),
),
'shortname' =>
array (
'type' => 'Pluf_DB_Field_Varchar' ,
'blank' => false ,
'size' => 50 ,
'verbose' => __ ( 'short name' ),
'help_text' => __ ( 'Used in the url to access the project, must be short with only letters and numbers.' ),
'unique' => true ,
),
2008-12-02 12:22:01 +00:00
'shortdesc' =>
array (
'type' => 'Pluf_DB_Field_Varchar' ,
'blank' => false ,
'size' => 255 ,
'verbose' => __ ( 'short description' ),
'help_text' => __ ( 'A one line description of the project.' ),
),
2008-07-25 08:26:05 +00:00
'description' =>
array (
'type' => 'Pluf_DB_Field_Text' ,
'blank' => false ,
'size' => 250 ,
'verbose' => __ ( 'description' ),
'help_text' => __ ( 'The description can be extended using the markdown syntax.' ),
),
2008-11-21 12:19:02 +00:00
'private' =>
array (
'type' => 'Pluf_DB_Field_Integer' ,
'blank' => false ,
'verbose' => __ ( 'private' ),
'default' => 0 ,
),
2008-07-25 08:26:05 +00:00
);
}
/**
* String representation of the abstract .
*/
function __toString ()
{
return $this -> name ;
}
/**
* String ready for indexation .
*/
function _toIndex ()
{
return '' ;
}
2008-09-02 13:51:57 +00:00
2008-08-07 21:12:24 +00:00
function preSave ( $create = false )
2008-07-25 08:26:05 +00:00
{
if ( $this -> id == '' ) {
$this -> creation_dtime = gmdate ( 'Y-m-d H:i:s' );
}
$this -> modif_dtime = gmdate ( 'Y-m-d H:i:s' );
}
public static function getOr404 ( $shortname )
{
$sql = new Pluf_SQL ( 'shortname=%s' , array ( trim ( $shortname )));
$projects = Pluf :: factory ( __CLASS__ ) -> getList ( array ( 'filter' => $sql -> gen ()));
if ( $projects -> count () != 1 ) {
throw new Pluf_HTTP_Error404 ( sprintf ( __ ( 'Project "%s" not found.' ),
$shortname ));
}
return $projects [ 0 ];
}
/**
* Returns the number of open / closed issues .
*
* @ param string Status ( 'open' ), 'closed'
* @ param IDF_Tag Subfilter with a label ( null )
* @ return int Count
*/
public function getIssueCountByStatus ( $status = 'open' , $label = null )
{
switch ( $status ) {
case 'open' :
$key = 'labels_issue_open' ;
$default = IDF_Form_IssueTrackingConf :: init_open ;
break ;
case 'closed' :
default :
$key = 'labels_issue_closed' ;
$default = IDF_Form_IssueTrackingConf :: init_closed ;
break ;
}
$tags = array ();
foreach ( $this -> getTagsFromConfig ( $key , $default , 'Status' ) as $tag ) {
$tags [] = ( int ) $tag -> id ;
}
if ( count ( $tags ) == 0 ) return array ();
$sql = new Pluf_SQL ( sprintf ( 'project=%%s AND status IN (%s)' , implode ( ', ' , $tags )), array ( $this -> id ));
if ( ! is_null ( $label )) {
$sql2 = new Pluf_SQL ( 'idf_tag_id=%s' , array ( $label -> id ));
$sql -> SAnd ( $sql2 );
}
$params = array ( 'filter' => $sql -> gen ());
if ( ! is_null ( $label )) { $params [ 'view' ] = 'join_tags' ; }
$gissue = new IDF_Issue ();
return $gissue -> getCount ( $params );
}
/**
* Get the open / closed tag ids as they are often used when doing
* listings .
*
2008-07-28 18:31:23 +00:00
* As this can be often used , the info are cached .
*
2008-07-25 08:26:05 +00:00
* @ param string Status ( 'open' ) or 'closed'
2008-07-28 18:31:23 +00:00
* @ param bool Force cache refresh ( false )
2008-07-25 08:26:05 +00:00
* @ return array Ids of the open / closed tags
*/
2008-07-28 18:31:23 +00:00
public function getTagIdsByStatus ( $status = 'open' , $cache_refresh = false )
2008-07-25 08:26:05 +00:00
{
2008-07-28 18:31:23 +00:00
if ( ! $cache_refresh
and isset ( $this -> _extra_cache [ 'getTagIdsByStatus-' . $status ])) {
return $this -> _extra_cache [ 'getTagIdsByStatus-' . $status ];
}
2008-07-25 08:26:05 +00:00
switch ( $status ) {
case 'open' :
$key = 'labels_issue_open' ;
$default = IDF_Form_IssueTrackingConf :: init_open ;
break ;
case 'closed' :
default :
$key = 'labels_issue_closed' ;
$default = IDF_Form_IssueTrackingConf :: init_closed ;
break ;
}
$tags = array ();
foreach ( $this -> getTagsFromConfig ( $key , $default , 'Status' ) as $tag ) {
$tags [] = ( int ) $tag -> id ;
}
2008-07-28 18:31:23 +00:00
$this -> _extra_cache [ 'getTagIdsByStatus-' . $status ] = $tags ;
2008-07-25 08:26:05 +00:00
return $tags ;
}
/**
* Convert the definition of tags in the configuration into the
* corresponding list of tags .
*
* @ param string Configuration key where the tag is .
* @ param string Default config if nothing in the db .
* @ param string Default class .
* @ return array List of tags
*/
public function getTagsFromConfig ( $cfg_key , $default , $dclass = 'Other' )
{
2008-09-02 13:51:57 +00:00
$conf = $this -> getConf ();
2008-07-25 08:26:05 +00:00
$tags = array ();
foreach ( preg_split ( " / \015 \012 | \015 | \012 / " , $conf -> getVal ( $cfg_key , $default ), - 1 , PREG_SPLIT_NO_EMPTY ) as $s ) {
$_s = split ( '=' , $s , 2 );
$v = trim ( $_s [ 0 ]);
$_v = split ( ':' , $v , 2 );
if ( count ( $_v ) > 1 ) {
$class = trim ( $_v [ 0 ]);
$name = trim ( $_v [ 1 ]);
} else {
$name = trim ( $_s [ 0 ]);
$class = $dclass ;
}
$tags [] = IDF_Tag :: add ( $name , $this , $class );
}
return $tags ;
}
/**
* Return membership data .
*
2008-11-21 12:19:02 +00:00
* The array has 3 keys : 'members' , 'owners' and 'authorized' .
2008-07-25 08:26:05 +00:00
*
* The list of users is only taken using the row level permission
* table . That is , if you set a user as administrator , he will
* have the member and owner rights but will not appear in the
* lists .
*
* @ param string Format ( 'objects' ), 'string' .
* @ return mixed Array of Pluf_User or newline separated list of logins .
*/
public function getMembershipData ( $fmt = 'objects' )
{
$mperm = Pluf_Permission :: getFromString ( 'IDF.project-member' );
$operm = Pluf_Permission :: getFromString ( 'IDF.project-owner' );
2008-11-21 12:19:02 +00:00
$aperm = Pluf_Permission :: getFromString ( 'IDF.project-authorized-user' );
2008-07-25 08:26:05 +00:00
$grow = new Pluf_RowPermission ();
$db =& Pluf :: db ();
$false = Pluf_DB_BooleanToDb ( false , $db );
$sql = new Pluf_SQL ( 'model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative=' . $false ,
array ( 'IDF_Project' , $this -> id , 'Pluf_User' , $operm -> id ));
2008-09-05 12:38:50 +00:00
$owners = new Pluf_Template_ContextVars ( array ());
2008-07-25 08:26:05 +00:00
foreach ( $grow -> getList ( array ( 'filter' => $sql -> gen ())) as $row ) {
if ( $fmt == 'objects' ) {
$owners [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id );
} else {
$owners [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id ) -> login ;
}
}
2008-08-01 20:03:56 +00:00
$sql = new Pluf_SQL ( 'model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative=' . $false ,
2008-07-25 08:26:05 +00:00
array ( 'IDF_Project' , $this -> id , 'Pluf_User' , $mperm -> id ));
2008-09-05 12:38:50 +00:00
$members = new Pluf_Template_ContextVars ( array ());
2008-07-25 08:26:05 +00:00
foreach ( $grow -> getList ( array ( 'filter' => $sql -> gen ())) as $row ) {
if ( $fmt == 'objects' ) {
$members [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id );
} else {
$members [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id ) -> login ;
}
}
2008-11-21 12:19:02 +00:00
$authorized = new Pluf_Template_ContextVars ( array ());
if ( $aperm != false ) {
$sql = new Pluf_SQL ( 'model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative=' . $false ,
array ( 'IDF_Project' , $this -> id , 'Pluf_User' , $aperm -> id ));
foreach ( $grow -> getList ( array ( 'filter' => $sql -> gen ())) as $row ) {
if ( $fmt == 'objects' ) {
$authorized [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id );
} else {
$authorized [] = Pluf :: factory ( 'Pluf_User' , $row -> owner_id ) -> login ;
}
}
}
2008-07-25 08:26:05 +00:00
if ( $fmt == 'objects' ) {
2008-11-21 12:19:02 +00:00
return new Pluf_Template_ContextVars ( array ( 'members' => $members , 'owners' => $owners , 'authorized' => $authorized ));
2008-07-25 08:26:05 +00:00
} else {
2008-09-12 14:23:43 +00:00
return array ( 'members' => implode ( " \n " , ( array ) $members ),
2008-11-21 12:19:02 +00:00
'owners' => implode ( " \n " , ( array ) $owners ),
'authorized' => implode ( " \n " , ( array ) $authorized ),
);
2008-07-25 08:26:05 +00:00
}
}
/**
* Generate the tag clouds .
*
2008-11-22 22:51:23 +00:00
* Return an array of tags sorted by class , then name . Each tag
* get the extra property 'nb_use' for the number of use in the
* project .
2008-07-25 08:26:05 +00:00
*
2008-11-25 20:07:51 +00:00
* @ param string ( 'issues' ) 'closed_issues' , 'wiki' or 'downloads'
2008-07-25 08:26:05 +00:00
* @ return ArrayObject of IDF_Tag
*/
2008-08-06 20:19:46 +00:00
public function getTagCloud ( $what = 'issues' )
2008-07-25 08:26:05 +00:00
{
$tag_t = Pluf :: factory ( 'IDF_Tag' ) -> getSqlTable ();
2008-08-08 18:34:40 +00:00
if ( $what == 'issues' or $what == 'closed_issues' ) {
2008-08-06 20:19:46 +00:00
$what_t = Pluf :: factory ( 'IDF_Issue' ) -> getSqlTable ();
$asso_t = $this -> _con -> pfx . 'idf_issue_idf_tag_assoc' ;
2008-08-08 18:34:40 +00:00
if ( $what == 'issues' ) {
$ostatus = $this -> getTagIdsByStatus ( 'open' );
} else {
$ostatus = $this -> getTagIdsByStatus ( 'closed' );
}
2008-08-06 20:19:46 +00:00
if ( count ( $ostatus ) == 0 ) $ostatus [] = 0 ;
$sql = sprintf ( 'SELECT ' . $tag_t . '.id AS id, COUNT(*) AS nb_use FROM ' . $tag_t . ' ' . " \n " .
2008-07-25 08:26:05 +00:00
'LEFT JOIN ' . $asso_t . ' ON idf_tag_id=' . $tag_t . '.id ' . " \n " .
2008-08-06 20:19:46 +00:00
'LEFT JOIN ' . $what_t . ' ON idf_issue_id=' . $what_t . '.id ' . " \n " .
'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' ,
2008-07-25 08:26:05 +00:00
implode ( ', ' , $ostatus ));
2008-11-25 20:07:51 +00:00
} elseif ( $what == 'wiki' ) {
$dep_ids = IDF_Views_Wiki :: getDeprecatedPagesIds ( $this );
$extra = '' ;
if ( count ( $dep_ids )) {
$extra = ' AND idf_wikipage_id NOT IN (' . implode ( ', ' , $dep_ids ) . ') ' ;
}
$what_t = Pluf :: factory ( 'IDF_WikiPage' ) -> getSqlTable ();
$asso_t = $this -> _con -> pfx . 'idf_tag_idf_wikipage_assoc' ;
$sql = 'SELECT ' . $tag_t . '.id AS id, COUNT(*) AS nb_use FROM ' . $tag_t . ' ' . " \n " .
'LEFT JOIN ' . $asso_t . ' ON idf_tag_id=' . $tag_t . '.id ' . " \n " .
'LEFT JOIN ' . $what_t . ' ON idf_wikipage_id=' . $what_t . '.id ' . " \n " .
'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' ;
2008-09-02 21:31:43 +00:00
} elseif ( $what == 'downloads' ) {
$dep_ids = IDF_Views_Download :: getDeprecatedFilesIds ( $this );
$extra = '' ;
2008-11-25 20:07:51 +00:00
if ( count ( $dep_ids )) {
2008-09-02 21:31:43 +00:00
$extra = ' AND idf_upload_id NOT IN (' . implode ( ', ' , $dep_ids ) . ') ' ;
}
2008-08-06 20:19:46 +00:00
$what_t = Pluf :: factory ( 'IDF_Upload' ) -> getSqlTable ();
$asso_t = $this -> _con -> pfx . 'idf_tag_idf_upload_assoc' ;
$sql = 'SELECT ' . $tag_t . '.id AS id, COUNT(*) AS nb_use FROM ' . $tag_t . ' ' . " \n " .
'LEFT JOIN ' . $asso_t . ' ON idf_tag_id=' . $tag_t . '.id ' . " \n " .
'LEFT JOIN ' . $what_t . ' ON idf_upload_id=' . $what_t . '.id ' . " \n " .
2008-09-02 21:31:43 +00:00
'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' ;
2008-08-06 20:19:46 +00:00
}
2008-07-25 08:26:05 +00:00
$tags = array ();
foreach ( $this -> _con -> select ( $sql ) as $idc ) {
$tag = new IDF_Tag ( $idc [ 'id' ]);
$tag -> nb_use = $idc [ 'nb_use' ];
$tags [] = $tag ;
}
2008-09-05 13:53:13 +00:00
return new Pluf_Template_ContextVars ( $tags );
2008-07-25 08:26:05 +00:00
}
2008-08-04 19:24:07 +00:00
/**
2008-09-02 13:51:57 +00:00
* Get the remote access url to the repository .
2008-08-04 19:24:07 +00:00
*
*/
2008-09-02 13:51:57 +00:00
public function getRemoteAccessUrl ()
2008-08-04 19:24:07 +00:00
{
2008-09-02 13:51:57 +00:00
$conf = $this -> getConf ();
$scm = $conf -> getVal ( 'scm' , 'git' );
$scms = Pluf :: f ( 'allowed_scm' );
return call_user_func ( array ( $scms [ $scm ], 'getRemoteAccessUrl' ),
$this );
2008-08-29 17:50:10 +00:00
}
2009-01-26 16:58:58 +00:00
/**
* Get the remote write access url to the repository .
*
* Some SCM have a remote access URL to write which is not the
* same as the one to read . For example , you do a checkout with
* git - daemon and push with SSH .
*/
public function getWriteRemoteAccessUrl ()
{
$conf = $this -> getConf ();
$scm = $conf -> getVal ( 'scm' , 'git' );
$scms = Pluf :: f ( 'allowed_scm' );
return call_user_func ( array ( $scms [ $scm ], 'getWriteRemoteAccessUrl' ),
$this );
}
2008-08-29 17:50:10 +00:00
/**
* Get the root name of the project scm
*
* @ return string SCM root
*/
public function getScmRoot ()
{
2008-09-02 13:51:57 +00:00
$conf = $this -> getConf ();
2008-11-23 16:45:00 +00:00
$roots = array (
'git' => 'master' ,
'svn' => 'HEAD' ,
'mercurial' => 'tip'
);
2008-08-29 17:50:10 +00:00
$scm = $conf -> getVal ( 'scm' , 'git' );
return $roots [ $scm ];
}
2008-08-05 17:58:21 +00:00
/**
* Check that the object belongs to the project or rise a 404
* error .
*
* By convention , all the objects belonging to a project have the
* 'project' property set , so this is easy to check .
*
* @ param Pluf_Model
*/
public function inOr404 ( $obj )
{
if ( $obj -> project != $this -> id ) {
throw new Pluf_HTTP_Error404 ();
}
}
2008-09-02 13:51:57 +00:00
/**
* Utility function to get a configuration object .
*
* @ return IDF_Conf
*/
public function getConf ()
{
if ( $this -> _pconf == null ) {
$this -> _pconf = new IDF_Conf ();
$this -> _pconf -> setProject ( $this );
}
return $this -> _pconf ;
}
2008-11-30 23:36:27 +00:00
/**
* Needs to be called when you update the memberships of a
* project .
*
* This will allow a plugin to , for example , update some access
* rights to a repository .
*/
public function membershipsUpdated ()
{
/**
* [ signal ]
*
* IDF_Project :: membershipsUpdated
*
* [ sender ]
*
* IDF_Project
*
* [ description ]
*
* This signal allows an application to update the some access
* rights to a repository when the project memberships is
* updated .
*
* [ parameters ]
*
* array ( 'project' => $project )
*
*/
$params = array ( 'project' => $this );
Pluf_Signal :: send ( 'IDF_Project::membershipsUpdated' ,
'IDF_Project' , $params );
}
/**
* Needs to be called when you create a project .
*
* We cannot put it into the postSave call as the configuration of
* the project is not defined at that time .
*/
function created ()
{
/**
* [ signal ]
*
* IDF_Project :: created
*
* [ sender ]
*
* IDF_Project
*
* [ description ]
*
* This signal allows an application to perform special
* operations at the creation of a project .
*
* [ parameters ]
*
* array ( 'project' => $project )
*
*/
$params = array ( 'project' => $this );
Pluf_Signal :: send ( 'IDF_Project::created' ,
'IDF_Project' , $params );
}
2008-11-23 16:45:00 +00:00
}