2017-07-26 13:33:32 +03:00
< ? php
/**
2019-12-03 21:57:53 +03:00
*
*
2020-04-29 12:57:22 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2019-12-03 21:57:53 +03:00
* @ author Georg Ehrke < oc . list @ georgehrke . com >
2020-08-24 15:54:25 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2020-12-30 16:07:05 +03:00
* @ author John Molakvoæ ( skjnldsv ) < skjnldsv @ protonmail . com >
2019-12-03 21:57:53 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2020-03-31 11:49:10 +03:00
* @ author Thomas Citharel < nextcloud @ tcit . fr >
2017-07-26 13:33:32 +03:00
*
2019-12-03 21:57:53 +03:00
* @ license GNU AGPL version 3 or any later version
2017-07-26 13:33:32 +03:00
*
2019-12-03 21:57:53 +03:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
2017-07-26 13:33:32 +03:00
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2019-12-03 21:57:53 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2017-07-26 13:33:32 +03:00
* GNU Affero General Public License for more details .
*
2019-12-03 21:57:53 +03:00
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
2017-07-26 13:33:32 +03:00
*
*/
2019-11-22 22:52:10 +03:00
2017-07-26 13:33:32 +03:00
namespace OCA\DAV\Command ;
use OCA\DAV\CalDAV\CalDavBackend ;
use OCA\DAV\CalDAV\Calendar ;
2018-03-08 16:44:02 +03:00
use OCP\IConfig ;
2017-07-26 13:33:32 +03:00
use OCP\IGroupManager ;
use OCP\IL10N ;
use OCP\IUserManager ;
2019-01-03 20:31:10 +03:00
use OCP\Share\IManager as IShareManager ;
2017-07-26 13:33:32 +03:00
use Symfony\Component\Console\Command\Command ;
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Output\OutputInterface ;
use Symfony\Component\Console\Style\SymfonyStyle ;
class MoveCalendar extends Command {
/** @var IUserManager */
2018-03-08 16:44:02 +03:00
private $userManager ;
2017-07-26 13:33:32 +03:00
2019-01-03 20:31:10 +03:00
/** @var IGroupManager */
2017-07-26 13:33:32 +03:00
private $groupManager ;
2019-01-03 20:31:10 +03:00
/** @var IShareManager */
private $shareManager ;
2018-03-08 16:44:02 +03:00
/** @var IConfig $config */
private $config ;
2017-07-26 13:33:32 +03:00
/** @var IL10N */
2018-03-08 16:44:02 +03:00
private $l10n ;
2017-07-26 13:33:32 +03:00
/** @var SymfonyStyle */
private $io ;
/** @var CalDavBackend */
2018-03-08 16:44:02 +03:00
private $calDav ;
2017-07-26 13:33:32 +03:00
2020-04-10 17:54:27 +03:00
public const URI_USERS = 'principals/users/' ;
2017-07-26 13:33:32 +03:00
/**
* @ param IUserManager $userManager
* @ param IGroupManager $groupManager
2019-01-03 20:31:10 +03:00
* @ param IShareManager $shareManager
2018-03-08 16:44:02 +03:00
* @ param IConfig $config
2017-07-26 13:33:32 +03:00
* @ param IL10N $l10n
2018-03-08 16:44:02 +03:00
* @ param CalDavBackend $calDav
2017-07-26 13:33:32 +03:00
*/
2020-04-10 17:51:06 +03:00
public function __construct (
2018-03-08 16:44:02 +03:00
IUserManager $userManager ,
IGroupManager $groupManager ,
2019-01-03 20:31:10 +03:00
IShareManager $shareManager ,
2018-03-08 16:44:02 +03:00
IConfig $config ,
IL10N $l10n ,
CalDavBackend $calDav
) {
2017-07-26 13:33:32 +03:00
parent :: __construct ();
$this -> userManager = $userManager ;
$this -> groupManager = $groupManager ;
2019-01-03 20:31:10 +03:00
$this -> shareManager = $shareManager ;
2018-03-08 16:44:02 +03:00
$this -> config = $config ;
2017-07-26 13:33:32 +03:00
$this -> l10n = $l10n ;
2018-03-08 16:44:02 +03:00
$this -> calDav = $calDav ;
2017-07-26 13:33:32 +03:00
}
protected function configure () {
$this
-> setName ( 'dav:move-calendar' )
-> setDescription ( 'Move a calendar from an user to another' )
-> addArgument ( 'name' ,
InputArgument :: REQUIRED ,
'Name of the calendar to move' )
2019-01-15 15:59:54 +03:00
-> addArgument ( 'sourceuid' ,
2017-07-26 13:33:32 +03:00
InputArgument :: REQUIRED ,
'User who currently owns the calendar' )
2019-01-15 15:59:54 +03:00
-> addArgument ( 'destinationuid' ,
2017-07-26 13:33:32 +03:00
InputArgument :: REQUIRED ,
'User who will receive the calendar' )
2020-12-08 12:22:46 +03:00
-> addOption ( 'force' , 'f' , InputOption :: VALUE_NONE , " Force the migration by removing existing shares and renaming calendars in case of conflicts " );
2017-07-26 13:33:32 +03:00
}
2020-06-26 16:12:11 +03:00
protected function execute ( InputInterface $input , OutputInterface $output ) : int {
2019-01-15 15:59:54 +03:00
$userOrigin = $input -> getArgument ( 'sourceuid' );
$userDestination = $input -> getArgument ( 'destinationuid' );
2017-07-26 13:33:32 +03:00
$this -> io = new SymfonyStyle ( $input , $output );
if ( ! $this -> userManager -> userExists ( $userOrigin )) {
throw new \InvalidArgumentException ( " User < $userOrigin > is unknown. " );
}
if ( ! $this -> userManager -> userExists ( $userDestination )) {
throw new \InvalidArgumentException ( " User < $userDestination > is unknown. " );
}
$name = $input -> getArgument ( 'name' );
2020-12-08 12:22:46 +03:00
$newName = null ;
2017-07-26 13:33:32 +03:00
2018-03-08 16:44:02 +03:00
$calendar = $this -> calDav -> getCalendarByUri ( self :: URI_USERS . $userOrigin , $name );
2017-07-26 13:33:32 +03:00
if ( null === $calendar ) {
2018-03-08 16:44:02 +03:00
throw new \InvalidArgumentException ( " User < $userOrigin > has no calendar named < $name >. You can run occ dav:list-calendars to list calendars URIs for this user. " );
2017-07-26 13:33:32 +03:00
}
2020-12-08 12:22:46 +03:00
// Calendar already exists
if ( $this -> calendarExists ( $userDestination , $name )) {
if ( $input -> getOption ( 'force' )) {
// Try to find a suitable name
$newName = $this -> getNewCalendarName ( $userDestination , $name );
// If we didn't find a suitable value after all the iterations, give up
if ( $this -> calendarExists ( $userDestination , $newName )) {
throw new \InvalidArgumentException ( " Unable to find a suitable calendar name for < $userDestination > with initial name < $name >. " );
}
} else {
throw new \InvalidArgumentException ( " User < $userDestination > already has a calendar named < $name >. " );
}
2017-07-26 13:33:32 +03:00
}
2020-12-08 12:22:46 +03:00
$hadShares = $this -> checkShares ( $calendar , $userOrigin , $userDestination , $input -> getOption ( 'force' ));
if ( $hadShares ) {
/**
* Warn that share links have changed if there are shares
*/
$this -> io -> note ([
" Please note that moving calendar " . $calendar [ 'uri' ] . " from user < $userOrigin > to < $userDestination > has caused share links to change. " ,
" Sharees will need to change \" example.com/remote.php/dav/calendars/uid/ " . $calendar [ 'uri' ] . " _shared_by_ $userOrigin\ " to \ " example.com/remote.php/dav/calendars/uid/ " . $newName ? : $calendar [ 'uri' ] . " _shared_by_ $userDestination\ " "
]);
}
2017-07-26 13:33:32 +03:00
2020-12-08 12:22:46 +03:00
$this -> calDav -> moveCalendar ( $name , self :: URI_USERS . $userOrigin , self :: URI_USERS . $userDestination , $newName );
2017-07-26 13:33:32 +03:00
2020-12-08 12:22:46 +03:00
$this -> io -> success ( " Calendar < $name > was moved from user < $userOrigin > to < $userDestination > " . ( $newName ? " as < $newName > " : '' ));
2020-06-26 16:12:11 +03:00
return 0 ;
2017-07-26 13:33:32 +03:00
}
2020-12-08 12:22:46 +03:00
/**
* Check if the calendar exists for user
*
* @ param string $userDestination
* @ param string $name
* @ return bool
*/
protected function calendarExists ( string $userDestination , string $name ) : bool {
return null !== $this -> calDav -> getCalendarByUri ( self :: URI_USERS . $userDestination , $name );
}
/**
* Try to find a suitable new calendar name that
* doesn ' t exists for the provided user
*
* @ param string $userDestination
* @ param string $name
* @ return string
*/
protected function getNewCalendarName ( string $userDestination , string $name ) : string {
$increment = 1 ;
$newName = $name . '-' . $increment ;
while ( $increment <= 10 ) {
$this -> io -> writeln ( " Trying calendar name < $newName > " , OutputInterface :: VERBOSITY_VERBOSE );
if ( ! $this -> calendarExists ( $userDestination , $newName )) {
// New name is good to go
$this -> io -> writeln ( " Found proper new calendar name < $newName > " , OutputInterface :: VERBOSITY_VERBOSE );
break ;
}
$newName = $name . '-' . $increment ;
$increment ++ ;
}
return $newName ;
}
2017-07-26 13:33:32 +03:00
/**
2019-01-15 15:59:54 +03:00
* Check that moving the calendar won ' t break shares
2017-07-26 13:33:32 +03:00
*
2019-01-15 15:59:54 +03:00
* @ param array $calendar
* @ param string $userOrigin
* @ param string $userDestination
2017-07-26 13:33:32 +03:00
* @ param bool $force
2020-12-08 12:22:46 +03:00
* @ return bool had any shares or not
* @ throws \InvalidArgumentException
2017-07-26 13:33:32 +03:00
*/
2020-12-08 12:22:46 +03:00
private function checkShares ( array $calendar , string $userOrigin , string $userDestination , bool $force = false ) : bool {
2018-03-08 16:44:02 +03:00
$shares = $this -> calDav -> getShares ( $calendar [ 'id' ]);
2017-07-26 13:33:32 +03:00
foreach ( $shares as $share ) {
2021-01-12 12:15:48 +03:00
[, $prefix , $userOrGroup ] = explode ( '/' , $share [ 'href' ], 3 );
2019-01-15 15:59:54 +03:00
/**
* Check that user destination is member of the groups which whom the calendar was shared
* If we ask to force the migration , the share with the group is dropped
*/
if ( $this -> shareManager -> shareWithGroupMembersOnly () === true && 'groups' === $prefix && ! $this -> groupManager -> isInGroup ( $userDestination , $userOrGroup )) {
if ( $force ) {
$this -> calDav -> updateShares ( new Calendar ( $this -> calDav , $calendar , $this -> l10n , $this -> config ), [], [ 'href' => 'principal:principals/groups/' . $userOrGroup ]);
} else {
throw new \InvalidArgumentException ( " User < $userDestination > is not part of the group < $userOrGroup > with whom the calendar < " . $calendar [ 'uri' ] . " > was shared. You may use -f to move the calendar while deleting this share. " );
}
}
/**
* Check that calendar isn ' t already shared with user destination
*/
if ( $userOrGroup === $userDestination ) {
2017-07-26 13:33:32 +03:00
if ( $force ) {
2019-01-15 15:59:54 +03:00
$this -> calDav -> updateShares ( new Calendar ( $this -> calDav , $calendar , $this -> l10n , $this -> config ), [], [ 'href' => 'principal:principals/users/' . $userOrGroup ]);
2017-07-26 13:33:32 +03:00
} else {
2019-01-15 15:59:54 +03:00
throw new \InvalidArgumentException ( " The calendar < " . $calendar [ 'uri' ] . " > is already shared to user < $userDestination >.You may use -f to move the calendar while deleting this share. " );
2017-07-26 13:33:32 +03:00
}
}
}
2020-12-08 12:22:46 +03:00
return count ( $shares ) > 0 ;
2017-07-26 13:33:32 +03:00
}
}