Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 50 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
# SyncX - Component sync for MODX Evolution
## What does it do?
How often have you modified a chunk or snippet in MODX and then wanted to go back to the previous version? Or done a round of updates which needed reverting, and cursed that your components weren't under version control?
Curse no more. This module for MODX Evolution (tested with 1.0.5) allows components stored in the database (chunks, snippets, templates, plugins) to be copied to the file system and vice versa. This enables them to be easily edited using your favourite text editor, and also stored under version control.
### WARNING:
Currently MODX has no way to determine when a component in the database was modified (we have a patch on the way for that...). This script is very naive, and will overwrite all items in the database/file system regardless of which is newer. **You have been warned!**
## To install
Assuming you are logged into a terminal and already in your MODX site's root directory, the following commands are all you need to use:
$ cd assets/modules
$ git clone git://github.com/mapledesign/modx-component-sync.git component-sync
## To use
You can either run the module from the command line as follows:
$ cd assets/modules/component-sync/
$ php ./cmd.php dump
or
$ php ./cmd.php load
or you can install the web-based module.
To do this, go to Modules -> Manage modules.
Click on the 'New Module' button
Give the module a name (e.g. 'Component Sync') and paste the following code in as the module code:
include $modx->config['base_path'].'assets/modules/component-sync/module.php';
Run the module, and follow the on-screen prompts.
That really is all there is to it!
## Colophon
SyncX was developed by Peter Bowyer and the team at Maple Design Ltd. We build custom MODX applications and add-ons like this [easy to manage photo gallery](http://www.youtube.com/watch?v=SUbM_D2GT4s) to make your clients' lives easier. [Contact us](http://www.mapledesign.co.uk/services/s/content-management-systems/modx-development/) to find out how we can help!
This code should be considered alpha-quality. We are using it but have not extensively tested it.
Feedback, bug reports, questions and usage scenarios we've not considered are all welcome. Please use the ticket system here on Github.
The code is licensed under the MIT license, and eventually we'll add license headers to the code :)
# SyncX - Component sync for MODX Evolution

## What does it do?
How often have you modified a chunk or snippet in MODX and then wanted to go back to the previous version? Or done a round of updates which needed reverting, and cursed that your components weren't under version control?

Curse no more. This module for MODX Evolution (tested with 1.0.5) allows components stored in the database (chunks, snippets, templates, plugins) to be copied to the file system and vice versa. This enables them to be easily edited using your favourite text editor, and also stored under version control.

### WARNING:

Currently MODX has no way to determine when a component in the database was modified (we have a patch on the way for that...). This script is very naive, and will overwrite all items in the database/file system regardless of which is newer. **You have been warned!**

## To install

Assuming you are logged into a terminal and already in your MODX site's root directory, the following commands are all you need to use:

$ cd assets/modules
$ git clone git://github.com/mapledesign/modx-component-sync.git component-sync

## To use

You can either run the module from the command line as follows:

$ cd assets/modules/component-sync/
$ php ./cmd.php dump

or

$ php ./cmd.php load

or you can install the web-based module.

To do this, go to Modules -> Manage modules.
Click on the 'New Module' button
Give the module a name (e.g. 'Component Sync') and paste the following code in as the module code:

include $modx->config['base_path'].'assets/modules/component-sync/module.php';

Run the module, and follow the on-screen prompts.

That really is all there is to it!

## Colophon

SyncX was developed by Peter Bowyer and the team at Maple Design Ltd. We build custom MODX applications and add-ons like this [easy to manage photo gallery](http://www.youtube.com/watch?v=SUbM_D2GT4s) to make your clients' lives easier. [Contact us](http://www.mapledesign.co.uk/services/s/content-management-systems/modx-development/) to find out how we can help!

This code should be considered alpha-quality. We are using it but have not extensively tested it.

Feedback, bug reports, questions and usage scenarios we've not considered are all welcome. Please use the ticket system here on Github.

The code is licensed under the MIT license, and eventually we'll add license headers to the code :)
128 changes: 74 additions & 54 deletions classes/ComponentBase.php
Original file line number Diff line number Diff line change
@@ -1,55 +1,75 @@
<?php
class ComponentBase
{
protected $modx, $table, $dir = null;

protected $component = array(
'snippets' => array(
'tablename' => 'site_snippets',
'col_name' => 'name',
'col_content' => 'snippet',
),
'chunks' => array(
'tablename' => 'site_htmlsnippets',
'col_name' => 'name',
'col_content' => 'snippet',
),
'plugins' => array(
'tablename' => 'site_plugins',
'col_name' => 'name',
'col_content' => 'plugincode',
),
'templates' => array(
'tablename' => 'site_templates',
'col_name' => 'templatename',
'col_content' => 'content',
),
);

public function __construct(&$modx, $type = 'snippets', $foldername = '_db')
{
$this->modx = $modx;
$this->type = $type;

$this->table = $this->modx->getFullTableName($this->component[$type]['tablename']);

$this->dir = MODX_BASE_PATH ."assets/$type/$foldername/";

if (!file_exists($this->dir) && !is_dir($this->dir)) {
$ret = mkdir($this->dir, 0777, true);
if ($ret == false && !is_dir($this->dir)) {
throw new Exception("Cannot create {$this->dir}. Please create directory manually, and set permissions to 0777");
}
}
}

protected function statsBlock($label, $array)
{
$o = "$label:\n";
foreach ($array as $i) {
$o .= "\t$i\n";
}
$o .= "\n";
return $o;
}
<?php
class ComponentBase
{
protected $modx, $table, $dir = null, $eol;

protected $component = array(
'snippets' => array(
'tablename' => 'site_snippets',
'col_name' => 'name',
'col_content' => 'snippet',
),
'chunks' => array(
'tablename' => 'site_htmlsnippets',
'col_name' => 'name',
'col_content' => 'snippet',
),
'plugins' => array(
'tablename' => 'site_plugins',
'col_name' => 'name',
'col_content' => 'plugincode',
),
'templates' => array(
'tablename' => 'site_templates',
'col_name' => 'templatename',
'col_content' => 'content',
),
);

public function __construct(&$modx, $type = 'snippets', $foldername = '_db', $eol='')
{
$this->modx = $modx;
$this->type = $type;

$this->table = $this->modx->getFullTableName($this->component[$type]['tablename']);

$this->dir = MODX_BASE_PATH ."assets/$type/$foldername/";

$this->eol = (empty($eol) or !in_array($eol,explode(",","lf,crlf,cr")))?"":$eol;

if (!file_exists($this->dir) && !is_dir($this->dir)) {
$ret = mkdir($this->dir, 0777, true);
if ($ret == false && !is_dir($this->dir)) {
throw new Exception("Cannot create {$this->dir}. Please create directory manually, and set permissions to 0777");
}
}
}

protected function statsBlock($label, $array)
{
$o = "$label:\n";
foreach ($array as $i) {
$o .= "\t$i\n";
}
$o .= "\n";
return $o;
}
protected function normalizeEol($string) {
if ($this->eol) {
switch ($this->eol) {
case "lf":
$string = preg_replace("/\r\n/s", "\n", $string);
$string = preg_replace("/\r/s", "\n", $string);
break;
case "cr":
$string = preg_replace("/\r\n/s", "\r", $string);
$string = preg_replace("/\r/s", "\r", $string);
break;
case "crlf":
$string = preg_replace("/(?<=[^\r]|^)\n/s", "\r\n", $string);
break;
}
}
return $string;
}
}
6 changes: 3 additions & 3 deletions classes/ComponentDump.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ public function run()
}
$res = $this->modx->db->query("SELECT * FROM $this->table");

while ($row = mysql_fetch_assoc($res)) {
$filename = $row[$this->component[$this->type]['col_name']] .'.php';
file_put_contents($this->dir . $filename, $row[$this->component[$this->type]['col_content']]);
while( $row = $this->modx->db->getRow( $res ) ) {
$filename = $row[$this->component[$this->type]['col_name']] .'.php';
file_put_contents($this->dir . $filename, $this->normalizeEol($row[$this->component[$this->type]['col_content']]));
$fs_items[] = $filename;
}

Expand Down
152 changes: 76 additions & 76 deletions classes/ComponentLoad.php
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
<?php
require_once 'ComponentBase.php';
/*
Really I want run() to take the component (snippets, chunks etc) as an argument so
this class is only initialised once (which as I'm going to have options like 'dry-run' to pass in
makes a lot more sense).
However how does the getStats() method work then? Maybe best to keep it this way...
*/
class ComponentLoad extends ComponentBase
{
public function run()
{
$files = glob($this->dir .'*.php');
foreach ($files as $file) {
$filename = basename($file, '.php');
$content = file_get_contents($file);
// Can't use REPLACE as the name column isn't indexed
#$res = $modx->db->query("REPLACE INTO $table SET {$component[$type]['col_content']} = '". mysql_real_escape_string($content, $modx->db->conn) ."' WHERE {$component[$type]['col_name']} = '". $filename ."'");
$id = $this->modx->db->getValue("SELECT id FROM $this->table WHERE {$this->component[$this->type]['col_name']} = '". $filename ."'");
if ($id) {
$res = $this->modx->db->query("UPDATE $this->table SET {$this->component[$this->type]['col_content']} = '". $this->modx->db->escape($content, $this->modx->db->conn) ."' WHERE {$this->component[$this->type]['col_name']} = '". $filename ."'");
$updated_items[] = $filename;
} else {
$res = $this->modx->db->query("INSERT INTO $this->table SET {$this->component[$this->type]['col_content']} = '". $this->modx->db->escape($content, $this->modx->db->conn) ."', {$this->component[$this->type]['col_name']} = '". $filename ."'");
$new_items[] = $filename;
}
$fs_items[] = $filename;
}
// Handle items which may now be in the DB/filesystem but are no longer present in the filesystem/db
// (i.e. remove deleted resources)
$this->fs_items = $fs_items;
$this->new_items = $new_items;
$this->updated_items = $updated_items;
// Clear cache if sync'ing back to DB - as chunks etc are cached there
include_once $this->modx->config['base_path'] ."manager/processors/cache_sync.class.processor.php";
$sync = new synccache();
$sync->setCachepath($this->modx->config['base_path'] ."assets/cache/");
$sync->setReport(false);
$sync->emptyCache(); // first empty the cache
}
public function getStats()
{
$o = '';
$o = "###################################################################\n";
$o .= "# Processing {$this->type}\n";
$o .= "###################################################################\n\n";
$o .= "Loaded the following from the file system:\n";
foreach ($this->fs_items as $i) {
$o .= "\t$i\n";
}
$o .= "\n";
// So our stats method can say which were new components loaded into
// the DB, we need to list the DB contents *BEFORE* we load stuff into it
$res = $this->modx->db->query("SELECT {$this->component[$this->type]['col_name']} AS name FROM $this->table");
$db_items = $this->modx->db->getColumn('name', $res);
$items_in_db_not_filesystem = array_diff($db_items, $this->fs_items);
if (count($items_in_db_not_filesystem) > 0) {
$o .= $this->statsBlock("The following are in the database but NOT the file system", $items_in_db_not_filesystem);
}
return $o;
}
<?php
require_once 'ComponentBase.php';
/*
Really I want run() to take the component (snippets, chunks etc) as an argument so
this class is only initialised once (which as I'm going to have options like 'dry-run' to pass in
makes a lot more sense).

However how does the getStats() method work then? Maybe best to keep it this way...
*/
class ComponentLoad extends ComponentBase
{
public function run()
{
$files = glob($this->dir .'*.php');

foreach ($files as $file) {
$filename = basename($file, '.php');
$content = file_get_contents($file);

// Can't use REPLACE as the name column isn't indexed
#$res = $modx->db->query("REPLACE INTO $table SET {$component[$type]['col_content']} = '". mysql_real_escape_string($content, $modx->db->conn) ."' WHERE {$component[$type]['col_name']} = '". $filename ."'");
$id = $this->modx->db->getValue("SELECT id FROM $this->table WHERE {$this->component[$this->type]['col_name']} = '". $filename ."'");
if ($id) {
$res = $this->modx->db->query("UPDATE $this->table SET {$this->component[$this->type]['col_content']} = '". $this->modx->db->escape($content, $this->modx->db->conn) ."' WHERE {$this->component[$this->type]['col_name']} = '". $filename ."'");
$updated_items[] = $filename;
} else {
$res = $this->modx->db->query("INSERT INTO $this->table SET {$this->component[$this->type]['col_content']} = '". $this->modx->db->escape($content, $this->modx->db->conn) ."', {$this->component[$this->type]['col_name']} = '". $filename ."'");
$new_items[] = $filename;
}


$fs_items[] = $filename;
}

// Handle items which may now be in the DB/filesystem but are no longer present in the filesystem/db
// (i.e. remove deleted resources)


$this->fs_items = $fs_items;
$this->new_items = $new_items;
$this->updated_items = $updated_items;


// Clear cache if sync'ing back to DB - as chunks etc are cached there
include_once $this->modx->config['base_path'] ."manager/processors/cache_sync.class.processor.php";
$sync = new synccache();
$sync->setCachepath($this->modx->config['base_path'] ."assets/cache/");
$sync->setReport(false);
$sync->emptyCache(); // first empty the cache
}

public function getStats()
{
$o = '';
$o = "###################################################################\n";
$o .= "# Processing {$this->type}\n";
$o .= "###################################################################\n\n";
$o .= "Loaded the following from the file system:\n";
foreach ($this->fs_items as $i) {
$o .= "\t$i\n";
}
$o .= "\n";

// So our stats method can say which were new components loaded into
// the DB, we need to list the DB contents *BEFORE* we load stuff into it
$res = $this->modx->db->query("SELECT {$this->component[$this->type]['col_name']} AS name FROM $this->table");
$db_items = $this->modx->db->getColumn('name', $res);

$items_in_db_not_filesystem = array_diff($db_items, $this->fs_items);

if (count($items_in_db_not_filesystem) > 0) {
$o .= $this->statsBlock("The following are in the database but NOT the file system", $items_in_db_not_filesystem);
}

return $o;
}
}
Loading