We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 10183
    • 6 Posts
    I know there are a few other approaches i have seen to being able to edit ModX content from the file system rather then from the manager, and i am going to humbly submit my attempt to the group. I have taken a look at modxfs-php but it seemed needlessly complex as well as requiring your system to support Fuse, the other method i saw was the use of just creating simple snippets in the manager that include your external resources verbatim but this seemed more labour intensive then i would like. And neither of these methods seemed to have a very clear cut way of handling template variables.

    Attached is the plugin code for my method of keeping things in sync, the only thing that you need to keep in mind when adding it is that you must hook it up to OnHandleRequest event. Once you do that your good to go.

    This plugin assumes a couple things:

    1) All of your components are located in $modx->config[’base_path’] . ’core/components’
    2) Your component library consists of 3 folders, "chunks", "snippets", "templates" and a single file called .dsyncable (this ensures that only the components you wish to be synced will be)
    3) Snippet files have a file extension ".php", chunks/templates have a file extension ".html" and template variable manifests have a file extension ".txml"

    The name of the items created will match the name of the filename that it is created under (ie. templates/Home_Page.html will create a template named "Home_Page" and it will parse templates/Home_Page.txml" for its template variables)

    Here is an example of a template manifest file:
    <vars>
    	<modTemplateVar type="dropdown" elements="Yes||No" display="string" default_text="No" display_params="%26format=">casestudy-hide-from-homepage</modTemplateVar>
    	<modTemplateVar type="image" elements="" display="default" default_text="" display_params="">casestudy-square-image</modTemplateVar>
    	<modTemplateVar type="text">casestudy-main-tag</modTemplateVar>
    	<modTemplateVar type="textarea">casestudy-awards</modTemplateVar>
    	<modTemplateVar type="image" elements="" display="default" default_text="" display_params="">casestudy-main-image</modTemplateVar>
    	<modTemplateVar type="image" elements="" display="default" default_text="" display_params="">casestudy-content-image</modTemplateVar>
    	<modTemplateVar type="image" elements="" display="default" default_text="" display_params="">casestudy-quote-image</modTemplateVar>
    	<modTemplateVar type="text" elements="" display="default" default_text="" display_params="">casestudy-quote-text</modTemplateVar>
    	<modTemplateVar type="text" elements="" display="default" default_text="" display_params="">casestudy-quote-name</modTemplateVar>
    	<modTemplateVar type="text" elements="" display="default" default_text="" display_params="">casestudy-quote-title</modTemplateVar>
    	<modTemplateVar type="text">casestudy-url</modTemplateVar>
    </vars>
    
    


    The main advantage i see to this method is for templates themselves. I work in a team environment where we all share a SVN code base and i wanted to allow the designer to work on the HTML/CSS statically without having to even think about ModX. As the template html files will render properly with all of their resources on their own without the need for a ModX installation. As well as being able to deploy a package to multiple environments from the command line.

    So yeah community let me know your thoughts, this hasnt been totally polished yet and i can think of a few bugs that i have not resolved, and i havnt added support for plugins yet either but i use this for all of my ModX projects at the moment. Enjoy! Its only 225 lines of plugin code, so i will just post it here. All of the "echos" in there are because previously i just ran it from the command line rather then integrating it as a plugin.

    <?php
    
    // predefined folders
    // /snippets
    // /chunks
    // /templates / template variables
    
    $components = scandir($modx->config['base_path'] . 'core/components');
    
    function dirSize($directory) { 
    			    $size = 0; 
    			    foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)) as $file){ 
    			        $size+=$file->getSize(); 
    			    } 
    			    return $size; 
    			} 
    
    foreach ($components as $component)
    {
    	// we dont want to fuck with code that does not usre this method
    	if (!file_exists($modx->config['base_path'] . 'core/components/' . $component . '/.dsyncable'))
    		continue;
    		
    	$basepath = $modx->base_path . 'core/components/' . $component;
    	
    	$to_sync = array(array('type' => 'modSnippet', 	'folder' => '/snippets', 	'ext' => '.php', 	'identifier' => 'name'),
    					 array('type' => 'modTemplate', 'folder' => '/templates', 	'ext' => '.html', 	'identifier' => 'templatename'),
    					 array('type' => 'modChunk', 	'folder' => '/chunks', 		'ext' => '.html', 	'identifier' => 'name'));
    								 
    	foreach ($to_sync as $sync_item)
    	{
    		$items = scandir($basepath . $sync_item['folder']);
    		
    		foreach ($items as $key => $item)
    		{
    			// we only want to sync html files to the database, everything else is just static content
    			if (strpos($item, $sync_item['ext']) === false)
    				unset($items[$key]);
    		}
    		
    		////echo "Found " . (count($items)) . " " . $sync_item['type'] . "\n";
    		
    		foreach ($items as $item)
    		{
    			// get the template name and content
    			$item_name = str_replace($sync_item['ext'], "", $item);
    			$item_content = file_get_contents($basepath . $sync_item['folder'] . '/' . $item);
    			
    			// check for existance, if not create it, otherwise update it with the FS data
    			if (!$obj = $modx->getObject($sync_item['type'], array($sync_item['identifier'] => $item_name)))
    			{
    				//echo "Creating New " . $sync_item['type'] . " - " . $item_name . "\n";
    				$obj = $modx->newObject($sync_item['type'], array($sync_item['identifier'] => $item_name));		
    			}
    			else 
    			{
    				//echo "Updating Existing " . $sync_item['type'] . " - " . $item_name . "\n";
    			}
    			
    			// update the content and save
    			$obj->setContent($item_content);
    			$obj->save();
    		}
    	}
    	
    	// template variables
    	$items = scandir($basepath . '/templates');
    	
    	foreach ($items as $key => $item)
    	{
    		// we only want to sync html files to the database, everything else is just static content
    		if (strpos($item, '.txml') === false)
    			unset($items[$key]);
    	}
    	
    	foreach ($items as $item)
    	{
    		// get the template name and content
    		$template_name = str_replace('.txml', "", $item);
    		$tv_content = file_get_contents($basepath . '/templates' . '/' . $item);
    		
    		if (!$tv_xml = simplexml_load_string($tv_content))
    		{
    			//echo "Failed to Load TV XML";
    			die;
    		}
    	
    		$template = $modx->getObject('modTemplate', array('templatename' => $template_name));
    		
    		//echo "Creating " . count($tv_xml->modTemplateVar) . " modTemplateVar/modTemplateVarTemplates\n";
    		
    		foreach ($tv_xml->modTemplateVar as $rank => $template_variable_name)
    		{
    			if (!$tv = $modx->getObject('modTemplateVar', array('name' => (string)$template_variable_name)))
    			{
    				//echo "Creating New modTemplateVar - " . $template_variable_name . "\n";
    				
    				$tv = $modx->newObject('modTemplateVar', array('name' => (string)$template_variable_name));
    				
    				$tv->set('type', 		$template_variable_name['type']);
    				$tv->set('caption', 	(string)$template_variable_name);
    				$tv->set('description', $template_variable_name['description']);
    				$tv->set('editor_type', $template_variable_name['editor_type']);
    				$tv->set('category', $template_variable_name['category']);
    				$tv->set('locked', $template_variable_name['locked']);
    				$tv->set('elements', $template_variable_name['elements']);
    				$tv->set('display', $template_variable_name['display']);
    				$tv->set('display', $template_variable_name['display']);
    				$tv->set('display_params', $template_variable_name['display_params']);
    				$tv->set('default_text', $template_variable_name['default_text']);
    				$tv->set('properties', $template_variable_name['properties']);
    				
    				$tv->save();
    				
    				$tv_assoc = $modx->newObject('modTemplateVarTemplate');
    					
    				$tv_assoc->set('tmplvarid', $tv->get('id'));
    				$tv_assoc->set('templateid', $template->get('id'));
    				$tv_assoc->set('rank', $rank);
    				
    				$tv_assoc->save();
    			}
    			else 
    			{
    				//echo "Updating Existing modTemplateVar - " . $template_variable_name . " ";
    				
    				$tv->set('type', 		$template_variable_name['type']);
    				$tv->set('caption', 	(string)$template_variable_name);
    				$tv->set('description', $template_variable_name['description']);
    				$tv->set('editor_type', $template_variable_name['editor_type']);
    				$tv->set('category', $template_variable_name['category']);
    				$tv->set('locked', $template_variable_name['locked']);
    				$tv->set('elements', $template_variable_name['elements']);
    				$tv->set('display', $template_variable_name['display']);
    				$tv->set('display', $template_variable_name['display']);
    				$tv->set('display_params', urldecode($template_variable_name['display_params']));
    				$tv->set('default_text', $template_variable_name['default_text']);
    				$tv->set('properties', $template_variable_name['properties']);
    				
    				$tv->save();
    				
    				if (!$tv_assoc = $modx->getObject('modTemplateVarTemplate', array('templateid' => $template->get('id'), 'tmplvarid' => $tv->get('id'))))
    				{
    					//echo " Creating Association to Template\n";
    					$tv_assoc = $modx->newObject('modTemplateVarTemplate');
    					
    					$tv_assoc->set('tmplvarid', $tv->get('id'));
    					$tv_assoc->set('templateid', $template->get('id'));
    					$tv_assoc->set('rank', $rank);
    					
    					$tv_assoc->save();
    				}
    				else 
    				{
    					//echo " Already Associated to Template\n";
    				}
    			}
    		}
    	}
    	
    	// static assets (javascript, css ect...)
    	
    	
    	
    	// template variables
    	$items = array();
    	$items = scandir($basepath . '/templates');
    	
    	foreach ($items as $key => $item)
    	{
    		// we only want to sync html files to the database, everything else is just static content
    		if (strpos($item, '.txml') !== false || strpos($item, '.html') !== false || $item == '.' || $item == '..' || $item == '.svn')
    			unset($items[$key]);
    	}
    	
    	//echo "Found " . (count($items)) . " Static Content Items\n";
    	
    	print_r($items);
    
    	foreach ($items as $item)
    	{
    		if (is_dir($basepath . '/templates/' . $item))
    		{
    			// lets check to make sure we are not syncing static resourses everytime (which can be big)
    			if (dirSize($basepath . '/templates/' . $item) == dirSize($modx->config['base_path'] . $item))
    			{
    				//echo "Folder Exists - " . $item . " Skipping\n";
    				continue;
    			}
    			else
    			{
    				//echo "Copying Folder - " . $item . "\n";
    			}
    		}
    		else 
    		{
    			// lets check to make sure we are not syncing static resourses everytime (which can be big)
    			if (filesize($basepath . '/templates/' . $item) == filesize($modx->config['base_path'] . $item))
    			{
    				//echo "File Exists - " . $item . " Skipping\n";
    				coninue;
    			}
    			else 
    			{
    				//echo "Copying File - " . $item . "\n";
    				
    			}
    		}
    		
    		
    		$cmd = 'cp -r ' . $basepath . '/templates/' . $item . ' ' . $modx->config['base_path'] . '.';
    
    		exec($cmd);
    	}
    			
    	
    	
    	// clear the modx cache
    }
    
    $cm = $modx->getCacheManager();
    $cm->clearCache();
    
    ?>
    
    

      • 24935
      • 160 Posts
      Thanks for your contribution. I was planning on creating something like this about a year ago, but never got to it, partially because it was becoming quite complex (I had plans to do a two-way sync and include resources). This would be great for svn/git development, although I wouldn’t like the extra time that it would add to every page load. Certainly you would disable it before making the site live, but I think the command line version would be better most of the time.
        • 10534
        • 40 Posts
        This seems like an interesting plugin: enabling it when you've added/changed some code in physical files (and disabling it again on live sites). It's been posted two and a half years ago, with just one reply.... Has anyone used this method?

        It seems strange that there still is no *real* core method for managing at least templates, chunks and snippets directly form the filesystem. It's hell managing code with a develop team when having to do it directly in the database. Adding symbolic links for each and every file is not a real solution in my opinion.

        I'll give this method a try one of these days and see if it'll work with the latest REVO codebase.
          • 3749
          • 24,544 Posts
          Maybe people have been using static elements instead. Have you tried that?
            Did I help you? Buy me a beer
            Get my Book: MODX:The Official Guide
            MODX info for everyone: http://bobsguides.com/modx.html
            My MODX Extras
            Bob's Guides is now hosted at A2 MODX Hosting
            • 10534
            • 40 Posts
            Well, the problem with static elements is that for every new file you create, you'll have yo manually add it @Modx. With many chunk, snippets, templates and TV's that's just not cutting it for me smiley
              • 3749
              • 24,544 Posts
              I agree that it's a pain. There should be default location and filename so all you have to do is check the static checkbox, or better yet, set a static_default System Setting.

              I'm usually working on a MODX extra project, so I create new objects through MyComponent, which does exactly what I described above. If the 'allStatic' setting is set it will do everything else automatically using the default media source -- all it needs are the names of the objects you're creating.

              I did have trouble at one time with static plugins, though I don't know if that's still an issue and I don't remember what the trouble was.
                Did I help you? Buy me a beer
                Get my Book: MODX:The Official Guide
                MODX info for everyone: http://bobsguides.com/modx.html
                My MODX Extras
                Bob's Guides is now hosted at A2 MODX Hosting
                • 9207 ☆ A M B ☆
                • 2,475 Posts
                This is a fertile topic, even if it's old... MODX has some frustrating limitations when it comes to syncing the database and filesystem. In general, I think a more usable solution here would be if MODX stored *everything* on the filesystem whenever possible, leaving *only* content and user data to the database. I realize the devil is in the details here and there is a web of little fine lines to this stuff. Maintaining a dev and prod environment brings this to the fore: it's really difficult to sync two environments like this.

                Part of this could be remedied with a "OnElementNotFound" and related events -- see http://tracker.modx.com/issues/9565
                That would let you have your cake and eat it too: the database could contain some stuff, and the file system could be the fallback. I have to think that some CMS's handle the dev/prod stuff a bit better...
                  • 10534
                  • 40 Posts
                  Well said... I've been actively using SilverStripe for the past two years, and I know, it's handling everything completely differently. But the way SilverStripe enables you to have dev/test/acc/prod environments just works (people are comparing SilverStripe with RoR). Having something like the "SilverStripe Development Tools" in MODX would be awesome. Just tick "/dev/build" (as an admin or on a 'dev'-mode site) and your database will be rebuild (non-destructive) with new objects (including any object relations). This type of tool could be used to be able to have chunks/snippets/templates/tv's/plugins on the filesystem and enabling/synching them to your MODX site/db.
                    • 9207 ☆ A M B ☆
                    • 2,475 Posts
                    I have some familiarity with Ruby on Rails, and I confess that I really have grown to like the rake/migration stuff for changing database structures. I've been using Laravel as my new PHP framework and it uses the same type of flow. Because xPDO can detect *existing* structures and generate schema and class files, it's so much easier to get things started in xPDO than it is when using any ActiveRecord derivative. I could see "MODX 3" (to throw that around) as incorporating some of those ideas.

                    A "dev tools" plugin would be welcome... I can imagine having a set of collection files similar to seed data in artisan to populate/override data based on environment. Something like dev/modsystemsettings.php and prod/modsystemsettings.php And then some process to pull things over and create a new branch in git.

                    The problem can never be solved *completely* because page ids will be used throughout the site, and those make maintaining environments very difficult. Page ids show up in

                    * Pages (links between pages)
                    * Chunks
                    * Templates
                    * TVs
                    * TV Values
                      • 3749
                      • 24,544 Posts
                      It might be feasible to write plugins connected to OnLoadWebDocument or OnParseDocument to translate page IDs in tags into aliases or vice versa.
                        Did I help you? Buy me a beer
                        Get my Book: MODX:The Official Guide
                        MODX info for everyone: http://bobsguides.com/modx.html
                        My MODX Extras
                        Bob's Guides is now hosted at A2 MODX Hosting