<?php
/**
* @package RSForm! Pro
* @copyright (C) 2007-2019 www.rsjoomla.com
* @license GPL, http://www.gnu.org/copyleft/gpl.html
*/
defined('_JEXEC') or die;
if (!function_exists('gzopen') && function_exists('gzopen64')) {
function gzopen($filename, $mode, $use_include_path = 0) {
return gzopen64($filename, $mode, $use_include_path);
}
}
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
class RSFormProRestore
{
// Database instance
protected $db;
// Path to the Joomla! temporary folder
protected $tmp;
// Path to the backup
protected $path;
// This is the MD5 key that's used to identify the backup.
protected $key;
// The option for overwriting the existing forms.
protected $overwrite;
// The form that is parsed.
protected $form;
// The number of the submissions file
protected $file;
// Holds the FormId for the proper restore of the submissions form
protected $formId;
// Holds the setting for keeping the form's ids from the backup
protected $keepId;
public function __construct($options = array()) {
$this->db = Factory::getDbo();
$this->tmp = Factory::getApplication()->get('tmp_path');
$this->key = isset($options['key']) ? $options['key'] : null;
$this->overwrite = isset($options['overwrite']) ? $options['overwrite'] : null;
$this->form = isset($options['form']) ? $options['form'] : null;
$this->file = isset($options['file']) ? (int) $options['file'] : 0;
$this->formId = isset($options['formId']) ? (int) $options['formId'] : 0;
$this->keepId = !empty($options['keepId']) ? true : false;
// Check if the temporary folder is writable.
if (!is_writable($this->tmp)) {
throw new Exception(sprintf('The temporary folder "%s" is not writable!', $this->tmp));
}
// Generate a path where we will copy the backup.
$this->path = $this->tmp.'/rsform_backup_'.$this->getKey();
// Let's create our folder if it doesn't exist.
if (!is_dir($this->path) && !Folder::create($this->path)) {
throw new Exception(sprintf('Could not create temporary path "%s"!', $this->path));
}
// Check if the newly created path (or supplied one) is writable.
if (!is_writable($this->path)) {
throw new Exception(sprintf('Path "%s" is not writable!', $this->path));
}
}
public function upload($file) {
// Upload it to the temp location.
if (!File::upload($file['tmp_name'], $this->getPath(), false, true)) {
throw new Exception(sprintf('Could not copy "%s" to "%s"!', $file['name'], $this->path));
}
}
public function decompress() {
$tgzFilePath = $this->getPath();
if (!file_exists($tgzFilePath)) {
throw new Exception(sprintf('File %s does not exist!', $tgzFilePath));
}
if (!is_readable($tgzFilePath)) {
throw new Exception(sprintf('File %s is not readable!', $tgzFilePath));
}
// Open .tgz file for reading
$gzHandle = @gzopen($tgzFilePath, 'rb');
if (!$gzHandle) {
throw new Exception(sprintf('Could not open %s for reading!', $tgzFilePath));
}
while (!gzeof($gzHandle)) {
if ($block = gzread($gzHandle, 512)) {
$meta['filename'] = trim(substr($block, 0, 99));
$meta['filesize'] = octdec(trim(substr($block, 124, 12)));
if ($bytes = ($meta['filesize'] % 512)) {
$meta['nullbytes'] = 512 - $bytes;
} else {
$meta['nullbytes'] = 0;
}
if ($meta['filesize']) {
// Make sure our extension is .xml
if (($ext = File::getExt($meta['filename'])) != 'xml') {
throw new Exception(sprintf('Attempted to extract a file with an invalid extension (%s) - archive might be damaged.', preg_replace('#[^a-z0-9]#is', '', $ext)));
}
// Let's see if somebody edited the archive manually and archived a folder...
$meta['filename'] = str_replace('\\', '/', $meta['filename']);
if (strpos($meta['filename'], '/') !== false)
{
$parts = explode('/', $meta['filename']);
$meta['filename'] = end($parts);
}
// Make sure file does not contain invalid characters
if (preg_match('/[^a-z_\-\.0-9]/i', File::stripExt($meta['filename']))) {
throw new Exception('Attempted to extract a file with invalid characters in its name.');
}
$chunk = 1024*1024;
$left = $meta['filesize'];
clearstatcache();
$fHandle = @fopen($this->path.'/'.$meta['filename'], 'wb');
if (!$fHandle) {
throw new Exception(sprintf('Could not write data to file %s!', htmlentities($meta['filename'], ENT_COMPAT, 'utf-8')));
}
do {
$left = $left - $chunk;
if ($left < 0) {
$chunk = $left + $chunk;
}
$data = gzread($gzHandle, $chunk);
fwrite($fHandle, $data);
} while ($left > 0);
fclose($fHandle);
}
if ($meta['nullbytes'] > 0) {
gzread($gzHandle, $meta['nullbytes']);
}
}
}
gzclose($gzHandle);
}
protected function getMetadata() {
$metadataFile = $this->path.'/metadata.xml';
// Check if the metadata.xml exists
if (!file_exists($metadataFile)) {
throw new Exception(sprintf('The file %s does not exist!', $metadataFile));
}
// Check if the metadata.xml can be opened
if (!is_readable($metadataFile)) {
throw new Exception(sprintf('File %s is not readable!', $metadataFile));
}
// Attempt to load the XML data
libxml_use_internal_errors(true);
if ($data = simplexml_load_file($metadataFile)) {
return $data;
} else {
$errors = array();
foreach (libxml_get_errors() as $error) {
$errors[] = 'Message: '.$error->message.'; Line: '.$error->line.'; Column: '.$error->column;
}
throw new Exception(sprintf('Error while parsing XML: %s<br/>', implode('<br />', $errors)));
}
}
public function getInfo() {
$metadata = $this->getMetadata();
$info = array();
$metaInfo = array();
foreach($metadata->children() as $property => $value) {
if ($property == 'forms') {
continue;
}
$metaInfo[$property] = (string) $value;
}
if (isset($metadata->forms)) {
foreach ($metadata->forms->form as $form) {
$info[] = array(
'id' => (string) $form->id,
'name' => (string) $form->name,
'title' => (string) $form->title,
'submissions' => (string) $form->submissions
);
}
}
if (!$info) {
throw new Exception(sprintf('No forms were found in %s!', $this->path.'/metadata.xml'));
}
return (object) array(
'info' => $info,
'metaInfo' => $metaInfo
);
}
public function overwriteForms() {
if (!is_null($this->overwrite) && $this->overwrite == 1) {
$db = Factory::getDbo();
$tables = array(
// Form fields
'#__rsform_forms',
'#__rsform_components',
'#__rsform_properties',
// Submissions
'#__rsform_submissions',
'#__rsform_submission_columns',
'#__rsform_submission_values',
// Translations
'#__rsform_translations',
// Mappings
'#__rsform_mappings',
// Post to Location
'#__rsform_posts',
// Conditions
'#__rsform_conditions',
'#__rsform_condition_details',
// Calculations
'#__rsform_calculations',
// Directory
'#__rsform_directory',
'#__rsform_directory_fields',
// Additional Emails
'#__rsform_emails'
);
foreach ($tables as $table) {
$db->truncateTable($table);
}
// Allow plugins to clear their tables as well
Factory::getApplication()->triggerEvent('onRsformBackendFormRestoreTruncate');
}
}
public function parseForm() {
require_once JPATH_ADMINISTRATOR.'/components/com_rsform/helpers/restore/form.php';
$info = $this->getInfo();
$form = new RSFormProRestoreForm(array(
'path' => $this->path.'/'.$this->form.'.xml',
'keepId' => $this->keepId,
'metaData' => $info->metaInfo
));
$form->restore();
return $form->getFormId();
}
public function parseSubmissions() {
require_once JPATH_ADMINISTRATOR.'/components/com_rsform/helpers/restore/submissions.php';
$submissions = new RSFormProRestoreSubmissions(array(
'path' => $this->path.'/'.$this->form.'-data-'.$this->file.'.xml',
'formId' => $this->formId
));
$submissions->restore();
}
public function checkNextFile($number) {
if (file_exists($this->path.'/'.$this->form.'-data-'.$number.'.xml')) {
return $number;
} else {
return 0;
}
}
public function getKey() {
if (empty($this->key)) {
$this->key = md5(mt_rand());
}
return $this->key;
}
public function getPath() {
return $this->path.'/backup.tgz';
}
}