├── .gitignore ├── _build ├── build.config.sample.php ├── build.transport.php └── data │ ├── transport.pluginevents.php │ ├── transport.plugins.php │ └── transport.settings.php ├── elementhelper ├── docs │ ├── changelog.txt │ ├── license.txt │ └── readme.txt ├── elements │ └── plugins │ │ └── plugin.elementhelper.php ├── lexicon │ └── en │ │ └── default.inc.php └── model │ ├── element.class.php │ ├── elementhelper.class.php │ ├── elementsync.class.php │ └── filehelper.class.php └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | _build/build.config.php -------------------------------------------------------------------------------- /_build/build.config.sample.php: -------------------------------------------------------------------------------- 1 | $root, 20 | 'build' => $root . '_build/', 21 | 'data' => $root . '_build/data/', 22 | 'lexicon' => $root . PKG_NAME_LOWER . '/lexicon/', 23 | 'docs' => $root . PKG_NAME_LOWER . '/docs/', 24 | 'elements' => $root . PKG_NAME_LOWER . '/elements/', 25 | 'source_core' => $root . PKG_NAME_LOWER 26 | ); 27 | 28 | $modx = new modX(); 29 | $modx->initialize('mgr'); 30 | 31 | echo '
';
 32 | 
 33 | $modx->setLogLevel(modX::LOG_LEVEL_INFO);
 34 | $modx->setLogTarget('ECHO');
 35 | $modx->loadClass('transport.modPackageBuilder', '', false, true);
 36 | 
 37 | $builder = new modPackageBuilder($modx);
 38 | $builder->createPackage(PKG_NAME_LOWER, PKG_VERSION, PKG_RELEASE);
 39 | $builder->registerNamespace(
 40 |     PKG_NAME_LOWER, 
 41 |     false, 
 42 |     true, 
 43 |     '{core_path}components/' . PKG_NAME_LOWER . '/'
 44 | );
 45 | 
 46 | //////////////////////////////////////////////////
 47 | //
 48 | // Package in the settings
 49 | //
 50 | //////////////////////////////////////////////////
 51 | 
 52 | // Package in the settings
 53 | $modx->log(modX::LOG_LEVEL_INFO, 'Packaging in settings...');
 54 | 
 55 | $settings = include $sources['data'] . 'transport.settings.php';
 56 | 
 57 | $attributes= array(
 58 |     xPDOTransport::UNIQUE_KEY => 'key',
 59 |     xPDOTransport::PRESERVE_KEYS => true,
 60 |     xPDOTransport::UPDATE_OBJECT => false,
 61 | );
 62 | 
 63 | foreach ($settings as $setting)
 64 | {
 65 |     $vehicle = $builder->createVehicle($setting, $attributes);
 66 |     $builder->putVehicle($vehicle);
 67 | }
 68 | 
 69 | $modx->log(modX::LOG_LEVEL_INFO, 'Packaged in settings.'); flush();
 70 | 
 71 | //////////////////////////////////////////////////
 72 | //
 73 | // Package in the category & plugin
 74 | //
 75 | //////////////////////////////////////////////////
 76 | 
 77 | $modx->log(modX::LOG_LEVEL_INFO, 'Packaging in category...');
 78 | 
 79 | // Setup the package category
 80 | $category = $modx->newObject('modCategory');
 81 | $category->set('id', 1);
 82 | $category->set('category', PKG_NAME);
 83 | 
 84 | $plugins = include $sources['data'] . 'transport.plugins.php';
 85 | 
 86 | $category->addMany($plugins);
 87 | 
 88 | $attributes = array(
 89 |     xPDOTransport::UNIQUE_KEY => 'category',
 90 |     xPDOTransport::PRESERVE_KEYS => false,
 91 |     xPDOTransport::UPDATE_OBJECT => true,
 92 |     xPDOTransport::RELATED_OBJECTS => true,
 93 |     xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(
 94 |         'Plugins' => array(
 95 |             xPDOTransport::UNIQUE_KEY => 'name',
 96 |             xPDOTransport::PRESERVE_KEYS => false,
 97 |             xPDOTransport::UPDATE_OBJECT => true,
 98 |             xPDOTransport::RELATED_OBJECTS => true,
 99 |             xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array(
100 |                 'PluginEvents' => array(
101 |                     xPDOTransport::UNIQUE_KEY => array('pluginid', 'event'),
102 |                     xPDOTransport::PRESERVE_KEYS => true,
103 |                     xPDOTransport::UPDATE_OBJECT => false
104 |                 )
105 |             )
106 |         )
107 |     )
108 | );
109 | 
110 | $vehicle = $builder->createVehicle($category, $attributes);
111 | 
112 | $modx->log(modX::LOG_LEVEL_INFO, 'Adding file resolvers to category...');
113 | 
114 | $vehicle->resolve('file', array(
115 |     'source' => $sources['source_core'],
116 |     'target' => "return MODX_CORE_PATH . 'components/';",
117 | ));
118 | 
119 | $modx->log(modX::LOG_LEVEL_INFO, 'Packaged in resolvers.'); flush();
120 | 
121 | $builder->putVehicle($vehicle);
122 | 
123 | //////////////////////////////////////////////////
124 | //
125 | // Add the package attributes
126 | //
127 | //////////////////////////////////////////////////
128 | 
129 | $modx->log(modX::LOG_LEVEL_INFO,'Adding package attributes...');
130 | 
131 | $builder->setPackageAttributes(array(
132 |     'license' => file_get_contents($sources['docs'] . 'license.txt'),
133 |     'readme' => file_get_contents($sources['docs'] . 'readme.txt'),
134 |     'changelog' => file_get_contents($sources['docs'] . 'changelog.txt')
135 | ));
136 | 
137 | $modx->log(modX::LOG_LEVEL_INFO, 'Packing up transport package zip...'); flush();
138 | 
139 | $builder->pack();
140 |  
141 | $tend= explode(" ", microtime());
142 | $tend= $tend[1] + $tend[0];
143 | $totalTime= sprintf("%2.4f s", ($tend - $tstart));
144 | $modx->log(modX::LOG_LEVEL_INFO, "\n
Package Built.
\nExecution time: {$totalTime}\n"); 145 | 146 | exit (); -------------------------------------------------------------------------------- /_build/data/transport.pluginevents.php: -------------------------------------------------------------------------------- 1 | newObject('modPluginEvent'); 6 | $events[1]->set('event', 'OnWebPageInit'); 7 | $events[1]->set('priority', 0); 8 | $events[1]->set('propertyset', 0); 9 | 10 | $events[2] = $modx->newObject('modPluginEvent'); 11 | $events[2]->set('event', 'OnManagerPageInit'); 12 | $events[2]->set('priority', 0); 13 | $events[2]->set('propertyset', 0); 14 | 15 | return $events; -------------------------------------------------------------------------------- /_build/data/transport.plugins.php: -------------------------------------------------------------------------------- 1 | newObject('modPlugin'); 7 | 8 | $plugins[1]->set('id', 1); 9 | $plugins[1]->set('name', 'ElementHelper'); 10 | $plugins[1]->set('description', 'Creates elements automatically from static files.'); 11 | 12 | $plugins[1]->setContent(file_get_contents($sources['elements'] . 'plugins/plugin.elementhelper.php')); 13 | 14 | $plugins[1]->addMany($events); 15 | 16 | return $plugins; -------------------------------------------------------------------------------- /_build/data/transport.settings.php: -------------------------------------------------------------------------------- 1 | newObject('modSystemSetting'); 6 | $settings['elementhelper.chunk_path']->fromArray(array( 7 | 'key' => 'elementhelper.chunk_path', 8 | 'value' => 'site/elements/chunks/', 9 | 'xtype' => 'textfield', 10 | 'namespace' => 'elementhelper', 11 | 'area' => 'paths' 12 | ), '', true, true); 13 | 14 | $settings['elementhelper.template_path'] = $modx->newObject('modSystemSetting'); 15 | $settings['elementhelper.template_path']->fromArray(array( 16 | 'key' => 'elementhelper.template_path', 17 | 'value' => 'site/elements/templates/', 18 | 'xtype' => 'textfield', 19 | 'namespace' => 'elementhelper', 20 | 'area' => 'paths' 21 | ), '', true, true); 22 | 23 | $settings['elementhelper.plugin_path'] = $modx->newObject('modSystemSetting'); 24 | $settings['elementhelper.plugin_path']->fromArray(array( 25 | 'key' => 'elementhelper.plugin_path', 26 | 'value' => 'site/elements/plugins/', 27 | 'xtype' => 'textfield', 28 | 'namespace' => 'elementhelper', 29 | 'area' => 'paths' 30 | ), '', true, true); 31 | 32 | $settings['elementhelper.snippet_path'] = $modx->newObject('modSystemSetting'); 33 | $settings['elementhelper.snippet_path']->fromArray(array( 34 | 'key' => 'elementhelper.snippet_path', 35 | 'value' => 'site/elements/snippets/', 36 | 'xtype' => 'textfield', 37 | 'namespace' => 'elementhelper', 38 | 'area' => 'paths' 39 | ), '', true, true); 40 | 41 | $settings['elementhelper.tv_file_path'] = $modx->newObject('modSystemSetting'); 42 | $settings['elementhelper.tv_file_path']->fromArray(array( 43 | 'key' => 'elementhelper.tv_file_path', 44 | 'value' => 'site/elements/template_variables.json', 45 | 'xtype' => 'textfield', 46 | 'namespace' => 'elementhelper', 47 | 'area' => 'paths' 48 | ), '', true, true); 49 | 50 | $settings['elementhelper.element_sync_file_path'] = $modx->newObject('modSystemSetting'); 51 | $settings['elementhelper.element_sync_file_path']->fromArray(array( 52 | 'key' => 'elementhelper.element_sync_file_path', 53 | 'value' => 'site/elements/element_sync.json', 54 | 'xtype' => 'textfield', 55 | 'namespace' => 'elementhelper', 56 | 'area' => 'paths' 57 | ), '', true, true); 58 | 59 | $settings['elementhelper.usergroups'] = $modx->newObject('modSystemSetting'); 60 | $settings['elementhelper.usergroups']->fromArray(array( 61 | 'key' => 'elementhelper.usergroups', 62 | 'value' => 'Administrator', 63 | 'xtype' => 'textfield', 64 | 'namespace' => 'elementhelper', 65 | 'area' => 'config' 66 | ), '', true, true); 67 | 68 | $settings['elementhelper.tv_access_control'] = $modx->newObject('modSystemSetting'); 69 | $settings['elementhelper.tv_access_control']->fromArray(array( 70 | 'key' => 'elementhelper.tv_access_control', 71 | 'value' => 0, 72 | 'xtype' => 'combo-boolean', 73 | 'namespace' => 'elementhelper', 74 | 'area' => 'config' 75 | ), '', true, true); 76 | 77 | $settings['elementhelper.category_whitelist'] = $modx->newObject('modSystemSetting'); 78 | $settings['elementhelper.category_whitelist']->fromArray(array( 79 | 'key' => 'elementhelper.category_whitelist', 80 | 'value' => '', 81 | 'xtype' => 'textfield', 82 | 'namespace' => 'elementhelper', 83 | 'area' => 'config' 84 | ), '', true, true); 85 | 86 | $settings['elementhelper.element_blacklist'] = $modx->newObject('modSystemSetting'); 87 | $settings['elementhelper.element_blacklist']->fromArray(array( 88 | 'key' => 'elementhelper.element_blacklist', 89 | 'value' => 'TinyMCE, Wayfinder, getResources, ClientConfig', 90 | 'xtype' => 'textfield', 91 | 'namespace' => 'elementhelper', 92 | 'area' => 'config' 93 | ), '', true, true); 94 | 95 | return $settings; -------------------------------------------------------------------------------- /elementhelper/docs/changelog.txt: -------------------------------------------------------------------------------- 1 | ==================== 2 | Version 2.0.0 3 | ==================== 4 | 5 | - Initial release -------------------------------------------------------------------------------- /elementhelper/docs/license.txt: -------------------------------------------------------------------------------- 1 | Element Helper - A MODx plugin for automatically creating elements 2 | from static files without the manager. 3 | 4 | Copyright (C) 2014 Rory Gibson 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | http://www.gnu.org/licenses/ -------------------------------------------------------------------------------- /elementhelper/docs/readme.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | Extra: Element Helper 3 | ===================== 4 | 5 | Version: 2.0.0 6 | 7 | Element Helper is a MODx Revolution plugin for automatically creating elements from static files without the MODx manager. 8 | 9 | For instructions on using ElementHelper please see - https://github.com/roryg/ElementHelper -------------------------------------------------------------------------------- /elementhelper/elements/plugins/plugin.elementhelper.php: -------------------------------------------------------------------------------- 1 | getOption('core_path') . 'components/elementhelper/'; 3 | $core_path = $modx->getOption('elementhelper.core_path', null, $default_core_path); 4 | 5 | $usergroups = explode(',', $modx->getOption('elementhelper.usergroups', null, 'Administrator')); 6 | 7 | // Return if the user isn't part of one of the allowed usergroups 8 | if ( ! $modx->user->isMember($usergroups)) 9 | { 10 | return; 11 | } 12 | 13 | // Load in our classes 14 | $modx->loadClass('FileHelper', $core_path . 'model/', true, true); 15 | $modx->loadClass('Element', $core_path . 'model/', true, true); 16 | $modx->loadClass('ElementHelper', $core_path . 'model/', true, true); 17 | $modx->loadClass('ElementSync', $core_path . 'model/', true, true); 18 | 19 | // Path to the element sync json file 20 | $element_sync_file = MODX_BASE_PATH . $modx->getOption('elementhelper.element_sync_file_path', null, 'site/elements/element_sync.json'); 21 | 22 | // Initialize the classes 23 | $element_helper = new ElementHelper($modx); 24 | $element_sync = new ElementSync($modx, $element_sync_file); 25 | 26 | $element_types = array( 27 | 'modTemplate' => $modx->getOption('elementhelper.template_path', null, 'site/elements/templates/'), 28 | 'modChunk' => $modx->getOption('elementhelper.chunk_path', null, 'site/elements/chunks/'), 29 | 'modSnippet' => $modx->getOption('elementhelper.snippet_path', null, 'site/elements/snippets/'), 30 | 'modPlugin' => $modx->getOption('elementhelper.plugin_path', null, 'site/elements/plugins/') 31 | ); 32 | 33 | $category_whitelist = array_map('trim', explode(',', $modx->getOption('elementhelper.category_whitelist', null, ''))); 34 | $element_blacklist = array_map('trim', explode(',', $modx->getOption('elementhelper.element_blacklist', null, ''))); 35 | 36 | // Loop through the element types 37 | foreach ($element_types as $type => $type_path) 38 | { 39 | $log_prefix = sprintf('[ElementHelper] %s: ', $type); 40 | $file_list = FileHelper::get_directory_file_list(MODX_BASE_PATH . $type_path); 41 | 42 | // Move onto the next element type if it has no files 43 | if (empty($file_list)) 44 | { 45 | $modx->log(MODX_LOG_LEVEL_INFO, $log_prefix . 'No files.'); 46 | 47 | continue; 48 | } 49 | 50 | // Process the files for this element type 51 | foreach ($file_list as $file_path) 52 | { 53 | $file = FileHelper::get_file_meta($file_path); 54 | $element = Element::get($modx, $type, $file['name']); 55 | 56 | // If the file is not in the sync 57 | if ( ! $element_sync->has_element($type, $file['name'])) 58 | { 59 | // If the element doesn't exist 60 | if ( ! $element) 61 | { 62 | // Create the element 63 | $element = Element::create($modx, $type, $file['name']); 64 | 65 | // If the element is created successfully set it's properties and then add it to the sync 66 | if ($element) 67 | { 68 | $properties = $element_helper->get_file_element_properties($type_path, $file_path); 69 | 70 | if ($element->set_properties($properties)) 71 | { 72 | $element_sync->add_element($type, $file['name'], $file['mod_time']); 73 | } 74 | } 75 | } 76 | else 77 | { 78 | $modx->log(MODX_LOG_LEVEL_INFO, $log_prefix . 'An element with the name [' . $file['name'] . '] already exists. Unable to sync the file and element.'); 79 | } 80 | } 81 | else 82 | { 83 | // If the element doesn't exist 84 | if ( ! $element) 85 | { 86 | // Delete the file and remove it from the sync if successful 87 | if (unlink($file_path)) 88 | { 89 | $element_sync->remove_element($type, $file['name']); 90 | } 91 | } 92 | else 93 | { 94 | // If file has been updated, update the element and sync 95 | if ($file['mod_time'] > $element_sync->get_element_mod_time($type, $file['name'])) 96 | { 97 | $properties = $element_helper->get_file_element_properties($type_path, $file_path); 98 | 99 | if ($element->set_properties($properties)) 100 | { 101 | $element_sync->add_element($type, $file['name'], $file['mod_time']); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | // Process the elements for this element type 109 | foreach ($modx->getCollection($type) as $element_object) 110 | { 111 | $element = Element::insert($element_object); 112 | $name = $element->get_property('name'); 113 | $category_id = $element->get_property('category'); 114 | $file_path = $element_helper->build_element_file_path($type, $type_path, $name, $category_id); 115 | 116 | // Check if the element is blacklisted 117 | if (in_array($name, $element_blacklist)) 118 | { 119 | continue; 120 | } 121 | 122 | // Check if the element has a category and is whitelisted 123 | if ($category_id !== 0) 124 | { 125 | $category = Element::get($modx, 'modCategory', $category_id); 126 | 127 | if ( ! in_array($category->get_property('name'), $category_whitelist)) 128 | { 129 | continue; 130 | } 131 | } 132 | 133 | // If a file with this element name doesn't exist 134 | if ( ! file_exists($file_path)) 135 | { 136 | // If the element is not in the sync 137 | if ( ! $element_sync->has_element($type, $name)) 138 | { 139 | $properties = $element_helper->get_element_static_file_properties($element, $file_path); 140 | 141 | if ($element->set_properties($properties)) 142 | { 143 | $file_mod_time = filemtime($file_path); 144 | $element_sync->add_element($type, $name, $file_mod_time); 145 | } 146 | } 147 | else 148 | { 149 | // Remove the element and remove it from the sync if successful 150 | if ($element->remove()) 151 | { 152 | $element_sync->remove_element($type, $name); 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | $log_prefix = '[ElementHelper] modTemplateVar: '; 160 | $tv_file_path = MODX_BASE_PATH . $modx->getOption('elementhelper.tv_file_path', null, 'site/elements/template_variables.json'); 161 | 162 | if (file_exists($tv_file_path)) 163 | { 164 | $tv_file_contents = file_get_contents($tv_file_path); 165 | $tv_file_mod_time = filemtime($tv_file_path); 166 | $tvs = ($tv_file_contents !== '' ? json_decode($tv_file_contents) : array()); 167 | $flagged_tvs = array(); 168 | 169 | // Loop through the template variables in the file 170 | foreach ($tvs as $i => $tv) 171 | { 172 | $element = Element::get($modx, 'modTemplateVar', $tv->name); 173 | 174 | // If the element is not in the sync 175 | if ( ! $element_sync->has_element('modTemplateVar', $tv->name)) 176 | { 177 | // If the tv doesn't exist 178 | if ( ! $element) 179 | { 180 | // Create the element 181 | $element = Element::create($modx, 'modTemplateVar', $tv->name); 182 | 183 | // If the element is created successfully 184 | if ($element) 185 | { 186 | $properties = $element_helper->get_tv_element_properties($tv); 187 | 188 | // If templates have been specified and permission to pair tvs with templates has been given 189 | if (isset($tv->template_access) && $modx->getOption('elementhelper.tv_access_control', null, false) == true) 190 | { 191 | $element_helper->setup_tv_template_access($element->get_property('id'), $tv->template_access); 192 | } 193 | 194 | // If a media source has been specified assign it to the TV 195 | if (isset($tv->media_source)) 196 | { 197 | $element_helper->setup_tv_media_source($element->get_property('id'), $tv->media_source); 198 | } 199 | 200 | // Set the tv properties and then add it to the sync 201 | if ($element->set_properties($properties)) 202 | { 203 | $element_sync->add_element('modTemplateVar', $tv->name, $tv_file_mod_time); 204 | } 205 | } 206 | } 207 | else 208 | { 209 | $modx->log(MODX_LOG_LEVEL_INFO, $log_prefix . 'An element with the name [' . $tv->name . '] already exists. Unable to sync the element.'); 210 | } 211 | } 212 | else 213 | { 214 | // If the tv doesn't exist 215 | if ( ! $element) 216 | { 217 | // Flag the tv for removal after we've checked all tvs in the file 218 | $flagged_tvs[] = $i; 219 | } 220 | else 221 | { 222 | // If the template variable file has been updated update the tv element and sync 223 | if ($tv_file_mod_time > $element_sync->get_element_mod_time('modTemplateVar', $tv->name)) 224 | { 225 | $properties = $element_helper->get_tv_element_properties($tv); 226 | 227 | // If templates have been specified and permission to pair tvs with templates has been given 228 | if (isset($tv->template_access) && $modx->getOption('elementhelper.tv_access_control', null, false) == true) 229 | { 230 | $element_helper->setup_tv_template_access($element->get_property('id'), $tv->template_access); 231 | } 232 | 233 | // If a media source has been specified assign it to the TV 234 | if (isset($tv->media_source)) 235 | { 236 | $element_helper->setup_tv_media_source($element->get_property('id'), $tv->media_source); 237 | } 238 | 239 | // Set the tv properties and then add it to the sync 240 | if ($element->set_properties($properties)) 241 | { 242 | $element_sync->add_element('modTemplateVar', $tv->name, $tv_file_mod_time); 243 | } 244 | } 245 | } 246 | } 247 | } 248 | 249 | // Remove any flagged tvs 250 | if (count($flagged_tvs) > 0) 251 | { 252 | $updated_tvs = $tvs; 253 | 254 | foreach ($flagged_tvs as $i) 255 | { 256 | unset($updated_tvs[$i]); 257 | } 258 | 259 | // Update the template variable file and remove the tvs from the sync if successfull 260 | if ($element_helper->update_tv_file($updated_tvs)) 261 | { 262 | foreach ($flagged_tvs as $i) 263 | { 264 | $element_sync->remove_element('modTemplateVar', $tvs[$i]->name); 265 | } 266 | } 267 | } 268 | 269 | // Process the template variable elements 270 | foreach ($modx->getCollection('modTemplateVar') as $element_object) 271 | { 272 | // Check if the tv exists in the template variables file 273 | $element = Element::insert($element_object); 274 | $name = $element->get_property('name'); 275 | $category_id = $element->get_property('category'); 276 | $tv_file_has_tv = false; 277 | 278 | // Check if the element has a category and is whitelisted 279 | if ($category_id !== 0) 280 | { 281 | $category = Element::get($modx, 'modCategory', $category_id); 282 | 283 | if ( ! in_array($category->get_property('name'), $category_whitelist)) 284 | { 285 | continue; 286 | } 287 | } 288 | 289 | // Loop through the tvs to check if it exists in the template variables file 290 | foreach ($tvs as $i => $tv) 291 | { 292 | if ($tv->name === $name) 293 | { 294 | $tv_file_has_tv = true; 295 | } 296 | } 297 | 298 | // If the tv doesn't exist in the template variables json file 299 | if ($tv_file_has_tv === false) 300 | { 301 | // If the element is not in the sync 302 | if ( ! $element_sync->has_element('modTemplateVar', $name)) 303 | { 304 | // Collect the tv element properties 305 | $new_tv= array(array( 306 | 'name' => $name, 307 | 'caption' => $element->get_property('caption'), 308 | 'type' => $element->get_property('type'), 309 | 'description' => $element->get_property('description'), 310 | 'category' => ($element->get_property('category') !== 0 ? $element->get_property('category') : null), 311 | 'locked' => $element->get_property('locked'), 312 | 'elements' => $element->get_property('elements'), 313 | 'rank' => $element->get_property('rank'), 314 | 'display' => $element->get_property('display'), 315 | 'default_text' => $element->get_property('default_text'), 316 | 'properties' => $element->get_property('properties'), 317 | 'input_properties' => $element->get_property('input_properties'), 318 | 'output_properties' => $element->get_property('output_properties') 319 | )); 320 | 321 | // Fix migx json properties 322 | if (isset($new_tv[0]['input_properties']['formtabs'])) 323 | { 324 | $new_tv[0]['input_properties']['formtabs'] = json_decode($new_tv[0]['input_properties']['formtabs']); 325 | $new_tv[0]['input_properties']['columns'] = json_decode($new_tv[0]['input_properties']['columns']); 326 | } 327 | 328 | $updated_tvs = array_merge($tvs, $new_tv); 329 | 330 | // Update the template variables file and add the tv to the sync 331 | if ($element_helper->update_tv_file($updated_tvs)) 332 | { 333 | $tv_file_mod_time = filemtime($tv_file_path); 334 | $element_sync->add_element('modTemplateVar', $name, $tv_file_mod_time); 335 | } 336 | } 337 | else 338 | { 339 | // Remove the element and remove it from the sync if successful 340 | if ($element->remove()) 341 | { 342 | $element_sync->remove_element('modTemplateVar', $name); 343 | } 344 | } 345 | } 346 | } 347 | } -------------------------------------------------------------------------------- /elementhelper/lexicon/en/default.inc.php: -------------------------------------------------------------------------------- 1 | element = $element; 10 | } 11 | 12 | /** 13 | * Creates an element object of the specified type 14 | * 15 | * @param modX $modx 16 | * @param string $type 17 | * @param string $name 18 | * 19 | * @return Element | boolean 20 | */ 21 | public static function create(modX $modx, $type, $name) 22 | { 23 | $element = $modx->newObject($type); 24 | 25 | $element->set(Element::get_name_field($type), $name); 26 | 27 | if ($element->save()) 28 | { 29 | return new Element($element); 30 | } 31 | 32 | return false; 33 | } 34 | 35 | /** 36 | * Gets an element object of the specifed type 37 | * 38 | * @todo Keep getting "Call to a member function getObject() on a non-object" 39 | * 40 | * @param modX $modx 41 | * @param string $type 42 | * @param integer | string $criteria (ID or name of the element) 43 | * 44 | * @return Element | boolean 45 | */ 46 | public static function get(modX $modx, $type, $criteria) 47 | { 48 | if (is_int($criteria)) 49 | { 50 | $element = $modx->getObject($type, $criteria); 51 | } 52 | else 53 | { 54 | $element = $modx->getObject($type, array((Element::get_name_field($type)) => $criteria)); 55 | } 56 | 57 | if (isset($element)) 58 | { 59 | return new Element($element); 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * Simply starts a new Element instance with the passed element object 67 | * 68 | * @param object $element 69 | * 70 | * @return Element 71 | */ 72 | public static function insert($element) 73 | { 74 | return new Element($element); 75 | } 76 | 77 | /** 78 | * Removes an element 79 | * 80 | * @return boolean 81 | */ 82 | public function remove() 83 | { 84 | if ($this->element->remove()) 85 | { 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /** 93 | * Weirdly Modx uses a different title for the name field of various element 94 | * types. This simplifies getting it. 95 | * 96 | * @param string $type 97 | * 98 | * @return string 99 | */ 100 | private static function get_name_field($type) 101 | { 102 | switch($type) 103 | { 104 | case 'modTemplate' : 105 | return 'templatename'; 106 | case 'modCategory' : 107 | return 'category'; 108 | default : 109 | return 'name'; 110 | } 111 | } 112 | 113 | /** 114 | * Loops through the supplied properties array and sets them on 115 | * the $element object 116 | * 117 | * @todo add name to this? 118 | * 119 | * @param array $properties 120 | * 121 | * @return boolean 122 | */ 123 | public function set_properties($properties) 124 | { 125 | foreach ($properties as $property => $value) 126 | { 127 | if ( ! isset($value)) 128 | { 129 | continue; 130 | } 131 | 132 | if ($property === 'content') 133 | { 134 | $this->element->setContent($value); 135 | } 136 | else 137 | { 138 | $this->element->set($property, $value); 139 | } 140 | } 141 | 142 | if ($this->element->save()) 143 | { 144 | return true; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | /** 151 | * Gets the specifed properties value from the element object 152 | * 153 | * @param string $property 154 | * 155 | * @return string 156 | */ 157 | public function get_property($property) 158 | { 159 | if ($property === 'name') 160 | { 161 | $name_field = Element::get_name_field($this->element->_class); 162 | 163 | return $this->element->get($name_field); 164 | } 165 | 166 | if ($property === 'content') 167 | { 168 | return $this->element->getContent(); 169 | } 170 | 171 | return $this->element->get($property); 172 | } 173 | } -------------------------------------------------------------------------------- /elementhelper/model/elementhelper.class.php: -------------------------------------------------------------------------------- 1 | modx = $modx; 11 | 12 | $modx->loadClass('Element', $modx->getOption('elementhelper.core_path') . 'model/', true, true); 13 | $modx->loadClass('FileHelper', $modx->getOption('elementhelper.core_path') . 'model/', true, true); 14 | } 15 | 16 | /** 17 | * Creates categories from an array of category names. Each category is 18 | * the parent of the following category. The last category in the tree 19 | * is returned. 20 | * 21 | * @param array $categories 22 | * 23 | * @return Element | boolean 24 | */ 25 | private function create_category_tree($categories) 26 | { 27 | foreach($categories as $category_name) 28 | { 29 | $parent_id = (isset($category) ? $category->get_property('id') : 0); 30 | $category = $this->create_category($category_name, $parent_id); 31 | 32 | if ( ! $category) 33 | { 34 | return false; 35 | } 36 | } 37 | 38 | // Return the last category made 39 | return $category; 40 | } 41 | 42 | /** 43 | * Creates and returns a category object 44 | * 45 | * @param string $name 46 | * @param int $parent_id 47 | * 48 | * @return Element | boolean 49 | */ 50 | private function create_category($name, $parent_id = 0) 51 | { 52 | $category = Element::get($this->modx, 'modCategory', $name); 53 | 54 | if ( ! $category) 55 | { 56 | $category = Element::create($this->modx, 'modCategory', $name); 57 | } 58 | 59 | $properties = array( 60 | 'parent' => $parent_id 61 | ); 62 | 63 | if ($category->set_properties($properties)) 64 | { 65 | return $category; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | /** 72 | * Returns a category tree as a forward-slash delimited path. Used to 73 | * create file paths when making an element static. 74 | * 75 | * @param integer $id 76 | * 77 | * @return string 78 | */ 79 | private function get_category_tree_path($id) 80 | { 81 | if ($id === 0) 82 | { 83 | $category = Element::get($this->modx, 'modCategory', $id); 84 | $path = $category->get_property('name'); 85 | } 86 | 87 | while ($id !== 0) 88 | { 89 | $category = Element::get($this->modx, 'modCategory', $id); 90 | $path = (isset($path) ? $category->get_property('name') . '/' . $path : $category->get_property('name') . '/'); 91 | $id = $category->get_property('parent'); 92 | } 93 | 94 | return $path; 95 | } 96 | 97 | /** 98 | * Gets the meta information for a file element e.g. description 99 | * 100 | * @param string $file_content 101 | * 102 | * @return array 103 | */ 104 | private function get_file_element_meta($file_content) 105 | { 106 | $meta = array(); 107 | $comments = FileHelper::get_file_doc_comments($file_content); 108 | 109 | foreach ($comments as $comment) 110 | { 111 | $comment_lines = explode("\n", $comment); 112 | 113 | foreach($comment_lines as $comment_line) 114 | { 115 | if (preg_match('/@Description (.*)/', $comment_line, $match)) 116 | { 117 | $meta['description'] = trim($match[1]); 118 | } 119 | } 120 | } 121 | 122 | return $meta; 123 | } 124 | 125 | /** 126 | * Gets and returns the properties for a file to be saved into an 127 | * element 128 | * 129 | * @param string $type_path 130 | * @param string $path 131 | * 132 | * @return array 133 | */ 134 | public function get_file_element_properties($type_path, $path) 135 | { 136 | $content = file_get_contents($path); 137 | $meta = $this->get_file_element_meta($content); 138 | 139 | // Get the files parent directories to use for building the categories 140 | $category_path = dirname(str_replace(MODX_BASE_PATH . $type_path, '', $path)); 141 | 142 | if ($category_path !== '.') 143 | { 144 | $categories = explode('/', $category_path); 145 | $category = $this->create_category_tree($categories); 146 | } 147 | 148 | $properties = array( 149 | 'source' => 1, 150 | 'static' => 1, 151 | 'static_file' => str_replace(MODX_BASE_PATH, '', $path), 152 | 'description' => (isset($meta['description']) ? $meta['description'] : ''), 153 | 'content' => $content, 154 | 'category' => (isset($category) ? $category->get_property('id') : null) 155 | ); 156 | 157 | return $properties; 158 | } 159 | 160 | /** 161 | * Builds the file path for an element 162 | * 163 | * @param string $type 164 | * @param string $type_path 165 | * @param string $name 166 | * @param string $category 167 | * 168 | * @return string 169 | */ 170 | public function build_element_file_path($type, $type_path, $name, $category) 171 | { 172 | $extension = ($type === 'modTemplate' || $type === 'modChunk' ? '.tpl' : '.php'); 173 | $file_name = $name . $extension; 174 | $file_path = MODX_BASE_PATH . $type_path; 175 | $file_path .= ($category === 0 ? $file_name : $this->get_category_tree_path($category) . $file_name); 176 | 177 | return $file_path; 178 | } 179 | 180 | /** 181 | * Creates a doc comment for element meta to be appended to the top of 182 | * an elements file. 183 | * 184 | * @todo See if there's a better way to do this 185 | * 186 | * @param array $meta 187 | * 188 | * @return string 189 | */ 190 | private function build_meta_doc_comment($meta) 191 | { 192 | $output = " $value) 195 | { 196 | $output .= sprintf('* @%s %s', ucfirst($tag), $value); 197 | } 198 | 199 | $output .= "\n *\n */ ?>\n\n"; 200 | 201 | return $output; 202 | } 203 | 204 | /** 205 | * Gets the the properties of an element for it's static file 206 | * 207 | * @todo maybe change the name of this 208 | * 209 | * @param Element $element 210 | * @param string $path 211 | * 212 | * @return array 213 | */ 214 | public function get_element_static_file_properties($element, $path) 215 | { 216 | $meta = array( 217 | 'description' => $element->get_property('description') 218 | ); 219 | 220 | $content = $this->build_meta_doc_comment($meta); 221 | $content .= $element->get_property('content'); 222 | 223 | $properties = array( 224 | 'content' => $content, 225 | 'source' => 1, 226 | 'static' => 1, 227 | 'static_file' => str_replace(MODX_BASE_PATH, '', $path) 228 | ); 229 | 230 | return $properties; 231 | } 232 | 233 | /** 234 | * Gets the properties for a template variable 235 | * 236 | * @todo map weird named properties to more sensible ones e.g. input option values is elements 237 | * @todo allow processing of additional values for properties like "display" e.g. when it's a url (related to output_properties?) 238 | * 239 | * @param object $tv 240 | * 241 | * @return array 242 | */ 243 | public function get_tv_element_properties($tv) 244 | { 245 | $properties = (array) $tv; 246 | 247 | // Properties that require processing beyond just setting the value 248 | $complex_properties = array( 249 | 'name', 250 | 'category', 251 | 'input_properties', 252 | 'template_access' 253 | ); 254 | 255 | // Remove the complex properties 256 | foreach ($complex_properties as $property) 257 | { 258 | if (array_key_exists($property, $properties)) 259 | { 260 | unset($properties[$property]); 261 | } 262 | } 263 | 264 | // Set up categories 265 | if (isset($tv->category)) 266 | { 267 | $category = $this->create_category($tv->category); 268 | 269 | $properties['category'] = ($category ? $category->get_property('id') : 0); 270 | } 271 | else 272 | { 273 | $properties['category'] = 0; 274 | } 275 | 276 | if (isset($tv->input_properties->formtabs)) 277 | { 278 | foreach ($tv->input_properties as $property => $value) 279 | { 280 | // MIGX Fix, convert array object into json string 281 | if ($property === 'formtabs' || $property === 'columns') 282 | { 283 | $properties['input_properties'][$property] = json_encode($value); 284 | } 285 | else 286 | { 287 | $properties['input_properties'][$property] = $value; 288 | } 289 | } 290 | 291 | $properties['input_properties']['configs'] = ''; 292 | } 293 | 294 | return $properties; 295 | } 296 | 297 | /** 298 | * Sets up the media source for a TV 299 | * 300 | * @param integer $tv_id 301 | * @param string $media_source_name 302 | * 303 | * @return boolean 304 | */ 305 | public function setup_tv_media_source($tv_id, $media_source_name) 306 | { 307 | $media_source = Element::get($this->modx, 'sources.modMediaSource', $media_source_name); 308 | $media_source_id = $media_source->get_property('id'); 309 | 310 | $media_source_element = $this->modx->getObject('sources.modMediaSourceElement', array( 311 | 'object' => $tv_id, 312 | 'object_class' => 'modTemplateVar', 313 | 'context_key' => 'web' 314 | )); 315 | 316 | // Remove the media source element first if it exists (for some reason updating 317 | // existing media source elements doesn't work) 318 | if ($media_source_element) 319 | { 320 | $media_source_element->remove(); 321 | } 322 | 323 | $media_source_element = $this->modx->newObject('sources.modMediaSourceElement'); 324 | 325 | $media_source_element->fromArray(array( 326 | 'object' => $tv_id, 327 | 'object_class' => 'modTemplateVar', 328 | 'context_key' => 'web' 329 | ), '', true, true); 330 | 331 | $media_source_element->set('source', $media_source_id); 332 | 333 | if ( ! $media_source_element->save()) 334 | { 335 | return false; 336 | } 337 | 338 | return true; 339 | } 340 | 341 | /** 342 | * Sets up all template access for a template variable 343 | * 344 | * @param integer $tv_id 345 | * @param array $templates 346 | * 347 | * @return boolean 348 | */ 349 | public function setup_tv_template_access($tv_id, $templates) 350 | { 351 | $template_collection = $this->modx->getCollection('modTemplate'); 352 | 353 | // Remove all tv access for each template 354 | foreach ($template_collection as $template) 355 | { 356 | $template = Element::insert($template); 357 | 358 | if ( ! $this->remove_template_access($tv_id, $template->get_property('id'))) 359 | { 360 | return false; 361 | } 362 | } 363 | 364 | // Give access to all templates if the first name is * 365 | if ($templates[0] === '*') 366 | { 367 | foreach ($template_collection as $template) 368 | { 369 | $template = Element::insert($template); 370 | 371 | if ($template) 372 | { 373 | if ( ! $this->add_template_access($tv_id, $template->get_property('id'))) 374 | { 375 | return false; 376 | } 377 | } 378 | } 379 | } 380 | else 381 | { 382 | foreach($templates as $template_name) 383 | { 384 | $template = Element::get($this->modx, 'modTemplate', $template_name); 385 | 386 | // If the template exists add access to the tv 387 | if ($template) 388 | { 389 | if ( ! $this->add_template_access($tv_id, $template->get_property('id'))) 390 | { 391 | return false; 392 | } 393 | } 394 | } 395 | } 396 | 397 | return true; 398 | } 399 | 400 | /** 401 | * Adds template access to a template variable 402 | * 403 | * @param integer $tv_id 404 | * @param integer $template_id 405 | * 406 | * @return boolean 407 | */ 408 | private function add_template_access($tv_id, $template_id) 409 | { 410 | $tv_template = $this->modx->getObject('modTemplateVarTemplate', array( 411 | 'tmplvarid' => $tv_id, 412 | 'templateid' => $template_id 413 | )); 414 | 415 | // If there is no tv template pairing 416 | if ( ! isset($tv_template)) 417 | { 418 | $tv_template = $this->modx->newObject('modTemplateVarTemplate'); 419 | 420 | $tv_template->set('tmplvarid', $tv_id); 421 | $tv_template->set('templateid', $template_id); 422 | 423 | if ( ! $tv_template->save()) 424 | { 425 | return false; 426 | } 427 | } 428 | 429 | return true; 430 | } 431 | 432 | /** 433 | * Removes template access from a template variable 434 | * 435 | * @param integer $tv_id 436 | * @param integer $template_id 437 | * 438 | * @return boolean 439 | */ 440 | private function remove_template_access($tv_id, $template_id) 441 | { 442 | $tv_template = $this->modx->getObject('modTemplateVarTemplate', array( 443 | 'tmplvarid' => $tv_id, 444 | 'templateid' => $template_id 445 | )); 446 | 447 | if (isset($tv_template)) 448 | { 449 | if ( ! $tv_template->remove()) 450 | { 451 | return false; 452 | } 453 | } 454 | 455 | return true; 456 | } 457 | 458 | /** 459 | * Updates the template variables file 460 | * 461 | * @param array $tvs 462 | * 463 | * @return boolean 464 | */ 465 | public function update_tv_file($tvs) 466 | { 467 | $tv_file_path = MODX_BASE_PATH . $this->modx->getOption('elementhelper.tv_file_path', null, 'site/elements/template_variables.json'); 468 | 469 | if (defined('JSON_PRETTY_PRINT')) 470 | { 471 | $tv_json = json_encode($tvs, JSON_PRETTY_PRINT); 472 | } 473 | else 474 | { 475 | $tv_json = $this->pretty_json(json_encode($tvs)); 476 | } 477 | 478 | if ( ! file_put_contents($tv_file_path, $tv_json)) 479 | { 480 | return false; 481 | } 482 | 483 | return true; 484 | } 485 | 486 | /** 487 | * https://gist.github.com/odan/7a04c02dbce59217a33c 488 | * 489 | * json_encode in PHP versions below 5.4 can't output neatly spaced 490 | * json so we use this when writing back to the template variables file 491 | * 492 | * @param string $json Original JSON string 493 | * @param array $options Encoding options 494 | * 495 | * @return string 496 | */ 497 | public function pretty_json($json, $options = array()) 498 | { 499 | $tokens = preg_split('|([\{\}\]\[,])|', $json, -1, PREG_SPLIT_DELIM_CAPTURE); 500 | $result = ''; 501 | $indent = 0; 502 | 503 | $format = 'txt'; 504 | 505 | //$ind = "\t"; 506 | $ind = " "; 507 | 508 | if (isset($options['format'])) { 509 | $format = $options['format']; 510 | } 511 | 512 | switch ($format) { 513 | case 'html': 514 | $lineBreak = '
'; 515 | $ind = '    '; 516 | break; 517 | default: 518 | case 'txt': 519 | $lineBreak = "\n"; 520 | //$ind = "\t"; 521 | $ind = " "; 522 | break; 523 | } 524 | 525 | // override the defined indent setting with the supplied option 526 | if (isset($options['indent'])) { 527 | $ind = $options['indent']; 528 | } 529 | 530 | $inLiteral = false; 531 | foreach ($tokens as $token) { 532 | if ($token == '') { 533 | continue; 534 | } 535 | 536 | $prefix = str_repeat($ind, $indent); 537 | if (!$inLiteral && ($token == '{' || $token == '[')) { 538 | $indent++; 539 | if (($result != '') && ($result[(strlen($result) - 1)] == $lineBreak)) { 540 | $result .= $prefix; 541 | } 542 | $result .= $token . $lineBreak; 543 | } elseif (!$inLiteral && ($token == '}' || $token == ']')) { 544 | $indent--; 545 | $prefix = str_repeat($ind, $indent); 546 | $result .= $lineBreak . $prefix . $token; 547 | } elseif (!$inLiteral && $token == ',') { 548 | $result .= $token . $lineBreak; 549 | } else { 550 | $result .= ( $inLiteral ? '' : $prefix ) . $token; 551 | 552 | // Count # of unescaped double-quotes in token, subtract # of 553 | // escaped double-quotes and if the result is odd then we are 554 | // inside a string literal 555 | if ((substr_count($token, "\"") - substr_count($token, "\\\"")) % 2 != 0) { 556 | $inLiteral = !$inLiteral; 557 | } 558 | } 559 | } 560 | return $result; 561 | } 562 | } -------------------------------------------------------------------------------- /elementhelper/model/elementsync.class.php: -------------------------------------------------------------------------------- 1 | modx = $modx; 16 | $this->sync_json_file = $sync_file_path; 17 | 18 | // If the sync file doesn't exist create it and set the elements to null 19 | if ( ! $this->get_elements()) 20 | { 21 | $sync_json_file = fopen($sync_file_path, 'wb'); 22 | 23 | fwrite($sync_json_file, ''); 24 | fclose($sync_json_file); 25 | 26 | $this->elements = null; 27 | } 28 | } 29 | 30 | /** 31 | * Gets the elements from the sync file 32 | * 33 | * @return boolean 34 | */ 35 | private function get_elements() 36 | { 37 | if (file_exists($this->sync_json_file)) 38 | { 39 | $sync_json = file_get_contents($this->sync_json_file); 40 | 41 | $this->elements = ($sync_json === '' ? null : json_decode($sync_json, true)); 42 | 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | /** 50 | * Checks if an element is in the sync 51 | * 52 | * @param string $type 53 | * @param string $name 54 | * 55 | * @return boolean 56 | */ 57 | public function has_element($type, $name) 58 | { 59 | return (isset($this->elements[$type][$name]) ? true : false); 60 | } 61 | 62 | /** 63 | * Returns the modification time for an element recorded in the sync 64 | * 65 | * @param string $type 66 | * @param string $name 67 | * 68 | * @return integer 69 | */ 70 | public function get_element_mod_time($type, $name) 71 | { 72 | return $this->elements[$type][$name]; 73 | } 74 | 75 | /** 76 | * Adds an element to the sync 77 | * 78 | * @param string $type 79 | * @param string $name 80 | * @param integer $mod_time 81 | * 82 | * @return boolean 83 | */ 84 | public function add_element($type, $name, $mod_time) 85 | { 86 | $this->elements[$type][$name] = $mod_time; 87 | 88 | $sync_json = json_encode($this->elements); 89 | 90 | if (file_put_contents($this->sync_json_file, $sync_json)) 91 | { 92 | return true; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | /** 99 | * Removes an element from the sync 100 | * 101 | * @param string $type 102 | * @param string $name 103 | * 104 | * @return boolean 105 | */ 106 | public function remove_element($type, $name) 107 | { 108 | unset($this->elements[$type][$name]); 109 | 110 | $sync_json = json_encode($this->elements); 111 | 112 | if (file_put_contents($this->sync_json_file, $sync_json)) 113 | { 114 | return true; 115 | } 116 | 117 | return false; 118 | } 119 | } -------------------------------------------------------------------------------- /elementhelper/model/filehelper.class.php: -------------------------------------------------------------------------------- 1 | basename($file_path, '.' . $path_parts['extension']), 82 | 'type' => $path_parts['extension'], 83 | 'mod_time' => filemtime($file_path) 84 | ); 85 | 86 | return $meta; 87 | } 88 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Element Helper for MODx Revolution 2 | ================================== 3 | 4 | Element Helper is a MODx plugin for automatically creating elements from static files without the need for the manager. 5 | 6 | Installation 7 | ------------ 8 | 9 | Install through the MODx package manager. [A guide for the package manager can be found here](http://rtfm.modx.com/display/revolution20/Package+Management) 10 | 11 | Usage 12 | ----- 13 | 14 | @todo 15 | 16 | See the readme in the `old` branch for rough instructions --------------------------------------------------------------------------------