2012-02-04 00:32:06 +04:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* ownCloud
|
|
|
|
*
|
|
|
|
* @author Tom Needham
|
|
|
|
* @copyright 2012 Tom Needham tom@owncloud.com
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 3 of the License, or any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public
|
|
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-20 00:44:20 +04:00
|
|
|
* provides an interface to migrate users and whole ownclouds
|
2012-02-04 00:32:06 +04:00
|
|
|
*/
|
|
|
|
class OC_Migrate{
|
2012-04-14 16:31:48 +04:00
|
|
|
|
|
|
|
|
2012-03-13 20:21:17 +04:00
|
|
|
// Array of OC_Migration_Provider objects
|
2012-02-04 01:00:12 +04:00
|
|
|
static private $providers=array();
|
2012-03-13 20:21:17 +04:00
|
|
|
// User id of the user to import/export
|
2012-03-10 22:18:58 +04:00
|
|
|
static private $uid=false;
|
2012-03-14 03:09:43 +04:00
|
|
|
// Holds the ZipArchive object
|
|
|
|
static private $zip=false;
|
2012-03-17 01:09:36 +04:00
|
|
|
// Stores the type of export
|
|
|
|
static private $exporttype=false;
|
|
|
|
// Array of temp files to be deleted after zip creation
|
|
|
|
static private $tmpfiles=array();
|
2012-03-20 00:44:20 +04:00
|
|
|
// Holds the db object
|
|
|
|
static private $MDB2=false;
|
|
|
|
// Schema db object
|
|
|
|
static private $schema=false;
|
|
|
|
// Path to the sqlite db
|
|
|
|
static private $dbpath=false;
|
|
|
|
// Holds the path to the zip file
|
|
|
|
static private $zippath=false;
|
|
|
|
// Holds the OC_Migration_Content object
|
|
|
|
static private $content=false;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-02-04 00:32:06 +04:00
|
|
|
/**
|
|
|
|
* register a new migration provider
|
|
|
|
* @param OC_Migrate_Provider $provider
|
|
|
|
*/
|
|
|
|
public static function registerProvider($provider){
|
|
|
|
self::$providers[]=$provider;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief finds and loads the providers
|
2012-03-17 20:25:14 +04:00
|
|
|
*/
|
|
|
|
static private function findProviders(){
|
|
|
|
// Find the providers
|
|
|
|
$apps = OC_App::getAllApps();
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-17 20:25:14 +04:00
|
|
|
foreach($apps as $app){
|
2012-03-28 00:45:37 +04:00
|
|
|
$path = OC::$SERVERROOT . '/apps/' . $app . '/appinfo/migrate.php';
|
2012-03-17 20:25:14 +04:00
|
|
|
if( file_exists( $path ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
include( $path );
|
|
|
|
}
|
|
|
|
}
|
2012-03-17 20:25:14 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief exports a user, or owncloud instance
|
2012-04-01 02:41:43 +04:00
|
|
|
* @param optional $uid string user id of user to export if export type is user, defaults to current
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param ootional $type string type of export, defualts to user
|
|
|
|
* @param otional $path string path to zip output folder
|
2012-04-14 16:31:48 +04:00
|
|
|
* @return false on error, path to zip on success
|
2012-03-20 00:44:20 +04:00
|
|
|
*/
|
2012-04-01 02:41:43 +04:00
|
|
|
public static function export( $uid=null, $type='user', $path=null ){
|
2012-03-20 00:44:20 +04:00
|
|
|
$datadir = OC_Config::getValue( 'datadirectory' );
|
|
|
|
// Validate export type
|
|
|
|
$types = array( 'user', 'instance', 'system', 'userfiles' );
|
|
|
|
if( !in_array( $type, $types ) ){
|
|
|
|
OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( array( 'success' => false ) ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
self::$exporttype = $type;
|
|
|
|
// Userid?
|
|
|
|
if( self::$exporttype == 'user' ){
|
|
|
|
// Check user exists
|
2012-04-14 16:31:48 +04:00
|
|
|
if( !is_null($uid) ){
|
|
|
|
$db = new OC_User_Database;
|
|
|
|
if( !$db->userExists( $uid ) ){
|
2012-03-20 00:44:20 +04:00
|
|
|
OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR);
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
self::$uid = $uid;
|
|
|
|
} else {
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$uid = OC_User::getUser();
|
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
// Calculate zipname
|
|
|
|
if( self::$exporttype == 'user' ){
|
2012-04-14 16:31:48 +04:00
|
|
|
$zipname = 'oc_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . '.zip';
|
2012-03-20 00:44:20 +04:00
|
|
|
} else {
|
|
|
|
$zipname = 'oc_export_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '.zip';
|
|
|
|
}
|
|
|
|
// Calculate path
|
|
|
|
if( self::$exporttype == 'user' ){
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$zippath = $datadir . '/' . self::$uid . '/' . $zipname;
|
2012-03-20 00:44:20 +04:00
|
|
|
} else {
|
|
|
|
if( !is_null( $path ) ){
|
|
|
|
// Validate custom path
|
|
|
|
if( !file_exists( $path ) || !is_writeable( $path ) ){
|
|
|
|
OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$zippath = $path . $zipname;
|
2012-03-20 00:44:20 +04:00
|
|
|
} else {
|
|
|
|
// Default path
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$zippath = get_temp_dir() . '/' . $zipname;
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Create the zip object
|
|
|
|
if( !self::createZip() ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
// Do the export
|
|
|
|
self::findProviders();
|
|
|
|
$exportdata = array();
|
|
|
|
switch( self::$exporttype ){
|
|
|
|
case 'user':
|
|
|
|
// Connect to the db
|
|
|
|
self::$dbpath = $datadir . '/' . self::$uid . '/migration.db';
|
|
|
|
if( !self::connectDB() ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 );
|
|
|
|
// Export the app info
|
2012-04-14 16:31:48 +04:00
|
|
|
$exportdata = self::exportAppData();
|
2012-03-20 00:44:20 +04:00
|
|
|
// Add the data dir to the zip
|
|
|
|
self::$content->addDir( $datadir . '/' . self::$uid, true, '/' );
|
2012-04-14 16:31:48 +04:00
|
|
|
break;
|
2012-03-20 00:44:20 +04:00
|
|
|
case 'instance':
|
|
|
|
self::$content = new OC_Migration_Content( self::$zip );
|
|
|
|
// Creates a zip that is compatable with the import function
|
2012-05-30 16:14:32 +04:00
|
|
|
$dbfile = tempnam( get_temp_dir(), "owncloud_export_data_" );
|
2012-03-20 00:44:20 +04:00
|
|
|
OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL');
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
// Now add in *dbname* and *dbprefix*
|
|
|
|
$dbexport = file_get_contents( $dbfile );
|
|
|
|
$dbnamestring = "<database>\n\n <name>" . OC_Config::getValue( "dbname", "owncloud" );
|
|
|
|
$dbtableprefixstring = "<table>\n\n <name>" . OC_Config::getValue( "dbtableprefix", "oc_" );
|
|
|
|
$dbexport = str_replace( $dbnamestring, "<database>\n\n <name>*dbname*", $dbexport );
|
|
|
|
$dbexport = str_replace( $dbtableprefixstring, "<table>\n\n <name>*dbprefix*", $dbexport );
|
|
|
|
// Add the export to the zip
|
|
|
|
self::$content->addFromString( $dbexport, "dbexport.xml" );
|
|
|
|
// Add user data
|
|
|
|
foreach(OC_User::getUsers() as $user){
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$content->addDir( $datadir . '/' . $user . '/', true, "/userdata/" );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'userfiles':
|
|
|
|
self::$content = new OC_Migration_Content( self::$zip );
|
|
|
|
// Creates a zip with all of the users files
|
|
|
|
foreach(OC_User::getUsers() as $user){
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$content->addDir( $datadir . '/' . $user . '/', true, "/" );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'system':
|
|
|
|
self::$content = new OC_Migration_Content( self::$zip );
|
|
|
|
// Creates a zip with the owncloud system files
|
|
|
|
self::$content->addDir( OC::$SERVERROOT . '/', false, '/');
|
|
|
|
foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) {
|
|
|
|
self::$content->addDir( OC::$SERVERROOT . '/' . $dir, true, "/");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if( !$info = self::getExportInfo( $exportdata ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
|
|
|
// Add the export info json to the export zip
|
|
|
|
self::$content->addFromString( $info, 'export_info.json' );
|
|
|
|
if( !self::$content->finish() ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => true, 'data' => self::$zippath ) );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief imports a user, or owncloud instance
|
2012-03-21 00:19:21 +04:00
|
|
|
* @param $path string path to zip
|
2012-03-21 00:32:01 +04:00
|
|
|
* @param optional $type type of import (user or instance)
|
2012-04-14 16:31:48 +04:00
|
|
|
* @param optional $uid userid of new user
|
2012-03-21 00:19:21 +04:00
|
|
|
*/
|
2012-03-21 00:32:01 +04:00
|
|
|
public static function import( $path, $type='user', $uid=null ){
|
2012-06-16 03:14:40 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
$datadir = OC_Config::getValue( 'datadirectory' );
|
|
|
|
// Extract the zip
|
|
|
|
if( !$extractpath = self::extractZip( $path ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
// Get export_info.json
|
|
|
|
$scan = scandir( $extractpath );
|
|
|
|
// Check for export_info.json
|
|
|
|
if( !in_array( 'export_info.json', $scan ) ){
|
|
|
|
OC_Log::write( 'migration', 'Invalid import file, export_info.json note found', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
$json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) );
|
2012-03-21 20:30:59 +04:00
|
|
|
if( $json->exporttype != $type ){
|
2012-03-21 00:32:01 +04:00
|
|
|
OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:32:01 +04:00
|
|
|
}
|
|
|
|
self::$exporttype = $type;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-06-16 03:14:40 +04:00
|
|
|
$currentuser = OC_User::getUser();
|
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
// Have we got a user if type is user
|
|
|
|
if( self::$exporttype == 'user' ){
|
2012-06-16 03:14:40 +04:00
|
|
|
self::$uid = !is_null($uid) ? $uid : $currentuser;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to be an admin if we are not importing our own data
|
|
|
|
if(($type == 'user' && self::$uid != $currentuser) || $type != 'user' ){
|
|
|
|
if( !OC_Group::inGroup( OC_User::getUser(), 'admin' )){
|
|
|
|
// Naughty.
|
|
|
|
OC_Log::write( 'migration', 'Import not permitted.', OC_Log::ERROR );
|
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
// Handle export types
|
|
|
|
switch( self::$exporttype ){
|
|
|
|
case 'user':
|
|
|
|
// Check user availability
|
2012-06-16 03:14:40 +04:00
|
|
|
if( !OC_User::userExists( self::$uid ) ){
|
|
|
|
OC_Log::write( 'migration', 'User doesn\'t exist', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
// Copy data
|
2012-04-08 23:16:03 +04:00
|
|
|
if( !self::copy_r( $extractpath . $json->exporteduser, $datadir . '/' . self::$uid ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
// Import user app data
|
2012-03-28 01:21:14 +04:00
|
|
|
if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
// All done!
|
|
|
|
if( !self::unlink_r( $extractpath ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-08 01:55:16 +04:00
|
|
|
return json_encode( array( 'success' => true, 'data' => $appsimported ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
break;
|
|
|
|
case 'instance':
|
2012-04-01 04:25:47 +04:00
|
|
|
/*
|
2012-04-07 21:27:09 +04:00
|
|
|
* EXPERIMENTAL
|
2012-03-21 00:19:21 +04:00
|
|
|
// Check for new data dir and dbexport before doing anything
|
|
|
|
// TODO
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
// Delete current data folder.
|
|
|
|
OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO );
|
2012-04-01 04:25:47 +04:00
|
|
|
if( !self::unlink_r( $datadir, false ) ){
|
2012-03-21 00:19:21 +04:00
|
|
|
OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
// Copy over data
|
2012-04-01 04:25:47 +04:00
|
|
|
if( !self::copy_r( $extractpath . 'userdata', $datadir ) ){
|
2012-03-21 00:19:21 +04:00
|
|
|
OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
// Import the db
|
|
|
|
if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( array( 'success' => false ) );
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
// Done
|
2012-04-14 16:31:48 +04:00
|
|
|
return json_encode( 'success' => true );
|
2012-04-01 04:25:47 +04:00
|
|
|
*/
|
2012-04-14 16:31:48 +04:00
|
|
|
break;
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief recursively deletes a directory
|
2012-03-21 00:19:21 +04:00
|
|
|
* @param $dir string path of dir to delete
|
|
|
|
* $param optional $deleteRootToo bool delete the root directory
|
|
|
|
* @return bool
|
|
|
|
*/
|
2012-04-14 16:31:48 +04:00
|
|
|
private static function unlink_r( $dir, $deleteRootToo=true ){
|
|
|
|
if( !$dh = @opendir( $dir ) ){
|
|
|
|
return false;
|
|
|
|
}
|
2012-03-21 00:19:21 +04:00
|
|
|
while (false !== ($obj = readdir($dh))){
|
2012-04-14 16:31:48 +04:00
|
|
|
if($obj == '.' || $obj == '..') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!@unlink($dir . '/' . $obj)){
|
|
|
|
self::unlink_r($dir.'/'.$obj, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir($dh);
|
|
|
|
if ( $deleteRootToo ) {
|
|
|
|
@rmdir($dir);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief copies recursively
|
2012-03-21 00:19:21 +04:00
|
|
|
* @param $path string path to source folder
|
|
|
|
* @param $dest string path to destination
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private static function copy_r( $path, $dest ){
|
|
|
|
if( is_dir($path) ){
|
|
|
|
@mkdir( $dest );
|
|
|
|
$objects = scandir( $path );
|
|
|
|
if( sizeof( $objects ) > 0 ){
|
|
|
|
foreach( $objects as $file ){
|
|
|
|
if( $file == "." || $file == ".." )
|
|
|
|
continue;
|
|
|
|
// go on
|
|
|
|
if( is_dir( $path . '/' . $file ) ){
|
|
|
|
self::copy_r( $path .'/' . $file, $dest . '/' . $file );
|
|
|
|
} else {
|
|
|
|
copy( $path . '/' . $file, $dest . '/' . $file );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
elseif( is_file( $path ) ){
|
|
|
|
return copy( $path, $dest );
|
|
|
|
} else {
|
|
|
|
return false;
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-21 00:19:21 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief tries to extract the import zip
|
2012-04-14 16:31:48 +04:00
|
|
|
* @param $path string path to the zip
|
2012-03-21 00:19:21 +04:00
|
|
|
* @return string path to extract location (with a trailing slash) or false on failure
|
|
|
|
*/
|
|
|
|
static private function extractZip( $path ){
|
|
|
|
self::$zip = new ZipArchive;
|
|
|
|
// Validate path
|
|
|
|
if( !file_exists( $path ) ){
|
|
|
|
OC_Log::write( 'migration', 'Zip not found', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
|
|
|
if ( self::$zip->open( $path ) != TRUE ) {
|
|
|
|
OC_Log::write( 'migration', "Failed to open zip file", OC_Log::ERROR );
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
$to = get_temp_dir() . '/oc_import_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '/';
|
2012-03-21 00:19:21 +04:00
|
|
|
if( !self::$zip->extractTo( $to ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-21 00:19:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
self::$zip->close();
|
2012-03-21 00:19:21 +04:00
|
|
|
return $to;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
/**
|
|
|
|
* @brief connects to a MDB2 database scheme
|
|
|
|
* @returns bool
|
|
|
|
*/
|
|
|
|
static private function connectScheme(){
|
|
|
|
// We need a mdb2 database connection
|
|
|
|
self::$MDB2->loadModule( 'Manager' );
|
|
|
|
self::$MDB2->loadModule( 'Reverse' );
|
|
|
|
|
|
|
|
// Connect if this did not happen before
|
|
|
|
if( !self::$schema ){
|
|
|
|
require_once('MDB2/Schema.php');
|
|
|
|
self::$schema=MDB2_Schema::factory( self::$MDB2 );
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-02-04 00:32:06 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief creates a migration.db in the users data dir with their app data in
|
2012-03-10 03:33:11 +04:00
|
|
|
* @return bool whether operation was successfull
|
2012-02-04 00:32:06 +04:00
|
|
|
*/
|
2012-03-14 03:09:43 +04:00
|
|
|
private static function exportAppData( ){
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$success = true;
|
2012-03-10 22:18:58 +04:00
|
|
|
$return = array();
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
// Foreach provider
|
2012-03-10 22:18:58 +04:00
|
|
|
foreach( self::$providers as $provider ){
|
2012-05-11 03:06:53 +04:00
|
|
|
// Check if the app is enabled
|
|
|
|
if( OC_App::isEnabled( $provider->getID() ) ){
|
|
|
|
$success = true;
|
|
|
|
// Does this app use the database?
|
|
|
|
if( file_exists( OC::$SERVERROOT.'/apps/'.$provider->getID().'/appinfo/database.xml' ) ){
|
|
|
|
// Create some app tables
|
|
|
|
$tables = self::createAppTables( $provider->getID() );
|
|
|
|
if( is_array( $tables ) ){
|
|
|
|
// Save the table names
|
|
|
|
foreach($tables as $table){
|
|
|
|
$return['apps'][$provider->getID()]['tables'][] = $table;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// It failed to create the tables
|
|
|
|
$success = false;
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
2012-05-11 03:06:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run the export function?
|
|
|
|
if( $success ){
|
|
|
|
// Set the provider properties
|
|
|
|
$provider->setData( self::$uid, self::$content );
|
|
|
|
$return['apps'][$provider->getID()]['success'] = $provider->export();
|
2012-03-12 22:40:14 +04:00
|
|
|
} else {
|
2012-05-11 03:06:53 +04:00
|
|
|
$return['apps'][$provider->getID()]['success'] = false;
|
|
|
|
$return['apps'][$provider->getID()]['message'] = 'failed to create the app tables';
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
2012-05-11 03:06:53 +04:00
|
|
|
|
|
|
|
// Now add some app info the the return array
|
|
|
|
$appinfo = OC_App::getAppInfo( $provider->getID() );
|
|
|
|
$return['apps'][$provider->getID()]['version'] = OC_App::getAppVersion($provider->getID());
|
2012-03-10 22:18:58 +04:00
|
|
|
}
|
2012-03-10 03:33:11 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 22:18:58 +04:00
|
|
|
return $return;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
|
|
|
|
2012-03-17 01:09:36 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief generates json containing export info, and merges any data supplied
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param optional $array array of data to include in the returned json
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
static private function getExportInfo( $array=array() ){
|
2012-03-17 01:09:36 +04:00
|
|
|
$info = array(
|
|
|
|
'ocversion' => OC_Util::getVersion(),
|
|
|
|
'exporttime' => time(),
|
|
|
|
'exportedby' => OC_User::getUser(),
|
|
|
|
'exporttype' => self::$exporttype
|
|
|
|
);
|
2012-03-17 17:53:00 +04:00
|
|
|
// Add hash if user export
|
2012-03-20 00:44:20 +04:00
|
|
|
if( self::$exporttype == 'user' ){
|
2012-04-15 12:30:22 +04:00
|
|
|
$query = OC_DB::prepare( "SELECT password FROM *PREFIX*users WHERE uid = ?" );
|
2012-03-17 17:53:00 +04:00
|
|
|
$result = $query->execute( array( self::$uid ) );
|
|
|
|
$row = $result->fetchRow();
|
|
|
|
$hash = $row ? $row['password'] : false;
|
|
|
|
if( !$hash ){
|
|
|
|
OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
$info['hash'] = $hash;
|
|
|
|
$info['exporteduser'] = self::$uid;
|
2012-03-17 17:53:00 +04:00
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
if( !is_array( $array ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write( 'migration', 'Supplied $array was not an array in getExportInfo()', OC_Log::ERROR );
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-03-17 17:53:00 +04:00
|
|
|
// Merge in other data
|
2012-03-20 00:44:20 +04:00
|
|
|
$info = array_merge( $info, (array)$array );
|
2012-03-17 01:09:36 +04:00
|
|
|
// Create json
|
|
|
|
$json = json_encode( $info );
|
2012-03-21 00:19:21 +04:00
|
|
|
return $json;
|
2012-03-17 01:09:36 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-14 03:09:43 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief connects to migration.db, or creates if not found
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param $db optional path to migration.db, defaults to user data dir
|
|
|
|
* @return bool whether the operation was successful
|
|
|
|
*/
|
|
|
|
static private function connectDB( $path=null ){
|
|
|
|
// Has the dbpath been set?
|
|
|
|
self::$dbpath = !is_null( $path ) ? $path : self::$dbpath;
|
|
|
|
if( !self::$dbpath ){
|
|
|
|
OC_Log::write( 'migration', 'connectDB() was called without dbpath being set', OC_Log::ERROR );
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-14 03:09:43 +04:00
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
// Already connected
|
|
|
|
if(!self::$MDB2){
|
|
|
|
require_once('MDB2.php');
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-28 00:55:53 +04:00
|
|
|
// DB type
|
2012-04-08 22:52:31 +04:00
|
|
|
if( class_exists( 'SQLite3' ) ){
|
|
|
|
$dbtype = 'sqlite3';
|
|
|
|
} else if( is_callable( 'sqlite_open' ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
$dbtype = 'sqlite';
|
2012-04-08 22:52:31 +04:00
|
|
|
} else {
|
2012-03-28 00:55:53 +04:00
|
|
|
OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR );
|
|
|
|
return false;
|
2012-04-08 22:52:31 +04:00
|
|
|
}
|
2012-03-28 00:55:53 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
// Prepare options array
|
|
|
|
$options = array(
|
|
|
|
'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE),
|
|
|
|
'log_line_break' => '<br>',
|
|
|
|
'idxname_format' => '%s',
|
|
|
|
'debug' => true,
|
|
|
|
'quote_identifier' => true
|
|
|
|
);
|
|
|
|
$dsn = array(
|
2012-04-08 22:52:31 +04:00
|
|
|
'phptype' => $dbtype,
|
2012-03-20 00:44:20 +04:00
|
|
|
'database' => self::$dbpath,
|
|
|
|
'mode' => '0644'
|
|
|
|
);
|
|
|
|
|
|
|
|
// Try to establish connection
|
|
|
|
self::$MDB2 = MDB2::factory( $dsn, $options );
|
|
|
|
// Die if we could not connect
|
|
|
|
if( PEAR::isError( self::$MDB2 ) ){
|
|
|
|
die( self::$MDB2->getMessage() );
|
|
|
|
OC_Log::write( 'migration', 'Failed to create/connect to migration.db', OC_Log::FATAL );
|
|
|
|
OC_Log::write( 'migration', self::$MDB2->getUserInfo(), OC_Log::FATAL );
|
|
|
|
OC_Log::write( 'migration', self::$MDB2->getMessage(), OC_Log::FATAL );
|
2012-03-14 20:43:06 +04:00
|
|
|
return false;
|
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
// We always, really always want associative arrays
|
|
|
|
self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC);
|
2012-03-14 03:09:43 +04:00
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
return true;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief creates the tables in migration.db from an apps database.xml
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param $appid string id of the app
|
|
|
|
* @return bool whether the operation was successful
|
|
|
|
*/
|
|
|
|
static private function createAppTables( $appid ){
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
if( !self::connectScheme() ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-14 03:09:43 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
|
|
|
// There is a database.xml file
|
2012-03-20 00:44:20 +04:00
|
|
|
$content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$file2 = 'static://db_scheme';
|
|
|
|
// TODO get the relative path to migration.db from the data dir
|
|
|
|
// For now just cheat
|
|
|
|
$path = pathinfo( self::$dbpath );
|
|
|
|
$content = str_replace( '*dbname*', self::$uid.'/migration', $content );
|
|
|
|
$content = str_replace( '*dbprefix*', '', $content );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$xml = new SimpleXMLElement($content);
|
|
|
|
foreach($xml->table as $table){
|
2012-04-14 16:31:48 +04:00
|
|
|
$tables[] = (string)$table->name;
|
|
|
|
}
|
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
file_put_contents( $file2, $content );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
// Try to create tables
|
|
|
|
$definition = self::$schema->parseDatabaseDefinitionFile( $file2 );
|
|
|
|
|
|
|
|
unlink( $file2 );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
// Die in case something went wrong
|
|
|
|
if( $definition instanceof MDB2_Schema_Error ){
|
|
|
|
OC_Log::write( 'migration', 'Failed to parse database.xml for: '.$appid, OC_Log::FATAL );
|
|
|
|
OC_Log::write( 'migration', $definition->getMessage().': '.$definition->getUserInfo(), OC_Log::FATAL );
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$definition['overwrite'] = true;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
$ret = self::$schema->createDatabase( $definition );
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
// Die in case something went wrong
|
|
|
|
if( $ret instanceof MDB2_Error ){
|
|
|
|
OC_Log::write( 'migration', 'Failed to create tables for: '.$appid, OC_Log::FATAL );
|
|
|
|
OC_Log::write( 'migration', $ret->getMessage().': '.$ret->getUserInfo(), OC_Log::FATAL );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return $tables;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-03-16 00:52:43 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief tries to create the zip
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param $path string path to zip destination
|
2012-03-16 00:52:43 +04:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
static private function createZip(){
|
2012-03-21 00:19:21 +04:00
|
|
|
self::$zip = new ZipArchive;
|
2012-03-16 00:52:43 +04:00
|
|
|
// Check if properties are set
|
2012-03-21 00:19:21 +04:00
|
|
|
if( !self::$zippath ){
|
2012-03-16 00:52:43 +04:00
|
|
|
OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR);
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-16 00:52:43 +04:00
|
|
|
}
|
2012-03-20 00:44:20 +04:00
|
|
|
if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) !== TRUE ) {
|
2012-03-16 00:52:43 +04:00
|
|
|
OC_Log::write('migration', 'Failed to create the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR);
|
|
|
|
return false;
|
|
|
|
} else {
|
2012-04-14 16:31:48 +04:00
|
|
|
return true;
|
|
|
|
}
|
2012-03-16 00:52:43 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-14 01:28:53 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief returns an array of apps that support migration
|
2012-03-14 01:28:53 +04:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
static public function getApps(){
|
|
|
|
$allapps = OC_App::getAllApps();
|
|
|
|
foreach($allapps as $app){
|
|
|
|
$path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php';
|
|
|
|
if( file_exists( $path ) ){
|
|
|
|
$supportsmigration[] = $app;
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
2012-03-14 01:28:53 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
return $supportsmigration;
|
2012-03-14 01:28:53 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
/**
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief imports a new user
|
2012-03-13 01:41:32 +04:00
|
|
|
* @param $db string path to migration.db
|
2012-03-28 01:21:14 +04:00
|
|
|
* @param $info object of migration info
|
2012-03-10 03:33:11 +04:00
|
|
|
* @param $uid optional uid to use
|
2012-03-28 01:21:14 +04:00
|
|
|
* @return array of apps with import statuses, or false on failure.
|
2012-03-10 03:33:11 +04:00
|
|
|
*/
|
2012-03-17 20:25:14 +04:00
|
|
|
public static function importAppData( $db, $info, $uid=null ){
|
2012-03-13 01:41:32 +04:00
|
|
|
// Check if the db exists
|
|
|
|
if( file_exists( $db ) ){
|
|
|
|
// Connect to the db
|
|
|
|
if(!self::connectDB( $db )){
|
2012-03-17 20:25:14 +04:00
|
|
|
OC_Log::write('migration','Failed to connect to migration.db',OC_Log::ERROR);
|
2012-03-13 01:41:32 +04:00
|
|
|
return false;
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
2012-03-13 01:41:32 +04:00
|
|
|
} else {
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write('migration','Migration.db not found at: '.$db, OC_Log::FATAL );
|
2012-03-13 01:41:32 +04:00
|
|
|
return false;
|
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-17 20:25:14 +04:00
|
|
|
// Find providers
|
|
|
|
self::findProviders();
|
|
|
|
|
|
|
|
// Generate importinfo array
|
2012-04-14 16:31:48 +04:00
|
|
|
$importinfo = array(
|
2012-03-17 20:25:14 +04:00
|
|
|
'olduid' => $info->exporteduser,
|
|
|
|
'newuid' => self::$uid
|
|
|
|
);
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-13 01:41:32 +04:00
|
|
|
foreach( self::$providers as $provider){
|
|
|
|
// Is the app in the export?
|
2012-03-20 00:44:20 +04:00
|
|
|
$id = $provider->getID();
|
2012-03-17 20:25:14 +04:00
|
|
|
if( isset( $info->apps->$id ) ){
|
2012-03-28 01:21:14 +04:00
|
|
|
// Is the app installed
|
|
|
|
if( !OC_App::isEnabled( $id ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write( 'migration', 'App: ' . $id . ' is not installed, can\'t import data.', OC_Log::INFO );
|
|
|
|
$appsstatus[$id] = 'notsupported';
|
2012-03-28 01:21:14 +04:00
|
|
|
} else {
|
|
|
|
// Did it succeed on export?
|
|
|
|
if( $info->apps->$id->success ){
|
|
|
|
// Give the provider the content object
|
|
|
|
if( !self::connectDB( $db ) ){
|
2012-04-14 16:31:48 +04:00
|
|
|
return false;
|
2012-03-28 01:21:14 +04:00
|
|
|
}
|
|
|
|
$content = new OC_Migration_Content( self::$zip, self::$MDB2 );
|
|
|
|
$provider->setData( self::$uid, $content, $info );
|
|
|
|
// Then do the import
|
|
|
|
if( !$appsstatus[$id] = $provider->import( $info->apps->$id, $importinfo ) ){
|
|
|
|
// Failed to import app
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write( 'migration', 'Failed to import app data for user: ' . self::$uid . ' for app: ' . $id, OC_Log::ERROR );
|
2012-03-28 01:21:14 +04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Add to failed list
|
2012-04-14 16:31:48 +04:00
|
|
|
$appsstatus[$id] = false;
|
2012-03-20 00:44:20 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
}
|
|
|
|
}
|
2012-03-10 03:33:11 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-28 01:21:14 +04:00
|
|
|
return $appsstatus;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-20 00:44:20 +04:00
|
|
|
/*
|
2012-04-14 20:31:37 +04:00
|
|
|
* @brief creates a new user in the database
|
2012-03-20 00:44:20 +04:00
|
|
|
* @param $uid string user_id of the user to be created
|
|
|
|
* @param $hash string hash of the user to be created
|
|
|
|
* @return bool result of user creation
|
|
|
|
*/
|
2012-03-17 17:53:00 +04:00
|
|
|
public static function createUser( $uid, $hash ){
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
// Check if userid exists
|
|
|
|
if(OC_User::userExists( $uid )){
|
|
|
|
return false;
|
2012-03-03 21:30:21 +04:00
|
|
|
}
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
// Create the user
|
|
|
|
$query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" );
|
2012-03-17 20:25:14 +04:00
|
|
|
$result = $query->execute( array( $uid, $hash));
|
2012-03-13 01:41:32 +04:00
|
|
|
if( !$result ){
|
2012-04-14 16:31:48 +04:00
|
|
|
OC_Log::write('migration', 'Failed to create the new user "'.$uid."");
|
2012-03-13 01:41:32 +04:00
|
|
|
}
|
2012-03-10 03:33:11 +04:00
|
|
|
return $result ? true : false;
|
2012-04-14 16:31:48 +04:00
|
|
|
|
2012-03-10 03:33:11 +04:00
|
|
|
}
|
2012-03-17 01:09:36 +04:00
|
|
|
|
2012-02-04 00:32:06 +04:00
|
|
|
}
|