2014-06-25 08:44:48 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
2014-06-27 11:37:01 +04:00
import (
2014-07-03 00:42:16 +04:00
"errors"
2015-02-23 10:15:53 +03:00
"fmt"
2014-07-05 06:43:39 +04:00
"os"
2014-06-27 11:37:01 +04:00
"strings"
2015-09-07 20:58:23 +03:00
2016-02-04 21:03:34 +03:00
"github.com/Unknwon/com"
2015-09-07 20:58:23 +03:00
"github.com/go-xorm/xorm"
2014-06-27 11:37:01 +04:00
)
2014-07-03 00:42:16 +04:00
var (
2016-01-30 01:06:14 +03:00
ErrOrgNotExist = errors . New ( "Organization does not exist" )
ErrTeamNotExist = errors . New ( "Team does not exist" )
2014-07-03 00:42:16 +04:00
)
2014-12-13 04:30:32 +03:00
// IsOwnedBy returns true if given user is in the owner team.
func ( org * User ) IsOwnedBy ( uid int64 ) bool {
2014-06-30 00:30:41 +04:00
return IsOrganizationOwner ( org . Id , uid )
}
// IsOrgMember returns true if given user is member of organization.
func ( org * User ) IsOrgMember ( uid int64 ) bool {
2015-02-24 08:27:22 +03:00
return org . IsOrganization ( ) && IsOrganizationMember ( org . Id , uid )
2014-06-30 00:30:41 +04:00
}
2015-02-13 08:58:46 +03:00
func ( org * User ) getTeam ( e Engine , name string ) ( * Team , error ) {
return getTeam ( e , org . Id , name )
}
2014-08-16 12:21:17 +04:00
// GetTeam returns named team of organization.
func ( org * User ) GetTeam ( name string ) ( * Team , error ) {
2015-02-13 08:58:46 +03:00
return org . getTeam ( x , name )
}
func ( org * User ) getOwnerTeam ( e Engine ) ( * Team , error ) {
return org . getTeam ( e , OWNER_TEAM )
2014-08-16 12:21:17 +04:00
}
2014-06-28 08:40:07 +04:00
// GetOwnerTeam returns owner team of organization.
func ( org * User ) GetOwnerTeam ( ) ( * Team , error ) {
2015-02-13 08:58:46 +03:00
return org . getOwnerTeam ( x )
}
func ( org * User ) getTeams ( e Engine ) error {
return e . Where ( "org_id=?" , org . Id ) . Find ( & org . Teams )
2014-06-28 08:40:07 +04:00
}
2014-06-28 23:43:25 +04:00
// GetTeams returns all teams that belong to organization.
func ( org * User ) GetTeams ( ) error {
2015-02-13 08:58:46 +03:00
return org . getTeams ( x )
2014-06-28 23:43:25 +04:00
}
// GetMembers returns all members of organization.
func ( org * User ) GetMembers ( ) error {
2016-07-15 19:36:39 +03:00
ous , err := GetOrgUsersByOrgID ( org . Id )
2014-06-28 23:43:25 +04:00
if err != nil {
return err
}
org . Members = make ( [ ] * User , len ( ous ) )
for i , ou := range ous {
2015-08-08 17:43:14 +03:00
org . Members [ i ] , err = GetUserByID ( ou . Uid )
2014-06-28 23:43:25 +04:00
if err != nil {
return err
}
}
return nil
}
2014-08-15 14:29:41 +04:00
// AddMember adds new member to organization.
func ( org * User ) AddMember ( uid int64 ) error {
return AddOrgUser ( org . Id , uid )
}
// RemoveMember removes member from organization.
func ( org * User ) RemoveMember ( uid int64 ) error {
return RemoveOrgUser ( org . Id , uid )
}
2015-03-01 05:44:09 +03:00
func ( org * User ) removeOrgRepo ( e Engine , repoID int64 ) error {
return removeOrgRepo ( e , org . Id , repoID )
}
// RemoveOrgRepo removes all team-repository relations of organization.
func ( org * User ) RemoveOrgRepo ( repoID int64 ) error {
return org . removeOrgRepo ( x , repoID )
}
2014-06-27 11:37:01 +04:00
// CreateOrganization creates record of a new organization.
2015-03-27 00:11:47 +03:00
func CreateOrganization ( org , owner * User ) ( err error ) {
if err = IsUsableName ( org . Name ) ; err != nil {
return err
2014-06-27 11:37:01 +04:00
}
2015-02-23 02:24:49 +03:00
isExist , err := IsUserExist ( 0 , org . Name )
2014-06-27 11:37:01 +04:00
if err != nil {
2015-03-27 00:11:47 +03:00
return err
2014-06-27 11:37:01 +04:00
} else if isExist {
2015-03-27 00:11:47 +03:00
return ErrUserAlreadyExist { org . Name }
2014-06-27 11:37:01 +04:00
}
org . LowerName = strings . ToLower ( org . Name )
2015-12-15 01:06:54 +03:00
org . Rands = GetUserSalt ( )
org . Salt = GetUserSalt ( )
2015-09-06 17:08:14 +03:00
org . UseCustomAvatar = true
2015-12-11 23:48:02 +03:00
org . MaxRepoCreation = - 1
2014-06-27 11:37:01 +04:00
org . NumTeams = 1
org . NumMembers = 1
sess := x . NewSession ( )
2015-02-23 10:15:53 +03:00
defer sessionRelease ( sess )
2014-06-27 11:37:01 +04:00
if err = sess . Begin ( ) ; err != nil {
2015-03-27 00:11:47 +03:00
return err
2014-06-27 11:37:01 +04:00
}
if _ , err = sess . Insert ( org ) ; err != nil {
2015-03-27 00:11:47 +03:00
return fmt . Errorf ( "insert organization: %v" , err )
2014-07-05 06:43:39 +04:00
}
2015-09-06 17:08:14 +03:00
org . GenerateRandomAvatar ( )
// Add initial creator to organization and owner team.
if _ , err = sess . Insert ( & OrgUser {
Uid : owner . Id ,
OrgID : org . Id ,
IsOwner : true ,
NumTeams : 1 ,
} ) ; err != nil {
return fmt . Errorf ( "insert org-user relation: %v" , err )
}
2014-07-05 06:43:39 +04:00
2014-06-27 11:37:01 +04:00
// Create default owner team.
t := & Team {
2015-02-23 10:15:53 +03:00
OrgID : org . Id ,
2014-08-11 07:11:18 +04:00
LowerName : strings . ToLower ( OWNER_TEAM ) ,
2014-06-27 11:37:01 +04:00
Name : OWNER_TEAM ,
2015-02-09 14:36:33 +03:00
Authorize : ACCESS_MODE_OWNER ,
2014-06-27 11:37:01 +04:00
NumMembers : 1 ,
}
if _ , err = sess . Insert ( t ) ; err != nil {
2015-03-27 00:11:47 +03:00
return fmt . Errorf ( "insert owner team: %v" , err )
2014-06-27 11:37:01 +04:00
}
2015-09-06 17:08:14 +03:00
if _ , err = sess . Insert ( & TeamUser {
2014-06-27 11:37:01 +04:00
Uid : owner . Id ,
2015-02-23 10:15:53 +03:00
OrgID : org . Id ,
TeamID : t . ID ,
2015-09-06 17:08:14 +03:00
} ) ; err != nil {
2015-03-27 00:11:47 +03:00
return fmt . Errorf ( "insert team-user relation: %v" , err )
2015-02-23 10:15:53 +03:00
}
if err = os . MkdirAll ( UserPath ( org . Name ) , os . ModePerm ) ; err != nil {
2015-03-27 00:11:47 +03:00
return fmt . Errorf ( "create directory: %v" , err )
2014-06-27 11:37:01 +04:00
}
2015-03-27 00:11:47 +03:00
return sess . Commit ( )
2014-06-27 11:37:01 +04:00
}
2014-12-13 04:30:32 +03:00
// GetOrgByName returns organization by given name.
func GetOrgByName ( name string ) ( * User , error ) {
if len ( name ) == 0 {
return nil , ErrOrgNotExist
}
u := & User {
LowerName : strings . ToLower ( name ) ,
2016-03-11 23:33:12 +03:00
Type : USER_TYPE_ORGANIZATION ,
2014-12-13 04:30:32 +03:00
}
has , err := x . Get ( u )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrOrgNotExist
}
return u , nil
}
2014-08-28 18:29:00 +04:00
// CountOrganizations returns number of organizations.
func CountOrganizations ( ) int64 {
count , _ := x . Where ( "type=1" ) . Count ( new ( User ) )
return count
}
2015-09-25 20:54:52 +03:00
// Organizations returns number of organizations in given page.
func Organizations ( page , pageSize int ) ( [ ] * User , error ) {
orgs := make ( [ ] * User , 0 , pageSize )
return orgs , x . Limit ( pageSize , ( page - 1 ) * pageSize ) . Where ( "type=1" ) . Asc ( "id" ) . Find ( & orgs )
2014-08-29 16:50:43 +04:00
}
2014-06-28 08:40:07 +04:00
// DeleteOrganization completely and permanently deletes everything of organization.
func DeleteOrganization ( org * User ) ( err error ) {
if err := DeleteUser ( org ) ; err != nil {
return err
}
sess := x . NewSession ( )
2015-09-06 15:54:08 +03:00
defer sessionRelease ( sess )
2014-06-28 08:40:07 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2015-09-06 15:54:08 +03:00
if err = deleteBeans ( sess ,
& Team { OrgID : org . Id } ,
& OrgUser { OrgID : org . Id } ,
& TeamUser { OrgID : org . Id } ,
) ; err != nil {
return fmt . Errorf ( "deleteBeans: %v" , err )
2014-06-28 08:40:07 +04:00
}
2015-09-06 15:54:08 +03:00
if err = deleteUser ( sess , org ) ; err != nil {
return fmt . Errorf ( "deleteUser: %v" , err )
2014-06-28 08:40:07 +04:00
}
2015-09-06 15:54:08 +03:00
2014-06-28 08:40:07 +04:00
return sess . Commit ( )
}
2014-06-25 08:44:48 +04:00
// ________ ____ ___
// \_____ \_______ ____ | | \______ ___________
// / | \_ __ \/ ___\| | / ___// __ \_ __ \
// / | \ | \/ /_/ > | /\___ \\ ___/| | \/
// \_______ /__| \___ /|______//____ >\___ >__|
// \/ /_____/ \/ \/
// OrgUser represents an organization-user relation.
type OrgUser struct {
2015-02-23 10:15:53 +03:00
ID int64 ` xorm:"pk autoincr" `
2014-08-15 14:29:41 +04:00
Uid int64 ` xorm:"INDEX UNIQUE(s)" `
2015-02-23 10:15:53 +03:00
OrgID int64 ` xorm:"INDEX UNIQUE(s)" `
2014-06-25 08:44:48 +04:00
IsPublic bool
IsOwner bool
2014-08-24 17:09:05 +04:00
NumTeams int
2014-06-25 08:44:48 +04:00
}
2014-06-30 00:30:41 +04:00
// IsOrganizationOwner returns true if given user is in the owner team.
func IsOrganizationOwner ( orgId , uid int64 ) bool {
has , _ := x . Where ( "is_owner=?" , true ) . And ( "uid=?" , uid ) . And ( "org_id=?" , orgId ) . Get ( new ( OrgUser ) )
return has
}
// IsOrganizationMember returns true if given user is member of organization.
func IsOrganizationMember ( orgId , uid int64 ) bool {
has , _ := x . Where ( "uid=?" , uid ) . And ( "org_id=?" , orgId ) . Get ( new ( OrgUser ) )
return has
}
2014-12-07 04:22:48 +03:00
// IsPublicMembership returns true if given user public his/her membership.
2014-08-15 14:29:41 +04:00
func IsPublicMembership ( orgId , uid int64 ) bool {
has , _ := x . Where ( "uid=?" , uid ) . And ( "org_id=?" , orgId ) . And ( "is_public=?" , true ) . Get ( new ( OrgUser ) )
return has
}
2016-02-20 02:10:03 +03:00
func getOrgsByUserID ( sess * xorm . Session , userID int64 , showAll bool ) ( [ ] * User , error ) {
2016-01-31 00:51:11 +03:00
orgs := make ( [ ] * User , 0 , 10 )
2016-02-20 02:10:03 +03:00
if ! showAll {
sess . And ( "`org_user`.is_public=?" , true )
}
return orgs , sess . And ( "`org_user`.uid=?" , userID ) .
2016-01-31 00:51:11 +03:00
Join ( "INNER" , "`org_user`" , "`org_user`.org_id=`user`.id" ) . Find ( & orgs )
}
2016-02-15 04:36:03 +03:00
// GetOrgsByUserID returns a list of organizations that the given user ID
// has joined.
2016-02-20 02:10:03 +03:00
func GetOrgsByUserID ( userID int64 , showAll bool ) ( [ ] * User , error ) {
return getOrgsByUserID ( x . NewSession ( ) , userID , showAll )
2016-01-31 00:51:11 +03:00
}
2016-02-15 04:36:03 +03:00
// GetOrgsByUserIDDesc returns a list of organizations that the given user ID
// has joined, ordered descending by the given condition.
2016-02-20 02:10:03 +03:00
func GetOrgsByUserIDDesc ( userID int64 , desc string , showAll bool ) ( [ ] * User , error ) {
return getOrgsByUserID ( x . NewSession ( ) . Desc ( desc ) , userID , showAll )
2016-01-31 00:51:11 +03:00
}
2015-09-07 20:58:23 +03:00
func getOwnedOrgsByUserID ( sess * xorm . Session , userID int64 ) ( [ ] * User , error ) {
orgs := make ( [ ] * User , 0 , 10 )
return orgs , sess . Where ( "`org_user`.uid=?" , userID ) . And ( "`org_user`.is_owner=?" , true ) .
Join ( "INNER" , "`org_user`" , "`org_user`.org_id=`user`.id" ) . Find ( & orgs )
}
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID ( userID int64 ) ( [ ] * User , error ) {
sess := x . NewSession ( )
return getOwnedOrgsByUserID ( sess , userID )
}
// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by
2016-01-31 00:51:11 +03:00
// given user ID, ordered descending by the given condition.
2015-09-07 20:58:23 +03:00
func GetOwnedOrgsByUserIDDesc ( userID int64 , desc string ) ( [ ] * User , error ) {
sess := x . NewSession ( )
return getOwnedOrgsByUserID ( sess . Desc ( desc ) , userID )
}
2015-12-17 10:28:47 +03:00
// GetOrgUsersByUserID returns all organization-user relations by user ID.
func GetOrgUsersByUserID ( uid int64 , all bool ) ( [ ] * OrgUser , error ) {
2014-06-25 08:44:48 +04:00
ous := make ( [ ] * OrgUser , 0 , 10 )
2015-12-17 10:28:47 +03:00
sess := x . Where ( "uid=?" , uid )
if ! all {
// Only show public organizations
sess . And ( "is_public=?" , true )
}
err := sess . Find ( & ous )
2014-06-25 08:44:48 +04:00
return ous , err
}
2016-07-15 19:36:39 +03:00
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
func GetOrgUsersByOrgID ( orgID int64 ) ( [ ] * OrgUser , error ) {
2014-06-25 13:14:36 +04:00
ous := make ( [ ] * OrgUser , 0 , 10 )
2016-07-15 19:36:39 +03:00
err := x . Where ( "org_id=?" , orgID ) . Find ( & ous )
2014-06-25 13:14:36 +04:00
return ous , err
}
2014-08-15 14:29:41 +04:00
// ChangeOrgUserStatus changes public or private membership status.
func ChangeOrgUserStatus ( orgId , uid int64 , public bool ) error {
ou := new ( OrgUser )
has , err := x . Where ( "uid=?" , uid ) . And ( "org_id=?" , orgId ) . Get ( ou )
if err != nil {
return err
} else if ! has {
return nil
}
ou . IsPublic = public
2015-02-23 10:15:53 +03:00
_ , err = x . Id ( ou . ID ) . AllCols ( ) . Update ( ou )
2014-08-15 14:29:41 +04:00
return err
}
// AddOrgUser adds new user to given organization.
func AddOrgUser ( orgId , uid int64 ) error {
if IsOrganizationMember ( orgId , uid ) {
return nil
}
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
2014-08-24 17:09:05 +04:00
ou := & OrgUser {
Uid : uid ,
2015-02-23 10:15:53 +03:00
OrgID : orgId ,
2014-08-24 17:09:05 +04:00
}
2014-08-15 14:29:41 +04:00
if _ , err := sess . Insert ( ou ) ; err != nil {
sess . Rollback ( )
return err
} else if _ , err = sess . Exec ( "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?" , orgId ) ; err != nil {
sess . Rollback ( )
return err
}
return sess . Commit ( )
}
// RemoveOrgUser removes user from given organization.
func RemoveOrgUser ( orgId , uid int64 ) error {
ou := new ( OrgUser )
has , err := x . Where ( "uid=?" , uid ) . And ( "org_id=?" , orgId ) . Get ( ou )
if err != nil {
2015-03-18 04:51:39 +03:00
return fmt . Errorf ( "get org-user: %v" , err )
2014-08-15 14:29:41 +04:00
} else if ! has {
return nil
}
2015-08-08 17:43:14 +03:00
u , err := GetUserByID ( uid )
2014-08-24 17:09:05 +04:00
if err != nil {
2015-03-18 04:51:39 +03:00
return fmt . Errorf ( "GetUserById: %v" , err )
2014-08-24 17:09:05 +04:00
}
2015-08-08 17:43:14 +03:00
org , err := GetUserByID ( orgId )
2014-08-24 17:09:05 +04:00
if err != nil {
2015-03-18 04:51:39 +03:00
return fmt . Errorf ( "get organization: %v" , err )
} else if err = org . GetRepositories ( ) ; err != nil {
return fmt . Errorf ( "GetRepositories: %v" , err )
2014-08-24 17:09:05 +04:00
}
2014-08-16 12:21:17 +04:00
// Check if the user to delete is the last member in owner team.
if IsOrganizationOwner ( orgId , uid ) {
t , err := org . GetOwnerTeam ( )
if err != nil {
return err
}
if t . NumMembers == 1 {
2015-03-18 04:51:39 +03:00
return ErrLastOrgOwner { UID : uid }
2014-08-16 12:21:17 +04:00
}
}
2014-08-15 14:29:41 +04:00
sess := x . NewSession ( )
2015-03-18 04:51:39 +03:00
defer sessionRelease ( sess )
2014-08-15 14:29:41 +04:00
if err := sess . Begin ( ) ; err != nil {
return err
}
2015-02-23 10:15:53 +03:00
if _ , err := sess . Id ( ou . ID ) . Delete ( ou ) ; err != nil {
2014-08-15 14:29:41 +04:00
return err
2015-03-18 04:51:39 +03:00
} else if _ , err = sess . Exec ( "UPDATE `user` SET num_members=num_members-1 WHERE id=?" , orgId ) ; err != nil {
2014-08-15 14:29:41 +04:00
return err
}
2014-08-24 17:09:05 +04:00
// Delete all repository accesses.
2015-03-18 04:51:39 +03:00
access := & Access { UserID : u . Id }
2014-08-24 17:09:05 +04:00
for _ , repo := range org . Repos {
2015-08-08 17:43:14 +03:00
access . RepoID = repo . ID
2014-08-24 17:09:05 +04:00
if _ , err = sess . Delete ( access ) ; err != nil {
return err
2015-08-08 17:43:14 +03:00
} else if err = watchRepo ( sess , u . Id , repo . ID , false ) ; err != nil {
2014-08-27 12:39:36 +04:00
return err
2014-08-24 17:09:05 +04:00
}
}
// Delete member in his/her teams.
2015-03-18 04:51:39 +03:00
teams , err := getUserTeams ( sess , org . Id , u . Id )
2014-08-24 17:09:05 +04:00
if err != nil {
return err
}
2015-03-18 04:51:39 +03:00
for _ , t := range teams {
2015-02-23 10:15:53 +03:00
if err = removeTeamMember ( sess , org . Id , t . ID , u . Id ) ; err != nil {
2014-08-24 17:09:05 +04:00
return err
}
}
2014-08-15 14:29:41 +04:00
return sess . Commit ( )
}
2015-03-01 05:44:09 +03:00
func removeOrgRepo ( e Engine , orgID , repoID int64 ) error {
_ , err := e . Delete ( & TeamRepo {
OrgID : orgID ,
RepoID : repoID ,
} )
return err
}
// RemoveOrgRepo removes all team-repository relations of given organization.
func RemoveOrgRepo ( orgID , repoID int64 ) error {
return removeOrgRepo ( x , orgID , repoID )
}
2016-01-31 13:46:04 +03:00
2016-02-04 20:08:25 +03:00
// GetUserRepositories gets all repositories of an organization,
// that the user with the given userID has access to.
func ( org * User ) GetUserRepositories ( userID int64 ) ( err error ) {
2016-03-04 21:08:47 +03:00
teams := make ( [ ] * Team , 0 , org . NumTeams )
2016-05-07 03:02:36 +03:00
if err = x . Sql ( ` SELECT team . id FROM team
2016-03-04 21:08:47 +03:00
INNER JOIN team_user ON team_user . team_id = team . id
WHERE team_user . org_id = ? AND team_user . uid = ? ` , org . Id , userID ) . Find ( & teams ) ; err != nil {
2016-03-01 22:39:28 +03:00
return fmt . Errorf ( "get teams: %v" , err )
2016-01-31 13:46:04 +03:00
}
2016-02-04 21:03:34 +03:00
teamIDs := make ( [ ] string , len ( teams ) )
for i := range teams {
teamIDs [ i ] = com . ToStr ( teams [ i ] . ID )
2016-01-31 13:46:04 +03:00
}
2016-01-31 21:13:39 +03:00
if len ( teamIDs ) == 0 {
// user has no team but "IN ()" is invalid SQL
2016-02-04 21:03:34 +03:00
teamIDs = append ( teamIDs , "-1" ) // there is no repo with id=-1
2016-01-31 21:13:39 +03:00
}
2016-03-04 21:08:47 +03:00
repos := make ( [ ] * Repository , 0 , 5 )
2016-03-10 07:18:39 +03:00
if err = x . Sql ( fmt . Sprintf ( ` SELECT repository . * FROM repository
2016-03-04 21:08:47 +03:00
INNER JOIN team_repo ON team_repo . repo_id = repository . id
2016-03-10 07:18:39 +03:00
WHERE ( repository . owner_id = ? AND repository . is_private = ? ) OR team_repo . team_id IN ( % s )
GROUP BY repository . id ` , strings . Join ( teamIDs , "," ) ) , org . Id , false ) . Find ( & repos ) ; err != nil {
2016-03-01 22:39:28 +03:00
return fmt . Errorf ( "get repositories: %v" , err )
2016-01-31 13:46:04 +03:00
}
2016-03-04 21:08:47 +03:00
org . Repos = repos
2016-01-31 13:46:04 +03:00
2016-02-04 21:03:34 +03:00
// FIXME: should I change this value inside method,
// or only in location of caller where it's really needed?
2016-01-31 16:28:42 +03:00
org . NumRepos = len ( org . Repos )
2016-02-04 21:03:34 +03:00
return nil
2016-01-31 13:46:04 +03:00
}
2016-02-04 20:08:25 +03:00
// GetTeams returns all teams that belong to organization,
// and that the user has joined.
2016-02-04 21:03:34 +03:00
func ( org * User ) GetUserTeams ( userID int64 ) error {
2016-03-05 00:00:00 +03:00
teams := make ( [ ] * Team , 0 , 5 )
if err := x . Sql ( ` SELECT team . * FROM team
INNER JOIN team_user ON team_user . team_id = team . id
WHERE team_user . org_id = ? AND team_user . uid = ? ` ,
org . Id , userID ) . Find ( & teams ) ; err != nil {
return fmt . Errorf ( "get teams: %v" , err )
2016-01-31 16:28:42 +03:00
}
2016-05-07 03:02:36 +03:00
org . Teams = teams
2016-02-04 21:03:34 +03:00
// FIXME: should I change this value inside method,
// or only in location of caller where it's really needed?
2016-01-31 16:28:42 +03:00
org . NumTeams = len ( org . Teams )
2016-02-04 21:03:34 +03:00
return nil
2016-01-31 16:28:42 +03:00
}