2011-05-15 18:31:30 +04:00
< ? php
/**
* ownCloud
*
* @ author Robin Appelman
2012-05-26 21:14:24 +04:00
* @ copyright 2012 Frank Karlitschek frank @ owncloud . org
2011-05-15 18:31:30 +04:00
*
* 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 />.
*
*/
/**
* This class provides the functionality needed to install , update and remove plugins / apps
*/
2011-07-29 23:36:03 +04:00
class OC_Installer {
2011-05-15 18:31:30 +04:00
/**
* @ brief Installs an app
* @ param $data array with all information
* @ returns integer
*
* This function installs an app . All information needed are passed in the
* associative array $data .
* The following keys are required :
* - source : string , can be " path " or " http "
*
* One of the following keys is required :
* - path : path to the file containing the app
* - href : link to the downloadable file containing the app
*
* The following keys are optional :
* - pretend : boolean , if set true the system won ' t do anything
* - noinstall : boolean , if true appinfo / install . php won ' t be loaded
* - inactive : boolean , if set true the appconfig / app . sample . php won ' t be
* renamed
*
* This function works as follows
* - # fetching the file
* - # unzipping it
2012-04-22 00:47:56 +04:00
* - # check the code
2011-05-28 19:33:25 +04:00
* - # installing the database at appinfo/database.xml
2011-05-15 18:31:30 +04:00
* - # including appinfo/install.php
* - # setting the installed version
*
* It is the task of oc_app_install to create the tables and do whatever is
* needed to get the app working .
*/
2012-09-07 17:22:01 +04:00
public static function installApp ( $data = array ()) {
if ( ! isset ( $data [ 'source' ])) {
2012-11-04 21:28:29 +04:00
OC_Log :: write ( 'core' , 'No source specified when installing app' , OC_Log :: ERROR );
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//download the file if necesary
2012-09-07 17:22:01 +04:00
if ( $data [ 'source' ] == 'http' ) {
2012-03-29 02:07:28 +04:00
$path = OC_Helper :: tmpFile ();
2012-09-07 17:22:01 +04:00
if ( ! isset ( $data [ 'href' ])) {
2012-11-04 21:28:29 +04:00
OC_Log :: write ( 'core' , 'No href specified when installing app from http' , OC_Log :: ERROR );
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-11-02 22:53:02 +04:00
copy ( $data [ 'href' ], $path );
2011-05-28 19:33:25 +04:00
} else {
2012-09-07 17:22:01 +04:00
if ( ! isset ( $data [ 'path' ])) {
2012-11-04 21:28:29 +04:00
OC_Log :: write ( 'core' , 'No path specified when installing app from local file' , OC_Log :: ERROR );
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
$path = $data [ 'path' ];
}
2012-08-04 23:25:03 +04:00
2012-03-29 02:07:28 +04:00
//detect the archive type
$mime = OC_Helper :: getMimeType ( $path );
2012-09-07 17:22:01 +04:00
if ( $mime == 'application/zip' ) {
2012-11-02 22:53:02 +04:00
rename ( $path , $path . '.zip' );
2012-03-29 02:07:28 +04:00
$path .= '.zip' ;
2012-09-07 17:22:01 +04:00
} elseif ( $mime == 'application/x-gzip' ) {
2012-11-02 22:53:02 +04:00
rename ( $path , $path . '.tgz' );
2012-03-29 02:07:28 +04:00
$path .= '.tgz' ;
} else {
2012-11-04 21:28:29 +04:00
OC_Log :: write ( 'core' , 'Archives of type ' . $mime . ' are not supported' , OC_Log :: ERROR );
2012-03-29 02:07:28 +04:00
return false ;
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//extract the archive in a temporary folder
2012-03-29 02:07:28 +04:00
$extractDir = OC_Helper :: tmpFolder ();
2012-04-22 00:47:56 +04:00
OC_Helper :: rmdirr ( $extractDir );
2011-05-28 19:33:25 +04:00
mkdir ( $extractDir );
2012-09-07 17:22:01 +04:00
if ( $archive = OC_Archive :: open ( $path )) {
2012-03-29 02:07:28 +04:00
$archive -> extract ( $extractDir );
2011-05-28 19:33:25 +04:00
} else {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'Failed to open archive when installing app' , OC_Log :: ERROR );
2011-07-29 23:36:03 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-09-07 17:22:01 +04:00
if ( $data [ 'source' ] == 'http' ) {
2011-05-28 19:33:25 +04:00
unlink ( $path );
}
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//load the info.xml file of the app
2012-09-07 17:22:01 +04:00
if ( ! is_file ( $extractDir . '/appinfo/info.xml' )) {
2012-03-29 02:11:29 +04:00
//try to find it in a subdir
$dh = opendir ( $extractDir );
2012-09-07 17:22:01 +04:00
while ( $folder = readdir ( $dh )) {
if ( $folder [ 0 ] != '.' and is_dir ( $extractDir . '/' . $folder )) {
if ( is_file ( $extractDir . '/' . $folder . '/appinfo/info.xml' )) {
2012-03-29 02:11:29 +04:00
$extractDir .= '/' . $folder ;
}
}
}
}
2012-09-07 17:22:01 +04:00
if ( ! is_file ( $extractDir . '/appinfo/info.xml' )) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'App does not provide an info.xml file' , OC_Log :: ERROR );
2011-07-29 23:36:03 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-09-07 17:22:01 +04:00
if ( $data [ 'source' ] == 'http' ) {
2011-05-28 19:33:25 +04:00
unlink ( $path );
}
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-10-24 00:53:54 +04:00
$info = OC_App :: getAppInfo ( $extractDir . '/appinfo/info.xml' , true );
2012-07-24 02:39:59 +04:00
// check the code for not allowed calls
2012-10-28 21:12:31 +04:00
if ( ! OC_Installer :: checkCode ( $info [ 'id' ], $extractDir )) {
OC_Log :: write ( 'core' , 'App can\'t be installed because of not allowed code in the App' , OC_Log :: ERROR );
2012-04-22 00:47:56 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-07-24 02:39:59 +04:00
return false ;
2012-04-22 00:47:56 +04:00
}
2012-05-18 17:54:36 +04:00
2012-07-24 02:39:59 +04:00
// check if the app is compatible with this version of ownCloud
2012-08-04 23:25:03 +04:00
$version = OC_Util :: getVersion ();
2012-09-07 17:22:01 +04:00
if ( ! isset ( $info [ 'require' ]) or ( $version [ 0 ] > $info [ 'require' ])) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'App can\'t be installed because it is not compatible with this version of ownCloud' , OC_Log :: ERROR );
2012-05-18 17:54:36 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-07-24 02:39:59 +04:00
return false ;
2012-05-18 17:54:36 +04:00
}
2013-01-21 23:40:23 +04:00
// check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
if ( isset ( $info [ 'shipped' ]) and ( $info [ 'shipped' ] == 'true' )) {
OC_Log :: write ( 'core' , 'App can\'t be installed because it contains the <shipped>true</shippe> tag which is not allowed for non shipped apps' , OC_Log :: ERROR );
OC_Helper :: rmdirr ( $extractDir );
return false ;
}
// check if the ocs version is the same as the version in info.xml/version
if ( ! isset ( $info [ 'version' ]) or ( $info [ 'version' ] <> $data [ 'appdata' ][ 'version' ])) {
OC_Log :: write ( 'core' , 'App can\'t be installed because the version in info.xml/version is not the same as the version reported from the app store' , OC_Log :: ERROR );
OC_Helper :: rmdirr ( $extractDir );
return false ;
}
2012-06-15 01:00:02 +04:00
$basedir = OC_App :: getInstallPath () . '/' . $info [ 'id' ];
2011-06-19 17:18:52 +04:00
//check if the destination directory already exists
2012-09-07 17:22:01 +04:00
if ( is_dir ( $basedir )) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'App directory already exists' , OC_Log :: WARN );
2011-07-29 23:36:03 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-09-07 17:22:01 +04:00
if ( $data [ 'source' ] == 'http' ) {
2011-06-20 00:42:33 +04:00
unlink ( $path );
}
return false ;
}
2012-08-04 23:25:03 +04:00
2012-09-07 17:22:01 +04:00
if ( isset ( $data [ 'pretent' ]) and $data [ 'pretent' ] == true ) {
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//copy the app to the correct place
2012-09-07 17:22:01 +04:00
if ( @! mkdir ( $basedir )) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'Can\'t create app folder. Please fix permissions. (' . $basedir . ')' , OC_Log :: ERROR );
2011-07-29 23:36:03 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-09-07 17:22:01 +04:00
if ( $data [ 'source' ] == 'http' ) {
2011-05-28 19:33:25 +04:00
unlink ( $path );
}
2011-06-19 17:18:52 +04:00
return false ;
2011-05-28 19:33:25 +04:00
}
2012-10-28 21:12:31 +04:00
OC_Helper :: copyr ( $extractDir , $basedir );
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//remove temporary files
2011-07-29 23:36:03 +04:00
OC_Helper :: rmdirr ( $extractDir );
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//install the database
2012-09-07 17:22:01 +04:00
if ( is_file ( $basedir . '/appinfo/database.xml' )) {
2011-05-28 19:33:25 +04:00
OC_DB :: createDbFromStructure ( $basedir . '/appinfo/database.xml' );
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//run appinfo/install.php
2012-09-07 17:22:01 +04:00
if (( ! isset ( $data [ 'noinstall' ]) or $data [ 'noinstall' ] == false ) and file_exists ( $basedir . '/appinfo/install.php' )) {
2012-10-22 23:40:33 +04:00
include $basedir . '/appinfo/install.php' ;
2011-05-28 19:33:25 +04:00
}
2012-08-04 23:25:03 +04:00
2011-05-28 19:33:25 +04:00
//set the installed version
2012-10-28 21:12:31 +04:00
OC_Appconfig :: setValue ( $info [ 'id' ], 'installed_version' , OC_App :: getAppVersion ( $info [ 'id' ]));
OC_Appconfig :: setValue ( $info [ 'id' ], 'enabled' , 'no' );
2012-05-11 22:58:23 +04:00
//set remote/public handelers
2012-09-07 17:22:01 +04:00
foreach ( $info [ 'remote' ] as $name => $path ) {
2012-08-04 23:25:03 +04:00
OCP\CONFIG :: setAppValue ( 'core' , 'remote_' . $name , $info [ 'id' ] . '/' . $path );
2012-05-11 22:58:23 +04:00
}
2012-09-07 17:22:01 +04:00
foreach ( $info [ 'public' ] as $name => $path ) {
2012-08-04 23:25:03 +04:00
OCP\CONFIG :: setAppValue ( 'core' , 'public_' . $name , $info [ 'id' ] . '/' . $path );
2012-05-11 22:58:23 +04:00
}
2012-05-15 00:49:20 +04:00
OC_App :: setAppTypes ( $info [ 'id' ]);
2012-08-04 23:25:03 +04:00
2012-01-27 20:34:47 +04:00
return $info [ 'id' ];
2011-06-19 17:18:52 +04:00
}
/**
* @ brief checks whether or not an app is installed
* @ param $app app
* @ returns true / false
*
* Checks whether or not an app is installed , i . e . registered in apps table .
*/
2012-09-07 17:22:01 +04:00
public static function isInstalled ( $app ) {
2011-06-19 17:18:52 +04:00
2012-09-07 17:22:01 +04:00
if ( null == OC_Appconfig :: getValue ( $app , " installed_version " )) {
2011-06-19 17:18:52 +04:00
return false ;
}
2011-05-15 18:31:30 +04:00
return true ;
}
/**
* @ brief Update an application
* @ param $data array with all information
*
* This function installs an app . All information needed are passed in the
* associative array $data .
* The following keys are required :
* - source : string , can be " path " or " http "
*
* One of the following keys is required :
* - path : path to the file containing the app
* - href : link to the downloadable file containing the app
*
* The following keys are optional :
* - pretend : boolean , if set true the system won ' t do anything
* - noupgrade : boolean , if true appinfo / upgrade . php won ' t be loaded
*
* This function works as follows
* - # fetching the file
* - # removing the old files
* - # unzipping new file
* - # including appinfo/upgrade.php
* - # setting the installed version
*
2012-11-04 14:10:46 +04:00
* upgrade . php can determine the current installed version of the app using " OC_Appconfig::getValue( $appid , 'installed_version') "
2011-05-15 18:31:30 +04:00
*/
2013-01-21 23:40:23 +04:00
public static function updateApp ( $app ) {
2013-01-31 13:00:42 +04:00
$ocsid = OC_Appconfig :: getValue ( $app , 'ocsid' );
2013-01-30 15:08:14 +04:00
OC_App :: disable ( $app );
OC_App :: enable ( $ocsid );
2011-05-15 18:31:30 +04:00
}
2013-01-31 13:30:13 +04:00
/**
* @ brief Check if an update for the app is available
* @ param $name name of the application
* @ returns empty string is no update available or the version number of the update
*
* The function will check if an update for a version is available
*/
public static function isUpdateAvailable ( $app ) {
2013-01-21 23:40:23 +04:00
$ocsid = OC_Appconfig :: getValue ( $app , 'ocsid' , '' );
if ( $ocsid <> '' ){
2013-01-31 13:30:13 +04:00
2013-01-21 23:40:23 +04:00
$ocsdata = OC_OCSClient :: getApplication ( $ocsid );
2013-01-30 15:08:14 +04:00
$ocsversion = ( string ) $ocsdata [ 'version' ];
2013-01-21 23:40:23 +04:00
$currentversion = OC_App :: getAppVersion ( $app );
2013-01-30 15:08:14 +04:00
if ( $ocsversion <> $currentversion ){
return ( $ocsversion );
2013-01-21 23:40:23 +04:00
2013-01-30 15:08:14 +04:00
} else {
return ( '' );
}
2013-01-21 23:40:23 +04:00
} else {
return ( '' );
}
2013-01-31 13:30:13 +04:00
}
2013-01-21 23:40:23 +04:00
2013-01-31 13:30:13 +04:00
/**
* @ brief Check if app is already downloaded
* @ param $name name of the application to remove
* @ returns true / false
*
* The function will check if the app is already downloaded in the apps repository
*/
public static function isDownloaded ( $name ) {
2013-01-21 23:40:23 +04:00
$downloaded = false ;
foreach ( OC :: $APPSROOTS as $dir ) {
2013-01-31 13:30:13 +04:00
if ( is_dir ( $dir [ 'path' ] . '/' . $name )) $downloaded = true ;
2013-01-21 23:40:23 +04:00
}
return ( $downloaded );
2013-01-31 13:30:13 +04:00
}
2013-01-21 23:40:23 +04:00
2011-05-15 18:31:30 +04:00
/**
* @ brief Removes an app
* @ param $name name of the application to remove
* @ param $options array with options
* @ returns true / false
*
* This function removes an app . $options is an associative array . The
* following keys are optional : ja
* - keeppreferences : boolean , if true the user preferences won ' t be deleted
* - keepappconfig : boolean , if true the config will be kept
* - keeptables : boolean , if true the database will be kept
* - keepfiles : boolean , if true the user files will be kept
*
* This function works as follows
* - # including appinfo/remove.php
* - # removing the files
*
* The function will not delete preferences , tables and the configuration ,
* this has to be done by the function oc_app_uninstall () .
*/
2012-09-07 17:22:01 +04:00
public static function removeApp ( $name , $options = array ()) {
2013-01-21 23:40:23 +04:00
if ( isset ( $options [ 'keeppreferences' ]) and $options [ 'keeppreferences' ] == false ){
// todo
// remove preferences
}
if ( isset ( $options [ 'keepappconfig' ]) and $options [ 'keepappconfig' ] == false ){
// todo
// remove app config
}
if ( isset ( $options [ 'keeptables' ]) and $options [ 'keeptables' ] == false ){
// todo
// remove app database tables
}
if ( isset ( $options [ 'keepfiles' ]) and $options [ 'keepfiles' ] == false ){
// todo
// remove user files
}
2013-01-31 13:30:13 +04:00
if ( OC_Installer :: isDownloaded ( $name )) {
2013-01-21 23:40:23 +04:00
$appdir = OC_App :: getInstallPath () . '/' . $name ;
OC_Helper :: rmdirr ( $appdir );
} else {
OC_Log :: write ( 'core' , 'can\'t remove app ' . $name . '. It is not installed.' , OC_Log :: ERROR );
}
2011-05-15 18:31:30 +04:00
}
2011-06-22 00:16:41 +04:00
/**
* @ brief Installs shipped apps
*
2012-04-29 16:38:56 +04:00
* This function installs all apps found in the 'apps' directory that should be enabled by default ;
2011-06-22 00:16:41 +04:00
*/
2012-09-07 17:22:01 +04:00
public static function installShippedApps () {
2012-06-05 00:37:00 +04:00
foreach ( OC :: $APPSROOTS as $app_dir ) {
2012-09-07 17:22:01 +04:00
if ( $dir = opendir ( $app_dir [ 'path' ] )) {
while ( false !== ( $filename = readdir ( $dir ))) {
if ( substr ( $filename , 0 , 1 ) != '.' and is_dir ( $app_dir [ 'path' ] . " / $filename " ) ) {
if ( file_exists ( $app_dir [ 'path' ] . " / $filename /appinfo/app.php " )) {
if ( ! OC_Installer :: isInstalled ( $filename )) {
2012-06-23 18:17:59 +04:00
$info = OC_App :: getAppInfo ( $filename );
$enabled = isset ( $info [ 'default_enable' ]);
2012-09-07 17:22:01 +04:00
if ( $enabled ) {
2012-06-23 18:17:59 +04:00
OC_Installer :: installShippedApp ( $filename );
2012-10-28 21:12:31 +04:00
OC_Appconfig :: setValue ( $filename , 'enabled' , 'yes' );
2012-06-23 18:17:59 +04:00
}
2012-06-05 00:37:00 +04:00
}
2011-06-22 00:16:41 +04:00
}
}
}
2012-06-23 18:21:47 +04:00
closedir ( $dir );
2011-06-22 00:16:41 +04:00
}
}
}
2011-08-22 16:17:38 +04:00
/**
* install an app already placed in the app folder
* @ param string $app id of the app to install
2011-09-28 00:36:14 +04:00
* @ returns array see OC_App :: getAppInfo
2011-08-22 16:17:38 +04:00
*/
2012-09-07 17:22:01 +04:00
public static function installShippedApp ( $app ) {
2011-08-22 16:17:38 +04:00
//install the database
2012-09-07 17:22:01 +04:00
if ( is_file ( OC_App :: getAppPath ( $app ) . " /appinfo/database.xml " )) {
2012-06-02 02:11:03 +04:00
OC_DB :: createDbFromStructure ( OC_App :: getAppPath ( $app ) . " /appinfo/database.xml " );
2011-08-22 16:17:38 +04:00
}
//run appinfo/install.php
2012-09-07 17:22:01 +04:00
if ( is_file ( OC_App :: getAppPath ( $app ) . " /appinfo/install.php " )) {
2012-10-22 23:40:33 +04:00
include OC_App :: getAppPath ( $app ) . " /appinfo/install.php " ;
2011-08-22 16:17:38 +04:00
}
2012-03-30 15:48:44 +04:00
$info = OC_App :: getAppInfo ( $app );
2012-10-28 21:12:31 +04:00
OC_Appconfig :: setValue ( $app , 'installed_version' , OC_App :: getAppVersion ( $app ));
2012-08-04 23:25:03 +04:00
2012-05-11 22:58:23 +04:00
//set remote/public handelers
2012-09-07 17:22:01 +04:00
foreach ( $info [ 'remote' ] as $name => $path ) {
2012-06-05 00:37:00 +04:00
OCP\CONFIG :: setAppValue ( 'core' , 'remote_' . $name , $app . '/' . $path );
2012-05-11 22:58:23 +04:00
}
2012-09-07 17:22:01 +04:00
foreach ( $info [ 'public' ] as $name => $path ) {
2012-06-05 00:37:00 +04:00
OCP\CONFIG :: setAppValue ( 'core' , 'public_' . $name , $app . '/' . $path );
2012-05-11 22:58:23 +04:00
}
2012-08-04 23:25:03 +04:00
2012-05-15 00:49:20 +04:00
OC_App :: setAppTypes ( $info [ 'id' ]);
2012-08-04 23:25:03 +04:00
2012-08-05 03:40:19 +04:00
return $info [ 'id' ];
2011-08-22 16:17:38 +04:00
}
2012-04-22 00:47:56 +04:00
2012-07-24 02:39:59 +04:00
/**
* check the code of an app with some static code checks
* @ param string $folder the folder of the app to check
* @ returns true for app is o . k . and false for app is not o . k .
*/
2012-11-02 22:53:02 +04:00
public static function checkCode ( $appname , $folder ) {
2012-04-22 00:47:56 +04:00
$blacklist = array (
2012-05-18 20:22:37 +04:00
'exec(' ,
2012-04-22 00:47:56 +04:00
'eval('
// more evil pattern will go here later
// will will also check if an app is using private api once the public api is in place
);
// is the code checker enabled?
2012-09-07 17:22:01 +04:00
if ( OC_Config :: getValue ( 'appcodechecker' , false )) {
2012-04-22 00:47:56 +04:00
// check if grep is installed
$grep = exec ( 'which grep' );
if ( $grep == '' ) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'grep not installed. So checking the code of the app "' . $appname . '" was not possible' , OC_Log :: ERROR );
2012-04-22 00:47:56 +04:00
return true ;
}
// iterate the bad patterns
foreach ( $blacklist as $bl ) {
$cmd = 'grep -ri ' . escapeshellarg ( $bl ) . ' ' . $folder . '' ;
$result = exec ( $cmd );
// bad pattern found
if ( $result <> '' ) {
2012-10-28 21:12:31 +04:00
OC_Log :: write ( 'core' , 'App "' . $appname . '" is using a not allowed call "' . $bl . '". Installation refused.' , OC_Log :: ERROR );
2012-04-22 00:47:56 +04:00
return false ;
}
}
return true ;
2012-08-04 23:25:03 +04:00
2012-04-22 00:47:56 +04:00
} else {
2012-07-24 02:39:59 +04:00
return true ;
2012-04-22 00:47:56 +04:00
}
2012-07-24 02:39:59 +04:00
}
2011-05-15 18:31:30 +04:00
}