Skip to content

Instantly share code, notes, and snippets.

@jktoole
Last active February 18, 2017 15:42
Show Gist options
  • Save jktoole/be9fdee2263b48194d07 to your computer and use it in GitHub Desktop.
Save jktoole/be9fdee2263b48194d07 to your computer and use it in GitHub Desktop.

VQCopy

A quick/dirty way to 'publish' a copy of your VQModded files on Windows. This is useful if your shop is hosted on a networked file system that has trouble with the LOCK_EX flag on file_put_contents (which is used in VQMod).

There are 4 files in this archive:

  • admin_index.php - a non-VQModded admin/index.php (NOT INCLUDED IN GIST)
  • readme.md - this file
  • root_index.php - a non-VQModded index.php (NOT INCLUDED IN GIST)
  • vqcopy.php - requests cache from VQMod and copies it to the specified folder.

Installation

This utility was put together in a short space of time to solve a specific problem. It deletes and replaces files, so have a good look at what it's doing and make sure you're happy before running it - and be sure to back up your work!

Installation assumes a WAMP setup, i.e. an OpenCart installation at "C:\XAMPP\htdocs\myshop" with a VQMod directory at "C:\XAMPP\htdocs\myshop\vqmod".

  1. Unzip the archive to your 'vqmod' folder, creating "C:\XAMPP\htdocs\myshop\vqmod\vqcopy".

  2. (Ultra-Important!) Replace admin_index.php and root_index.php with the right index files for your installation of OpenCart (they need to be the non-VQModded versions). The included files are the ones that come with OpenCart-CE. (NOT INCLUDED IN GIST)

  3. Edit the scripts to suit your needs. There is one argument in vqcopy.bat, which is the name of a folder at the same level as your OpenCart installation. If the folder doesn't exist already then it'll be created during the process. There aren't any other parameters, so you can probably just leave everything else as it is.

Usage

Replace 'live' with whatever your folder's called

php vqcopy.php live MAX > live.log

Choose one of MIN or MAX, where MIN is just the modified files (for dropping on top of an existing OpenCart installation) and MAX is everything in the OpenCart installation that you're working from (so a complete copy of the current site with all mods applied).

What it does

The script starts by deleting VQMod's cache. It probably doesn't need to do that at all because VQMod always gives you up-to-date or freshly generated cache.

Next it makes a list of all the files affected by mods in the xml folder and calls VQMod to generate cache for them. It's indiscriminate, so if you have extra themes installed then you'll get cache for them too.

Finally, the cache is copied to the target folder with the appropriate filenames/structure.

It also tries to do some logging, but that bit doesn't work yet.

<?php
/**
* VQCopy
* @description 'Publish' VQModded files.
* VQCopy copies a VQmodded directory to another directory at the same level.
* It will then recursively copy the contents of a subfolder with the same name in the 'vqmod/vqcopy' directory. This step is required to remove the VQMod changes from index.php files and can be useful if other files also need to be copied to the 'live' directory (e.g. config files with different database connection details).
* WISHLIST: Offer the option to copy all files, not just modded ones
* WISHLIST: Offer to delete *all* files in the destination folder before copying
* TODO: Needs to respect whatever this is: 'vqmod/vqprotect.txt'
* TODO: Consider optionally uninstalling VQMod from index.php rather than requiring files to be copied
* Installation:
* 1. Copy this file to the VQMod directory.
* 2. In the VQMod directory, create a directory called 'vqcopy'
* 3. In the vqcopy directory, create a directory with the same name as the
* Usage:
* php vqcopy.php target MIN/MAX
* Where:
* 'target' is the name of a directory at the same level as the directory that contains the VQMod directory.
* There should also be a directory with the same name as 'target' in the vqmod/vqcopy directory.
* This directory *must* contain index.php and admin/index.php, it can optionally contain any other files.
* 'MIN/MAX' is one of MIN or MAX
* MIN: Copy only files that were modified (and the files in 'vqcopy/target').
* MAX: Copy the entire directory (and the files in 'vqcopy/target').
*/
error_reporting(E_ALL);
// http://stackoverflow.com/a/2050909/957246
function recurseCopy($src,$dst) {
$dir = opendir($src);
if(!is_dir($dst)) if(!@mkdir($dst)) die('Error creating directory: ' . $dst);
while(false !== ($file = readdir($dir))) {
if(($file != '.') && ($file != '..')) {
if(is_dir($src . DIRECTORY_SEPARATOR . $file)) {
recurseCopy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
}
else {
if(!@copy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file)) die('Error copying file: ' . $file);
// copy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
}
}
}
closedir($dir);
}
function MD5Dir($directory) {
if (!is_dir($directory)) return false;
$files = array();
$dir = dir($directory);
while (false !== ($file = $dir->read())) {
if ($file != '.' and $file != '..') {
if (is_dir($directory . '/' . $file)) $files[] = MD5Dir($directory . '/' . $file);
else $files[] = md5_file($directory . '/' . $file);
}
}
$dir->close();
return md5(implode('', $files));
}
function fixPath($path) {
$path = str_replace('\\',DIRECTORY_SEPARATOR,$path);
$path = str_replace('/',DIRECTORY_SEPARATOR,$path);
return $path;
}
$ignore_mods = array('vqmod_opencart.xml'); // Mods in this list will not be applied
$ignore_dirs = array('vqmod','cache'); // Dirs named in this list will not be copied
$copy_mode = 'MIN'; // $copy_mode is one of MIN (just the modified files), MAX (the entire, modified Opencart folder, excluding the VQMod subfolder)
$path = dirname(dirname(__FILE__));
$vqmod_path = dirname(__FILE__);
$pathlen = strlen($path);
$parent = basename($path);
$affected = array();
$wildcards = array();
$output_dir = $argv[1];
if(isset($argv[2]) AND in_array($argv[2],array('MIN','MAX'))) $copy_mode = $argv[2];
$input_path = $path;
$output_path = dirname($path) . DIRECTORY_SEPARATOR . $output_dir;
define('VC_LD',"=====================================================================");
$log = array(VC_LD);
$log[] = date("D M j Y, G:i:s");
$log[] = "Ignoring directories: " . implode(', ',$ignore_dirs);
$log[] = "Path: " . $path;
$log[] = "VQMod Path: " . $vqmod_path;
$log[] = "Parent: " . $parent;
$log[] = VC_LD;
$log[] = "Copying from $input_path to $output_path";
$log[] = VC_LD;
// Suppress potential REQUEST_URI error from VQMod
if(!isset($_SERVER['REQUEST_URI'])) $_SERVER['REQUEST_URI'] = 'vqcopy';
// Temporarily rename ignored files so that VQMod doesn't parse them
foreach($ignore_mods as $mod) {
$mpath = fixPath("$vqmod_path//xml//$mod");
if(file_exists($mpath)) rename($mpath,$mpath . '__');
$log[] = "Renaming $mpath to $mpath" ."__.";
}
require_once(fixPath("$vqmod_path//vqmod.php"));
include_once(fixPath("$vqmod_path//pathReplaces.php")); // Populates $replaces
VQMod::bootup();
// This assumes it's a subdir, it might not be
$vqmod_log_folder = $path . DIRECTORY_SEPARATOR . VQMod::$logFolder;
if (!is_dir($vqmod_log_folder)) mkdir($vqmod_log_folder, 0777, true);
// TODO: We should look at vqmod's log before we start - then again afterwards to check if anything's been added. if it has then say so.
// Get log details
// TODO: This doesn't work. The logsum is the same at the end.
$log_sum = MD5Dir($vqmod_log_folder);
$log[] = "Log sum: " . $log_sum;
// Clear cache
$log[] = 'Deleting cache.';
@unlink($path . DIRECTORY_SEPARATOR . VQMod::$modCache);
@unlink($path . DIRECTORY_SEPARATOR . VQMod::$checkedCache);
foreach(glob(fixPath("$path\\vqmod\\vqcache\\*")) as $d) if(is_file($d)) unlink($d);
// Go through each mod and build a list of affected names
// TODO: would there be an advantage if we got this list from VQMod itself? Can we do that?
foreach(glob(fixPath("$path\\vqmod\\xml\\*.xml")) as $xml_file) {
$log[] = "Processing mod: $xml_file.";
$xml = simplexml_load_file($xml_file);
foreach($xml->file as $files) {
$x = array();
$p = null;
foreach($files->attributes() as $k => $v) {
if($k == 'name') { // 'name' is a comma-delimited list of files, which can contain wildcards (*).
$x = (array) $v; // Cast object to array (which will only contain 1 element)
$unmodified_x[] = $x[0];
if(strpos($x[0],',')) { // Explode 'name' if it's a comma-delimited list
$x = explode(',',$x[0]);
$x = array_map('trim', $x);
}
}
if($k == 'path') { // 'path' is a prefix to apply to 'name', which can probably contain wildcards (*).
$p = (array) $v; // Convert object to array (which will only contain 1 element)
$p = trim($p[0]);
}
}
if($p) foreach($x as $xk => $xv) $x[$xk] = $p.trim($xv); // Prefix names with path
foreach($x as $xk => $xv) if(!in_array($xv,$affected)) $affected[] = $xv; // Add names to list
}
}
// For each name, we should have an expanded list of files
$unmodified_affected = $affected;
sort($unmodified_affected);
// Expand wildcards and de-dupe the list of affected files
foreach($affected as $xk => $x) { // Expand wildcards
$x = str_replace('/',DIRECTORY_SEPARATOR,$x); // Fix slashes for Windows
if(!empty($replaces) AND is_array($replaces)) foreach($replaces as $r) if(count($r) == 2) $x = preg_replace($r[0], $r[1], $x); // Apply replaces
$affected[$xk] = $x;
if(strpos($x,'*')) {
unset($affected[$xk]);
$log[] = "Expanding path: " . fixPath($path . DIRECTORY_SEPARATOR . $x);
$y = glob(fixPath($path . DIRECTORY_SEPARATOR . $x));
foreach($y as $z) {
$log[] = "Adding path: " . substr($z,$pathlen+1);
$wildcards[] = substr($z,$pathlen+1);
}
}
}
foreach($wildcards as $w) if(!in_array($w,$affected)) $affected[] = $w;
sort($affected);
$log[] = "Files to be processed:";
$log[] = "\r\n\t".implode("\r\n\t",$affected);
foreach($affected as $target) {
if(strpos($target,'*')) $log[] = "Handling: " . $target;
$target = $path . DIRECTORY_SEPARATOR . $target;
$cache_file = VQMod::modCheck($target); // This needs to be getting the de-replaced path
$push_target = preg_replace("~$parent~","$output_dir",$target,1);
if(!file_exists(dirname($push_target))) mkdir(dirname($push_target), 0777, true);
copy($cache_file,$push_target);
}
$admin_index = 'admin' . DIRECTORY_SEPARATOR . 'index.php';
if(!empty($replaces)) foreach($replaces as $r) if(count($r) == 2) $admin_index = preg_replace($r[0], $r[1], $admin_index); // Apply replaces to admin path
$tpath = str_replace($parent,$output_dir,$path);
$admin_index = $tpath . DIRECTORY_SEPARATOR . $admin_index;
$root_index = $tpath . DIRECTORY_SEPARATOR . 'index.php';
// Restore ignored files
foreach($ignore_mods as $mod) {
$mpath = fixPath("$vqmod_path//xml//$mod");
if(file_exists($mpath . '__')) rename($mpath . '__',$mpath);
$log[] = "Renaming $mpath" ."__ back to $mpath.";
}
// Check log details - TODO/BUG: this comparison doesn't work, it's looking at the file as it was when this script started.
$new_log_sum = MD5Dir($vqmod_log_folder);
$log[] = "New log sum: " . $new_log_sum;
if($new_log_sum != $log_sum) $log = array_merge($log,array(VC_LD,"!!! WARNING: Not all mods were fully processed! Check VQMod logs. !!!",VC_LD));
// Copy other files
if($copy_mode != 'MIN') {
$file_list = array();
$copied_file_list = array();
$source_files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(realpath($path)));
// Build list of files, removing anything in ignored dirs
foreach($source_files as $file => $object) {
if((substr($file,-1) !== '.')) {
$include_flag = true;
foreach($ignore_dirs as $i) if(strpos($file,DIRECTORY_SEPARATOR . $i . DIRECTORY_SEPARATOR) == true) $include_flag = false;
if($include_flag) $file_list[] = $file;
}
}
$copied_files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(realpath(dirname($path) . DIRECTORY_SEPARATOR . $output_dir)));
foreach($copied_files as $file => $object) {
if((substr($file,-1) !== '.')) {
$include_flag = true;
foreach($ignore_dirs as $i) if(strpos($file,DIRECTORY_SEPARATOR . $i . DIRECTORY_SEPARATOR) == true) $include_flag = false;
if($include_flag) $copied_file_list[] = $file;
}
}
// Strip path prefixes from copies of the lists so that they can be compared
$compare_file_list = array();
$strip = strlen($input_path);
foreach($file_list as $k => $v) $compare_file_list[$k] = substr($v,$strip);
$compare_copied_file_list = array();
$strip = strlen($output_path);
foreach($copied_file_list as $k => $v) $compare_copied_file_list[$k] = substr($v,$strip);
// Remove anything from the source list that's already in the target list
$files_listed_to_copy = array_diff($compare_file_list,$compare_copied_file_list);
foreach($files_listed_to_copy as $filename) {
$copy_target = $output_path.$filename;
if(!file_exists(dirname($copy_target))) mkdir(dirname($copy_target), 0777, true);
copy($input_path.$filename,$output_path.$filename);
}
}
// Copy extras
recurseCopy($vqmod_path . DIRECTORY_SEPARATOR . 'vqcopy' . DIRECTORY_SEPARATOR . $output_dir ,$output_path);
print_r(implode("\r\n",$log));
// EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment