├── .gitignore ├── README.md ├── _build ├── build.php ├── config.inc.php ├── elements │ ├── _plugins.php │ ├── _policies.php │ ├── _policy_templates.php │ ├── _resources.php │ ├── _settings.php │ ├── _templates.php │ ├── _widgets.php │ ├── chunks.php │ ├── menus.php │ └── snippets.php └── resolvers │ ├── _office.php │ ├── _policy.php │ ├── _setup.php │ ├── symlinks.php │ └── tables.php ├── assets └── components │ └── modextra │ ├── connector.php │ ├── css │ ├── index.html │ └── mgr │ │ └── main.css │ ├── index.html │ └── js │ ├── index.html │ ├── mgr │ ├── misc │ │ ├── combo.js │ │ └── utils.js │ ├── modextra.js │ ├── sections │ │ └── home.js │ └── widgets │ │ ├── home.panel.js │ │ ├── items.grid.js │ │ └── items.windows.js │ └── office │ ├── default.js │ ├── home.panel.js │ ├── items.grid.js │ └── items.windows.js ├── core └── components │ └── modextra │ ├── controllers │ ├── home.class.php │ └── office │ │ └── modextra.class.php │ ├── docs │ ├── changelog.txt │ ├── license.txt │ └── readme.txt │ ├── elements │ ├── chunks │ │ ├── item.tpl │ │ └── office.tpl │ ├── plugins │ │ └── modextra.php │ ├── snippets │ │ └── modextra.php │ └── templates │ │ └── base.tpl │ ├── lexicon │ ├── en │ │ ├── default.inc.php │ │ ├── permissions.inc.php │ │ ├── properties.inc.php │ │ └── setting.inc.php │ └── ru │ │ ├── default.inc.php │ │ ├── permissions.inc.php │ │ ├── properties.inc.php │ │ └── setting.inc.php │ ├── model │ ├── modextra.class.php │ ├── modextra │ │ ├── metadata.mysql.php │ │ ├── modextraitem.class.php │ │ └── mysql │ │ │ ├── modextraitem.class.php │ │ │ └── modextraitem.map.inc.php │ └── schema │ │ └── modextra.mysql.schema.xml │ └── processors │ ├── mgr │ └── item │ │ ├── create.class.php │ │ ├── disable.class.php │ │ ├── enable.class.php │ │ ├── get.class.php │ │ ├── getlist.class.php │ │ ├── remove.class.php │ │ └── update.class.php │ └── office │ └── item │ ├── create.class.php │ ├── disable.class.php │ ├── enable.class.php │ ├── get.class.php │ ├── getlist.class.php │ ├── remove.class.php │ └── update.class.php └── rename_it.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | config.core.php 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Quick start 2 | 3 | * Install MODX Revolution 4 | 5 | * Upload this package into the `Extras` directory in the root of site 6 | 7 | * You need to rename it to `anyOtherName` your package, so enter into SSH console and run 8 | ``` 9 | php ~/www/Extras/modExtra/rename_it.php anyOtherName 10 | ``` 11 | *path on your site may differs* 12 | 13 | * Then install it on dev site 14 | ``` 15 | php ~/www/Extras/anyOtherName/_build/build.php 16 | ``` 17 | 18 | ## Settings 19 | 20 | See `_build/config.inc.php` for editable package options. 21 | 22 | All resolvers and elements are in `_build` path. All files that begins not from `.` or `_` will be added automatically. 23 | 24 | If you will add a new type of element, you will need to add the method with that name into `build.php` script as well. 25 | 26 | ## Build and download 27 | 28 | You can build package at any time by opening `http://dev.site.com/Extras/anyOtherName/_build/build.php` 29 | 30 | If you want to download built package - just add `?download=1` to the address. 31 | 32 | ## Example deploy settings 33 | 34 | [![](https://file.modx.pro/files/3/a/b/3ab2753b9e8b6c09a4ca0da819db37b6s.jpg)](https://file.modx.pro/files/3/a/b/3ab2753b9e8b6c09a4ca0da819db37b6.png) [![](https://file.modx.pro/files/c/1/a/c1afbb8988ab358a0b400cdcdb0391d4s.jpg)](https://file.modx.pro/files/c/1/a/c1afbb8988ab358a0b400cdcdb0391d4.png) 35 | -------------------------------------------------------------------------------- /_build/build.php: -------------------------------------------------------------------------------- 1 | modx = new modX(); 31 | $this->modx->initialize('mgr'); 32 | $this->modx->getService('error', 'error.modError'); 33 | 34 | $root = dirname(dirname(__FILE__)) . '/'; 35 | $assets = $root . 'assets/components/' . $config['name_lower'] . '/'; 36 | $core = $root . 'core/components/' . $config['name_lower'] . '/'; 37 | 38 | $this->config = array_merge([ 39 | 'log_level' => modX::LOG_LEVEL_INFO, 40 | 'log_target' => XPDO_CLI_MODE ? 'ECHO' : 'HTML', 41 | 42 | 'root' => $root, 43 | 'build' => $root . '_build/', 44 | 'elements' => $root . '_build/elements/', 45 | 'resolvers' => $root . '_build/resolvers/', 46 | 47 | 'assets' => $assets, 48 | 'core' => $core, 49 | ], $config); 50 | $this->modx->setLogLevel($this->config['log_level']); 51 | $this->modx->setLogTarget($this->config['log_target']); 52 | 53 | $this->initialize(); 54 | } 55 | 56 | 57 | /** 58 | * Initialize package builder 59 | */ 60 | protected function initialize() 61 | { 62 | $this->builder = $this->modx->getService('transport.modPackageBuilder'); 63 | $this->builder->createPackage($this->config['name_lower'], $this->config['version'], $this->config['release']); 64 | $this->builder->registerNamespace($this->config['name_lower'], false, true, '{core_path}components/' . $this->config['name_lower'] . '/'); 65 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Created Transport Package and Namespace.'); 66 | 67 | $this->category = $this->modx->newObject('modCategory'); 68 | $this->category->set('category', $this->config['name']); 69 | $this->category_attributes = [ 70 | xPDOTransport::UNIQUE_KEY => 'category', 71 | xPDOTransport::PRESERVE_KEYS => false, 72 | xPDOTransport::UPDATE_OBJECT => true, 73 | xPDOTransport::RELATED_OBJECTS => true, 74 | xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [], 75 | ]; 76 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Created main Category.'); 77 | } 78 | 79 | 80 | /** 81 | * Update the model 82 | */ 83 | protected function model() 84 | { 85 | $model_file = $this->config['core'] . 'model/schema/' . $this->config['name_lower'] . '.mysql.schema.xml'; 86 | if (!file_exists($model_file) || empty(file_get_contents($model_file))) { 87 | return; 88 | } 89 | /** @var xPDOCacheManager $cache */ 90 | if ($cache = $this->modx->getCacheManager()) { 91 | $cache->deleteTree( 92 | $this->config['core'] . 'model/' . $this->config['name_lower'] . '/mysql', 93 | ['deleteTop' => true, 'skipDirs' => false, 'extensions' => []] 94 | ); 95 | } 96 | 97 | /** @var xPDOManager $manager */ 98 | $manager = $this->modx->getManager(); 99 | /** @var xPDOGenerator $generator */ 100 | $generator = $manager->getGenerator(); 101 | $generator->parseSchema( 102 | $this->config['core'] . 'model/schema/' . $this->config['name_lower'] . '.mysql.schema.xml', 103 | $this->config['core'] . 'model/' 104 | ); 105 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Model updated'); 106 | } 107 | 108 | 109 | /** 110 | * Install nodejs and update assets 111 | */ 112 | protected function assets() 113 | { 114 | $output = []; 115 | if (!file_exists($this->config['build'] . 'node_modules')) { 116 | putenv('PATH=' . trim(shell_exec('echo $PATH')) . ':' . dirname(MODX_BASE_PATH) . '/'); 117 | if (file_exists($this->config['build'] . 'package.json')) { 118 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Trying to install or update nodejs dependencies'); 119 | $output = [ 120 | shell_exec('cd ' . $this->config['build'] . ' && npm config set scripts-prepend-node-path true && npm install'), 121 | ]; 122 | } 123 | if (file_exists($this->config['build'] . 'gulpfile.js')) { 124 | $output = array_merge($output, [ 125 | shell_exec('cd ' . $this->config['build'] . ' && npm link gulp'), 126 | shell_exec('cd ' . $this->config['build'] . ' && gulp copy'), 127 | ]); 128 | } 129 | if ($output) { 130 | $this->modx->log(xPDO::LOG_LEVEL_INFO, implode("\n", array_map('trim', $output))); 131 | } 132 | } 133 | if (file_exists($this->config['build'] . 'gulpfile.js')) { 134 | $output = shell_exec('cd ' . $this->config['build'] . ' && gulp default 2>&1'); 135 | $this->modx->log(xPDO::LOG_LEVEL_INFO, 'Compile scripts and styles ' . trim($output)); 136 | } 137 | } 138 | 139 | 140 | /** 141 | * Add settings 142 | */ 143 | protected function settings() 144 | { 145 | /** @noinspection PhpIncludeInspection */ 146 | $settings = include($this->config['elements'] . 'settings.php'); 147 | if (!is_array($settings)) { 148 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in System Settings'); 149 | 150 | return; 151 | } 152 | $attributes = [ 153 | xPDOTransport::UNIQUE_KEY => 'key', 154 | xPDOTransport::PRESERVE_KEYS => true, 155 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['settings']), 156 | xPDOTransport::RELATED_OBJECTS => false, 157 | ]; 158 | foreach ($settings as $name => $data) { 159 | /** @var modSystemSetting $setting */ 160 | $setting = $this->modx->newObject('modSystemSetting'); 161 | $setting->fromArray(array_merge([ 162 | 'key' => $this->config['name_lower'] . '_' . $name, 163 | 'namespace' => $this->config['name_lower'], 164 | ], $data), '', true, true); 165 | $vehicle = $this->builder->createVehicle($setting, $attributes); 166 | $this->builder->putVehicle($vehicle); 167 | } 168 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($settings) . ' System Settings'); 169 | } 170 | 171 | 172 | /** 173 | * Add menus 174 | */ 175 | protected function menus() 176 | { 177 | /** @noinspection PhpIncludeInspection */ 178 | $menus = include($this->config['elements'] . 'menus.php'); 179 | if (!is_array($menus)) { 180 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Menus'); 181 | 182 | return; 183 | } 184 | $attributes = [ 185 | xPDOTransport::PRESERVE_KEYS => true, 186 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['menus']), 187 | xPDOTransport::UNIQUE_KEY => 'text', 188 | xPDOTransport::RELATED_OBJECTS => true, 189 | ]; 190 | if (is_array($menus)) { 191 | foreach ($menus as $name => $data) { 192 | /** @var modMenu $menu */ 193 | $menu = $this->modx->newObject('modMenu'); 194 | $menu->fromArray(array_merge([ 195 | 'text' => $name, 196 | 'parent' => 'components', 197 | 'namespace' => $this->config['name_lower'], 198 | 'icon' => '', 199 | 'menuindex' => 0, 200 | 'params' => '', 201 | 'handler' => '', 202 | ], $data), '', true, true); 203 | $vehicle = $this->builder->createVehicle($menu, $attributes); 204 | $this->builder->putVehicle($vehicle); 205 | } 206 | } 207 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($menus) . ' Menus'); 208 | } 209 | 210 | 211 | /** 212 | * Add Dashboard Widgets 213 | */ 214 | protected function widgets() 215 | { 216 | /** @noinspection PhpIncludeInspection */ 217 | $widgets = include($this->config['elements'] . 'widgets.php'); 218 | if (!is_array($widgets)) { 219 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Dashboard Widgets'); 220 | 221 | return; 222 | } 223 | $attributes = [ 224 | xPDOTransport::PRESERVE_KEYS => true, 225 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['widgets']), 226 | xPDOTransport::UNIQUE_KEY => 'name', 227 | ]; 228 | foreach ($widgets as $name => $data) { 229 | /** @var modDashboardWidget $widget */ 230 | $widget = $this->modx->newObject('modDashboardWidget'); 231 | $widget->fromArray(array_merge([ 232 | 'name' => $name, 233 | 'namespace' => 'core', 234 | 'lexicon' => 'core:dashboards', 235 | ], $data), '', true, true); 236 | $vehicle = $this->builder->createVehicle($widget, $attributes); 237 | $this->builder->putVehicle($vehicle); 238 | } 239 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($widgets) . ' Dashboard Widgets'); 240 | } 241 | 242 | 243 | /** 244 | * Add resources 245 | */ 246 | protected function resources() 247 | { 248 | /** @noinspection PhpIncludeInspection */ 249 | $resources = include($this->config['elements'] . 'resources.php'); 250 | if (!is_array($resources)) { 251 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Resources'); 252 | 253 | return; 254 | } 255 | $attributes = [ 256 | xPDOTransport::UNIQUE_KEY => 'id', 257 | xPDOTransport::PRESERVE_KEYS => true, 258 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['resources']), 259 | xPDOTransport::RELATED_OBJECTS => false, 260 | ]; 261 | $objects = []; 262 | foreach ($resources as $context => $items) { 263 | $menuindex = 0; 264 | foreach ($items as $alias => $item) { 265 | if (!isset($item['id'])) { 266 | $item['id'] = $this->_idx++; 267 | } 268 | $item['alias'] = $alias; 269 | $item['context_key'] = $context; 270 | $item['menuindex'] = $menuindex++; 271 | $objects = array_merge( 272 | $objects, 273 | $this->_addResource($item, $alias) 274 | ); 275 | } 276 | } 277 | 278 | /** @var modResource $resource */ 279 | foreach ($objects as $resource) { 280 | $vehicle = $this->builder->createVehicle($resource, $attributes); 281 | $this->builder->putVehicle($vehicle); 282 | } 283 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($objects) . ' Resources'); 284 | } 285 | 286 | 287 | /** 288 | * Add plugins 289 | */ 290 | protected function plugins() 291 | { 292 | /** @noinspection PhpIncludeInspection */ 293 | $plugins = include($this->config['elements'] . 'plugins.php'); 294 | if (!is_array($plugins)) { 295 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Plugins'); 296 | 297 | return; 298 | } 299 | $this->category_attributes[xPDOTransport::RELATED_OBJECT_ATTRIBUTES]['Plugins'] = [ 300 | xPDOTransport::UNIQUE_KEY => 'name', 301 | xPDOTransport::PRESERVE_KEYS => false, 302 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['plugins']), 303 | xPDOTransport::RELATED_OBJECTS => true, 304 | xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [ 305 | 'PluginEvents' => [ 306 | xPDOTransport::PRESERVE_KEYS => true, 307 | xPDOTransport::UPDATE_OBJECT => true, 308 | xPDOTransport::UNIQUE_KEY => ['pluginid', 'event'], 309 | ], 310 | ], 311 | ]; 312 | $objects = []; 313 | foreach ($plugins as $name => $data) { 314 | /** @var modPlugin $plugin */ 315 | $plugin = $this->modx->newObject('modPlugin'); 316 | $plugin->fromArray(array_merge([ 317 | 'name' => $name, 318 | 'category' => 0, 319 | 'description' => @$data['description'], 320 | 'plugincode' => $this::_getContent($this->config['core'] . 'elements/plugins/' . $data['file'] . '.php'), 321 | 'static' => !empty($this->config['static']['plugins']), 322 | 'source' => 1, 323 | 'static_file' => 'core/components/' . $this->config['name_lower'] . '/elements/plugins/' . $data['file'] . '.php', 324 | ], $data), '', true, true); 325 | 326 | $events = []; 327 | if (!empty($data['events'])) { 328 | foreach ($data['events'] as $event_name => $event_data) { 329 | /** @var modPluginEvent $event */ 330 | $event = $this->modx->newObject('modPluginEvent'); 331 | $event->fromArray(array_merge([ 332 | 'event' => $event_name, 333 | 'priority' => 0, 334 | 'propertyset' => 0, 335 | ], $event_data), '', true, true); 336 | $events[] = $event; 337 | } 338 | } 339 | if (!empty($events)) { 340 | $plugin->addMany($events); 341 | } 342 | $objects[] = $plugin; 343 | } 344 | $this->category->addMany($objects); 345 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($objects) . ' Plugins'); 346 | } 347 | 348 | 349 | /** 350 | * Add snippets 351 | */ 352 | protected function snippets() 353 | { 354 | /** @noinspection PhpIncludeInspection */ 355 | $snippets = include($this->config['elements'] . 'snippets.php'); 356 | if (!is_array($snippets)) { 357 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Snippets'); 358 | 359 | return; 360 | } 361 | $this->category_attributes[xPDOTransport::RELATED_OBJECT_ATTRIBUTES]['Snippets'] = [ 362 | xPDOTransport::PRESERVE_KEYS => false, 363 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['snippets']), 364 | xPDOTransport::UNIQUE_KEY => 'name', 365 | ]; 366 | $objects = []; 367 | foreach ($snippets as $name => $data) { 368 | /** @var modSnippet[] $objects */ 369 | $objects[$name] = $this->modx->newObject('modSnippet'); 370 | $objects[$name]->fromArray(array_merge([ 371 | 'id' => 0, 372 | 'name' => $name, 373 | 'description' => @$data['description'], 374 | 'snippet' => $this::_getContent($this->config['core'] . 'elements/snippets/' . $data['file'] . '.php'), 375 | 'static' => !empty($this->config['static']['snippets']), 376 | 'source' => 1, 377 | 'static_file' => 'core/components/' . $this->config['name_lower'] . '/elements/snippets/' . $data['file'] . '.php', 378 | ], $data), '', true, true); 379 | $properties = []; 380 | foreach (@$data['properties'] as $k => $v) { 381 | $properties[] = array_merge([ 382 | 'name' => $k, 383 | 'desc' => $this->config['name_lower'] . '_prop_' . $k, 384 | 'lexicon' => $this->config['name_lower'] . ':properties', 385 | ], $v); 386 | } 387 | $objects[$name]->setProperties($properties); 388 | } 389 | $this->category->addMany($objects); 390 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($objects) . ' Snippets'); 391 | } 392 | 393 | 394 | /** 395 | * Add chunks 396 | */ 397 | protected function chunks() 398 | { 399 | /** @noinspection PhpIncludeInspection */ 400 | $chunks = include($this->config['elements'] . 'chunks.php'); 401 | if (!is_array($chunks)) { 402 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Chunks'); 403 | 404 | return; 405 | } 406 | $this->category_attributes[xPDOTransport::RELATED_OBJECT_ATTRIBUTES]['Chunks'] = [ 407 | xPDOTransport::PRESERVE_KEYS => false, 408 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['chunks']), 409 | xPDOTransport::UNIQUE_KEY => 'name', 410 | ]; 411 | $objects = []; 412 | foreach ($chunks as $name => $data) { 413 | /** @var modChunk[] $objects */ 414 | $objects[$name] = $this->modx->newObject('modChunk'); 415 | $objects[$name]->fromArray(array_merge([ 416 | 'id' => 0, 417 | 'name' => $name, 418 | 'description' => @$data['description'], 419 | 'snippet' => $this::_getContent($this->config['core'] . 'elements/chunks/' . $data['file'] . '.tpl'), 420 | 'static' => !empty($this->config['static']['chunks']), 421 | 'source' => 1, 422 | 'static_file' => 'core/components/' . $this->config['name_lower'] . '/elements/chunks/' . $data['file'] . '.tpl', 423 | ], $data), '', true, true); 424 | $objects[$name]->setProperties(@$data['properties']); 425 | } 426 | $this->category->addMany($objects); 427 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($objects) . ' Chunks'); 428 | } 429 | 430 | 431 | /** 432 | * Add templates 433 | */ 434 | protected function templates() 435 | { 436 | /** @noinspection PhpIncludeInspection */ 437 | $templates = include($this->config['elements'] . 'templates.php'); 438 | if (!is_array($templates)) { 439 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Templates'); 440 | 441 | return; 442 | } 443 | $this->category_attributes[xPDOTransport::RELATED_OBJECT_ATTRIBUTES]['Templates'] = [ 444 | xPDOTransport::UNIQUE_KEY => 'templatename', 445 | xPDOTransport::PRESERVE_KEYS => false, 446 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['templates']), 447 | xPDOTransport::RELATED_OBJECTS => false, 448 | ]; 449 | $objects = []; 450 | foreach ($templates as $name => $data) { 451 | /** @var modTemplate[] $objects */ 452 | $objects[$name] = $this->modx->newObject('modTemplate'); 453 | $objects[$name]->fromArray(array_merge([ 454 | 'templatename' => $name, 455 | 'description' => $data['description'], 456 | 'content' => $this::_getContent($this->config['core'] . 'elements/templates/' . $data['file'] . '.tpl'), 457 | 'static' => !empty($this->config['static']['templates']), 458 | 'source' => 1, 459 | 'static_file' => 'core/components/' . $this->config['name_lower'] . '/elements/templates/' . $data['file'] . '.tpl', 460 | ], $data), '', true, true); 461 | } 462 | $this->category->addMany($objects); 463 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($objects) . ' Templates'); 464 | } 465 | 466 | 467 | /** 468 | * Add access policy 469 | */ 470 | protected function policies() 471 | { 472 | /** @noinspection PhpIncludeInspection */ 473 | $policies = include($this->config['elements'] . 'policies.php'); 474 | if (!is_array($policies)) { 475 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Access Policies'); 476 | return; 477 | } 478 | $attributes = [ 479 | xPDOTransport::PRESERVE_KEYS => false, 480 | xPDOTransport::UNIQUE_KEY => array('name'), 481 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['policies']), 482 | ]; 483 | foreach ($policies as $name => $data) { 484 | if (isset($data['data'])) { 485 | $data['data'] = json_encode($data['data']); 486 | } 487 | /** @var $policy modAccessPolicy */ 488 | $policy = $this->modx->newObject('modAccessPolicy'); 489 | $policy->fromArray(array_merge(array( 490 | 'name' => $name, 491 | 'lexicon' => $this->config['name_lower'] . ':permissions', 492 | ), $data) 493 | , '', true, true); 494 | $vehicle = $this->builder->createVehicle($policy, $attributes); 495 | $this->builder->putVehicle($vehicle); 496 | } 497 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($policies) . ' Access Policies'); 498 | } 499 | 500 | 501 | /** 502 | * Add policy templates 503 | */ 504 | protected function policy_templates() 505 | { 506 | /** @noinspection PhpIncludeInspection */ 507 | $policy_templates = include($this->config['elements'] . 'policy_templates.php'); 508 | if (!is_array($policy_templates)) { 509 | $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Policy Templates'); 510 | return; 511 | } 512 | $attributes = [ 513 | xPDOTransport::PRESERVE_KEYS => false, 514 | xPDOTransport::UNIQUE_KEY => array('name'), 515 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['policy_templates']), 516 | xPDOTransport::RELATED_OBJECTS => true, 517 | xPDOTransport::RELATED_OBJECT_ATTRIBUTES => array( 518 | 'Permissions' => array( 519 | xPDOTransport::PRESERVE_KEYS => false, 520 | xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['permission']), 521 | xPDOTransport::UNIQUE_KEY => array('template', 'name'), 522 | ), 523 | ), 524 | ]; 525 | foreach ($policy_templates as $name => $data) { 526 | $permissions = array(); 527 | if (isset($data['permissions']) && is_array($data['permissions'])) { 528 | foreach ($data['permissions'] as $name2 => $data2) { 529 | /** @var $permission modAccessPermission */ 530 | $permission = $this->modx->newObject('modAccessPermission'); 531 | $permission->fromArray(array_merge(array( 532 | 'name' => $name2, 533 | 'description' => $name2, 534 | 'value' => true, 535 | ), $data2) 536 | , '', true, true); 537 | $permissions[] = $permission; 538 | } 539 | } 540 | /** @var $permission modAccessPolicyTemplate */ 541 | $permission = $this->modx->newObject('modAccessPolicyTemplate'); 542 | $permission->fromArray(array_merge(array( 543 | 'name' => $name, 544 | 'lexicon' => $this->config['name_lower'] . ':permissions', 545 | ), $data) 546 | , '', true, true); 547 | if (!empty($permissions)) { 548 | $permission->addMany($permissions); 549 | } 550 | $vehicle = $this->builder->createVehicle($permission, $attributes); 551 | $this->builder->putVehicle($vehicle); 552 | } 553 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($policy_templates) . ' Access Policy Templates'); 554 | } 555 | 556 | 557 | /** 558 | * @param $filename 559 | * 560 | * @return string 561 | */ 562 | static public function _getContent($filename) 563 | { 564 | if (file_exists($filename)) { 565 | $file = trim(file_get_contents($filename)); 566 | 567 | return preg_match('#\<\?php(.*)#is', $file, $data) 568 | ? rtrim(rtrim(trim(@$data[1]), '?>')) 569 | : $file; 570 | } 571 | 572 | return ''; 573 | } 574 | 575 | 576 | /** 577 | * @param array $data 578 | * @param string $uri 579 | * @param int $parent 580 | * 581 | * @return array 582 | */ 583 | protected function _addResource(array $data, $uri, $parent = 0) 584 | { 585 | $file = $data['context_key'] . '/' . $uri; 586 | /** @var modResource $resource */ 587 | $resource = $this->modx->newObject('modResource'); 588 | $resource->fromArray(array_merge([ 589 | 'parent' => $parent, 590 | 'published' => true, 591 | 'deleted' => false, 592 | 'hidemenu' => false, 593 | 'createdon' => time(), 594 | 'template' => 1, 595 | 'isfolder' => !empty($data['isfolder']) || !empty($data['resources']), 596 | 'uri' => $uri, 597 | 'uri_override' => false, 598 | 'richtext' => false, 599 | 'searchable' => true, 600 | 'content' => $this::_getContent($this->config['core'] . 'elements/resources/' . $file . '.tpl'), 601 | ], $data), '', true, true); 602 | 603 | if (!empty($data['groups'])) { 604 | foreach ($data['groups'] as $group) { 605 | $resource->joinGroup($group); 606 | } 607 | } 608 | $resources[] = $resource; 609 | 610 | if (!empty($data['resources'])) { 611 | $menuindex = 0; 612 | foreach ($data['resources'] as $alias => $item) { 613 | if (!isset($item['id'])) { 614 | $item['id'] = $this->_idx++; 615 | } 616 | $item['alias'] = $alias; 617 | $item['context_key'] = $data['context_key']; 618 | $item['menuindex'] = $menuindex++; 619 | $resources = array_merge( 620 | $resources, 621 | $this->_addResource($item, $uri . '/' . $alias, $data['id']) 622 | ); 623 | } 624 | } 625 | 626 | return $resources; 627 | } 628 | 629 | 630 | /** 631 | * Install package 632 | */ 633 | protected function install() 634 | { 635 | $signature = $this->builder->getSignature(); 636 | $sig = explode('-', $signature); 637 | $versionSignature = explode('.', $sig[1]); 638 | 639 | /** @var modTransportPackage $package */ 640 | if (!$package = $this->modx->getObject('transport.modTransportPackage', ['signature' => $signature])) { 641 | $package = $this->modx->newObject('transport.modTransportPackage'); 642 | $package->set('signature', $signature); 643 | $package->fromArray([ 644 | 'created' => date('Y-m-d h:i:s'), 645 | 'updated' => null, 646 | 'state' => 1, 647 | 'workspace' => 1, 648 | 'provider' => 0, 649 | 'source' => $signature . '.transport.zip', 650 | 'package_name' => $this->config['name'], 651 | 'version_major' => $versionSignature[0], 652 | 'version_minor' => !empty($versionSignature[1]) ? $versionSignature[1] : 0, 653 | 'version_patch' => !empty($versionSignature[2]) ? $versionSignature[2] : 0, 654 | ]); 655 | if (!empty($sig[2])) { 656 | $r = preg_split('#([0-9]+)#', $sig[2], -1, PREG_SPLIT_DELIM_CAPTURE); 657 | if (is_array($r) && !empty($r)) { 658 | $package->set('release', $r[0]); 659 | $package->set('release_index', (isset($r[1]) ? $r[1] : '0')); 660 | } else { 661 | $package->set('release', $sig[2]); 662 | } 663 | } 664 | $package->save(); 665 | } 666 | if ($package->install()) { 667 | $this->modx->runProcessor('system/clearcache'); 668 | } 669 | } 670 | 671 | 672 | /** 673 | * @return modPackageBuilder 674 | */ 675 | public function process() 676 | { 677 | $this->model(); 678 | $this->assets(); 679 | 680 | // Add elements 681 | $elements = scandir($this->config['elements']); 682 | foreach ($elements as $element) { 683 | if (in_array($element[0], ['_', '.'])) { 684 | continue; 685 | } 686 | $name = preg_replace('#\.php$#', '', $element); 687 | if (method_exists($this, $name)) { 688 | $this->{$name}(); 689 | } 690 | } 691 | 692 | // Create main vehicle 693 | /** @var modTransportVehicle $vehicle */ 694 | $vehicle = $this->builder->createVehicle($this->category, $this->category_attributes); 695 | 696 | // Files resolvers 697 | $vehicle->resolve('file', [ 698 | 'source' => $this->config['core'], 699 | 'target' => "return MODX_CORE_PATH . 'components/';", 700 | ]); 701 | $vehicle->resolve('file', [ 702 | 'source' => $this->config['assets'], 703 | 'target' => "return MODX_ASSETS_PATH . 'components/';", 704 | ]); 705 | 706 | // Add resolvers into vehicle 707 | $resolvers = scandir($this->config['resolvers']); 708 | // Remove Office files 709 | if (!in_array('office', $resolvers)) { 710 | if ($cache = $this->modx->getCacheManager()) { 711 | $dirs = [ 712 | $this->config['assets'] . 'js/office', 713 | $this->config['core'] . 'controllers/office', 714 | $this->config['core'] . 'processors/office', 715 | ]; 716 | foreach ($dirs as $dir) { 717 | $cache->deleteTree($dir, ['deleteTop' => true, 'skipDirs' => false, 'extensions' => []]); 718 | } 719 | } 720 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Deleted Office files'); 721 | } 722 | foreach ($resolvers as $resolver) { 723 | if (in_array($resolver[0], ['_', '.'])) { 724 | continue; 725 | } 726 | if ($vehicle->resolve('php', ['source' => $this->config['resolvers'] . $resolver])) { 727 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Added resolver ' . preg_replace('#\.php$#', '', $resolver)); 728 | } 729 | } 730 | $this->builder->putVehicle($vehicle); 731 | 732 | $this->builder->setPackageAttributes([ 733 | 'changelog' => file_get_contents($this->config['core'] . 'docs/changelog.txt'), 734 | 'license' => file_get_contents($this->config['core'] . 'docs/license.txt'), 735 | 'readme' => file_get_contents($this->config['core'] . 'docs/readme.txt'), 736 | ]); 737 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Added package attributes and setup options.'); 738 | 739 | $this->modx->log(modX::LOG_LEVEL_INFO, 'Packing up transport package zip...'); 740 | $this->builder->pack(); 741 | 742 | if (!empty($this->config['install'])) { 743 | $this->install(); 744 | } 745 | 746 | return $this->builder; 747 | } 748 | 749 | } 750 | 751 | /** @var array $config */ 752 | if (!file_exists(dirname(__FILE__) . '/config.inc.php')) { 753 | exit('Could not load MODX config. Please specify correct MODX_CORE_PATH constant in config file!'); 754 | } 755 | $config = require(dirname(__FILE__) . '/config.inc.php'); 756 | $install = new modExtraPackage(MODX_CORE_PATH, $config); 757 | $builder = $install->process(); 758 | 759 | if (!empty($config['download'])) { 760 | $name = $builder->getSignature() . '.transport.zip'; 761 | if ($content = file_get_contents(MODX_CORE_PATH . '/packages/' . $name)) { 762 | header('Content-Description: File Transfer'); 763 | header('Content-Type: application/octet-stream'); 764 | header('Content-Disposition: attachment; filename=' . $name); 765 | header('Content-Transfer-Encoding: binary'); 766 | header('Expires: 0'); 767 | header('Cache-Control: must-revalidate'); 768 | header('Pragma: public'); 769 | header('Content-Length: ' . strlen($content)); 770 | exit($content); 771 | } 772 | } 773 | -------------------------------------------------------------------------------- /_build/config.inc.php: -------------------------------------------------------------------------------- 1 | 1)) { 6 | $path = dirname($path); 7 | } 8 | define('MODX_CORE_PATH', $path . '/core/'); 9 | } 10 | 11 | return [ 12 | 'name' => 'modExtra', 13 | 'name_lower' => 'modextra', 14 | 'version' => '2.0.0', 15 | 'release' => 'pl', 16 | // Install package to site right after build 17 | 'install' => true, 18 | // Which elements should be updated on package upgrade 19 | 'update' => [ 20 | 'chunks' => false, 21 | 'menus' => true, 22 | 'permission' => true, 23 | 'plugins' => true, 24 | 'policies' => true, 25 | 'policy_templates' => true, 26 | 'resources' => false, 27 | 'settings' => false, 28 | 'snippets' => true, 29 | 'templates' => false, 30 | 'widgets' => false, 31 | ], 32 | // Which elements should be static by default 33 | 'static' => [ 34 | 'plugins' => false, 35 | 'snippets' => false, 36 | 'chunks' => false, 37 | ], 38 | // Log settings 39 | 'log_level' => !empty($_REQUEST['download']) ? 0 : 3, 40 | 'log_target' => php_sapi_name() == 'cli' ? 'ECHO' : 'HTML', 41 | // Download transport.zip after build 42 | 'download' => !empty($_REQUEST['download']), 43 | ]; -------------------------------------------------------------------------------- /_build/elements/_plugins.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'file' => 'modextra', 6 | 'description' => '', 7 | 'events' => [ 8 | 'OnManagerPageInit' => [], 9 | ], 10 | ], 11 | ]; -------------------------------------------------------------------------------- /_build/elements/_policies.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'description' => 'modExtra policy description.', 6 | 'data' => [ 7 | 'modextra_save' => true, 8 | ] 9 | ], 10 | ]; -------------------------------------------------------------------------------- /_build/elements/_policy_templates.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'description' => 'modExtra policy template description.', 6 | 'template_group' => 1, 7 | 'permissions' => [ 8 | 'modextra_save' => [], 9 | ] 10 | ], 11 | ]; -------------------------------------------------------------------------------- /_build/elements/_resources.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'index' => [ 6 | 'pagetitle' => 'Home', 7 | 'template' => 1, 8 | 'hidemenu' => false, 9 | ], 10 | 'service' => [ 11 | 'pagetitle' => 'Service', 12 | 'template' => 0, 13 | 'hidemenu' => true, 14 | 'published' => false, 15 | 'resources' => [ 16 | '404' => [ 17 | 'pagetitle' => '404', 18 | 'template' => 1, 19 | 'hidemenu' => true, 20 | 'uri' => '404', 21 | 'uri_override' => true, 22 | ], 23 | 'sitemap.xml' => [ 24 | 'pagetitle' => 'Sitemap', 25 | 'template' => 0, 26 | 'hidemenu' => true, 27 | 'uri' => 'sitemap.xml', 28 | 'uri_override' => true, 29 | ], 30 | ], 31 | ], 32 | ], 33 | ]; -------------------------------------------------------------------------------- /_build/elements/_settings.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'xtype' => 'combo-boolean', 6 | 'value' => true, 7 | 'area' => 'modextra_main', 8 | ], 9 | ]; -------------------------------------------------------------------------------- /_build/elements/_templates.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'file' => 'base', 6 | 'description' => 'Base template', 7 | ], 8 | ]; -------------------------------------------------------------------------------- /_build/elements/_widgets.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'description' => '', 6 | 'type' => 'file', 7 | 'content' => '', 8 | 'namespace' => 'modextra', 9 | 'lexicon' => 'modextra:dashboards', 10 | 'size' => 'half', 11 | ], 12 | ]; -------------------------------------------------------------------------------- /_build/elements/chunks.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'file' => 'item', 6 | 'description' => '', 7 | ], 8 | 'tpl.modExtra.office' => [ 9 | 'file' => 'office', 10 | 'description' => '', 11 | ], 12 | ]; -------------------------------------------------------------------------------- /_build/elements/menus.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'description' => 'modextra_menu_desc', 6 | 'action' => 'home', 7 | //'icon' => '', 8 | ], 9 | ]; -------------------------------------------------------------------------------- /_build/elements/snippets.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'file' => 'modextra', 6 | 'description' => 'modExtra snippet to list items', 7 | 'properties' => [ 8 | 'tpl' => [ 9 | 'type' => 'textfield', 10 | 'value' => 'tpl.modExtra.item', 11 | ], 12 | 'sortby' => [ 13 | 'type' => 'textfield', 14 | 'value' => 'name', 15 | ], 16 | 'sortdir' => [ 17 | 'type' => 'list', 18 | 'options' => [ 19 | ['text' => 'ASC', 'value' => 'ASC'], 20 | ['text' => 'DESC', 'value' => 'DESC'], 21 | ], 22 | 'value' => 'ASC', 23 | ], 24 | 'limit' => [ 25 | 'type' => 'numberfield', 26 | 'value' => 10, 27 | ], 28 | 'outputSeparator' => [ 29 | 'type' => 'textfield', 30 | 'value' => "\n", 31 | ], 32 | 'toPlaceholder' => [ 33 | 'type' => 'combo-boolean', 34 | 'value' => false, 35 | ], 36 | ], 37 | ], 38 | ]; -------------------------------------------------------------------------------- /_build/resolvers/_office.php: -------------------------------------------------------------------------------- 1 | xpdo) { 6 | $modx =& $transport->xpdo; 7 | /** @var Office $office */ 8 | if ($Office = $modx->getService('Office', 'Office', MODX_CORE_PATH . 'components/office/model/office/')) { 9 | if (!($Office instanceof Office)) { 10 | $modx->log(xPDO::LOG_LEVEL_ERROR, '[modExtra] Could not register paths for Office component!'); 11 | 12 | return true; 13 | } elseif (!method_exists($Office, 'addExtension')) { 14 | $modx->log(xPDO::LOG_LEVEL_ERROR, 15 | '[modExtra] You need to update Office for support of 3rd party packages!'); 16 | 17 | return true; 18 | } 19 | 20 | /** @var array $options */ 21 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 22 | case xPDOTransport::ACTION_INSTALL: 23 | case xPDOTransport::ACTION_UPGRADE: 24 | $Office->addExtension('modExtra', '[[++core_path]]components/modextra/controllers/office/'); 25 | $modx->log(xPDO::LOG_LEVEL_INFO, '[modExtra] Successfully registered modExtra as Office extension!'); 26 | break; 27 | 28 | case xPDOTransport::ACTION_UNINSTALL: 29 | $Office->removeExtension('modExtra'); 30 | $modx->log(xPDO::LOG_LEVEL_INFO, '[modExtra] Successfully unregistered modExtra as Office extension.'); 31 | break; 32 | } 33 | } 34 | } 35 | 36 | return true; -------------------------------------------------------------------------------- /_build/resolvers/_policy.php: -------------------------------------------------------------------------------- 1 | xpdo) { 6 | $modx =& $transport->xpdo; 7 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 8 | case xPDOTransport::ACTION_INSTALL: 9 | case xPDOTransport::ACTION_UPGRADE: 10 | // Assign policy to template 11 | if ($policy = $modx->getObject('modAccessPolicy', array('name' => 'modExtraUserPolicy'))) { 12 | if ($template = $modx->getObject('modAccessPolicyTemplate', 13 | array('name' => 'modExtraUserPolicyTemplate')) 14 | ) { 15 | $policy->set('template', $template->get('id')); 16 | $policy->save(); 17 | } else { 18 | $modx->log(xPDO::LOG_LEVEL_ERROR, 19 | '[modExtra] Could not find modExtraUserPolicyTemplate Access Policy Template!'); 20 | } 21 | } else { 22 | $modx->log(xPDO::LOG_LEVEL_ERROR, '[modExtra] Could not find modExtraUserPolicyTemplate Access Policy!'); 23 | } 24 | break; 25 | } 26 | } 27 | return true; -------------------------------------------------------------------------------- /_build/resolvers/_setup.php: -------------------------------------------------------------------------------- 1 | xpdo || !($transport instanceof xPDOTransport)) { 6 | return false; 7 | } 8 | 9 | $modx =& $transport->xpdo; 10 | $packages = [ 11 | 'Ace' => [ 12 | 'version' => '1.6.5-pl', 13 | 'service_url' => 'modstore.pro', 14 | ], 15 | 'pdoTools' => [ 16 | 'version' => '2.10.0-pl', 17 | 'service_url' => 'modstore.pro', 18 | ], 19 | ]; 20 | 21 | $downloadPackage = function ($src, $dst) { 22 | if (ini_get('allow_url_fopen')) { 23 | $file = @file_get_contents($src); 24 | } else { 25 | if (function_exists('curl_init')) { 26 | $ch = curl_init(); 27 | curl_setopt($ch, CURLOPT_URL, $src); 28 | curl_setopt($ch, CURLOPT_HEADER, 0); 29 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 30 | curl_setopt($ch, CURLOPT_TIMEOUT, 180); 31 | $safeMode = @ini_get('safe_mode'); 32 | $openBasedir = @ini_get('open_basedir'); 33 | if (empty($safeMode) && empty($openBasedir)) { 34 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 35 | } 36 | 37 | $file = curl_exec($ch); 38 | curl_close($ch); 39 | } else { 40 | return false; 41 | } 42 | } 43 | file_put_contents($dst, $file); 44 | 45 | return file_exists($dst); 46 | }; 47 | 48 | $installPackage = function ($packageName, $options = []) use ($modx, $downloadPackage) { 49 | /** @var modTransportProvider $provider */ 50 | if (!empty($options['service_url'])) { 51 | $provider = $modx->getObject('transport.modTransportProvider', [ 52 | 'service_url:LIKE' => '%' . $options['service_url'] . '%', 53 | ]); 54 | } 55 | if (empty($provider)) { 56 | $provider = $modx->getObject('transport.modTransportProvider', 1); 57 | } 58 | $modx->getVersionData(); 59 | $productVersion = $modx->version['code_name'] . '-' . $modx->version['full_version']; 60 | 61 | $response = $provider->request('package', 'GET', [ 62 | 'supports' => $productVersion, 63 | 'query' => $packageName, 64 | ]); 65 | 66 | if (!empty($response)) { 67 | $foundPackages = simplexml_load_string($response->response); 68 | foreach ($foundPackages as $foundPackage) { 69 | /** @var modTransportPackage $foundPackage */ 70 | /** @noinspection PhpUndefinedFieldInspection */ 71 | if ($foundPackage->name == $packageName) { 72 | $sig = explode('-', $foundPackage->signature); 73 | $versionSignature = explode('.', $sig[1]); 74 | /** @noinspection PhpUndefinedFieldInspection */ 75 | $url = $foundPackage->location; 76 | 77 | if (!$downloadPackage($url, $modx->getOption('core_path') . 'packages/' . $foundPackage->signature . '.transport.zip')) { 78 | return [ 79 | 'success' => 0, 80 | 'message' => "Could not download package {$packageName}.", 81 | ]; 82 | } 83 | 84 | // Add in the package as an object so it can be upgraded 85 | /** @var modTransportPackage $package */ 86 | $package = $modx->newObject('transport.modTransportPackage'); 87 | $package->set('signature', $foundPackage->signature); 88 | /** @noinspection PhpUndefinedFieldInspection */ 89 | $package->fromArray([ 90 | 'created' => date('Y-m-d h:i:s'), 91 | 'updated' => null, 92 | 'state' => 1, 93 | 'workspace' => 1, 94 | 'provider' => $provider->get('id'), 95 | 'source' => $foundPackage->signature . '.transport.zip', 96 | 'package_name' => $packageName, 97 | 'version_major' => $versionSignature[0], 98 | 'version_minor' => !empty($versionSignature[1]) ? $versionSignature[1] : 0, 99 | 'version_patch' => !empty($versionSignature[2]) ? $versionSignature[2] : 0, 100 | ]); 101 | 102 | if (!empty($sig[2])) { 103 | $r = preg_split('/([0-9]+)/', $sig[2], -1, PREG_SPLIT_DELIM_CAPTURE); 104 | if (is_array($r) && !empty($r)) { 105 | $package->set('release', $r[0]); 106 | $package->set('release_index', (isset($r[1]) ? $r[1] : '0')); 107 | } else { 108 | $package->set('release', $sig[2]); 109 | } 110 | } 111 | 112 | if ($package->save() && $package->install()) { 113 | return [ 114 | 'success' => 1, 115 | 'message' => "{$packageName} was successfully installed", 116 | ]; 117 | } else { 118 | return [ 119 | 'success' => 0, 120 | 'message' => "Could not save package {$packageName}", 121 | ]; 122 | } 123 | break; 124 | } 125 | } 126 | } else { 127 | return [ 128 | 'success' => 0, 129 | 'message' => "Could not find {$packageName} in MODX repository", 130 | ]; 131 | } 132 | 133 | return true; 134 | }; 135 | 136 | $success = false; 137 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 138 | case xPDOTransport::ACTION_INSTALL: 139 | case xPDOTransport::ACTION_UPGRADE: 140 | foreach ($packages as $name => $data) { 141 | if (!is_array($data)) { 142 | $data = ['version' => $data]; 143 | } 144 | $installed = $modx->getIterator('transport.modTransportPackage', ['package_name' => $name]); 145 | /** @var modTransportPackage $package */ 146 | foreach ($installed as $package) { 147 | if ($package->compareVersion($data['version'], '<=')) { 148 | continue(2); 149 | } 150 | } 151 | $modx->log(modX::LOG_LEVEL_INFO, "Trying to install {$name}. Please wait..."); 152 | $response = $installPackage($name, $data); 153 | $level = $response['success'] 154 | ? modX::LOG_LEVEL_INFO 155 | : modX::LOG_LEVEL_ERROR; 156 | $modx->log($level, $response['message']); 157 | } 158 | $success = true; 159 | break; 160 | 161 | case xPDOTransport::ACTION_UNINSTALL: 162 | $success = true; 163 | break; 164 | } 165 | 166 | return $success; -------------------------------------------------------------------------------- /_build/resolvers/symlinks.php: -------------------------------------------------------------------------------- 1 | xpdo) { 6 | $modx =& $transport->xpdo; 7 | 8 | $dev = MODX_BASE_PATH . 'Extras/modExtra/'; 9 | /** @var xPDOCacheManager $cache */ 10 | $cache = $modx->getCacheManager(); 11 | if (file_exists($dev) && $cache) { 12 | if (!is_link($dev . 'assets/components/modextra')) { 13 | $cache->deleteTree( 14 | $dev . 'assets/components/modextra/', 15 | ['deleteTop' => true, 'skipDirs' => false, 'extensions' => []] 16 | ); 17 | symlink(MODX_ASSETS_PATH . 'components/modextra/', $dev . 'assets/components/modextra'); 18 | } 19 | if (!is_link($dev . 'core/components/modextra')) { 20 | $cache->deleteTree( 21 | $dev . 'core/components/modextra/', 22 | ['deleteTop' => true, 'skipDirs' => false, 'extensions' => []] 23 | ); 24 | symlink(MODX_CORE_PATH . 'components/modextra/', $dev . 'core/components/modextra'); 25 | } 26 | } 27 | } 28 | 29 | return true; -------------------------------------------------------------------------------- /_build/resolvers/tables.php: -------------------------------------------------------------------------------- 1 | xpdo) { 6 | $modx =& $transport->xpdo; 7 | 8 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 9 | case xPDOTransport::ACTION_INSTALL: 10 | case xPDOTransport::ACTION_UPGRADE: 11 | $modx->addPackage('modextra', MODX_CORE_PATH . 'components/modextra/model/'); 12 | $manager = $modx->getManager(); 13 | $objects = []; 14 | $schemaFile = MODX_CORE_PATH . 'components/modextra/model/schema/modextra.mysql.schema.xml'; 15 | if (is_file($schemaFile)) { 16 | $schema = new SimpleXMLElement($schemaFile, 0, true); 17 | if (isset($schema->object)) { 18 | foreach ($schema->object as $obj) { 19 | $objects[] = (string)$obj['class']; 20 | } 21 | } 22 | unset($schema); 23 | } 24 | foreach ($objects as $class) { 25 | $table = $modx->getTableName($class); 26 | $sql = "SHOW TABLES LIKE '" . trim($table, '`') . "'"; 27 | $stmt = $modx->prepare($sql); 28 | $newTable = true; 29 | if ($stmt->execute() && $stmt->fetchAll()) { 30 | $newTable = false; 31 | } 32 | // If the table is just created 33 | if ($newTable) { 34 | $manager->createObjectContainer($class); 35 | } else { 36 | // If the table exists 37 | // 1. Operate with tables 38 | $tableFields = []; 39 | $c = $modx->prepare("SHOW COLUMNS IN {$modx->getTableName($class)}"); 40 | $c->execute(); 41 | while ($cl = $c->fetch(PDO::FETCH_ASSOC)) { 42 | $tableFields[$cl['Field']] = $cl['Field']; 43 | } 44 | foreach ($modx->getFields($class) as $field => $v) { 45 | if (in_array($field, $tableFields)) { 46 | unset($tableFields[$field]); 47 | $manager->alterField($class, $field); 48 | } else { 49 | $manager->addField($class, $field); 50 | } 51 | } 52 | foreach ($tableFields as $field) { 53 | $manager->removeField($class, $field); 54 | } 55 | // 2. Operate with indexes 56 | $indexes = []; 57 | $c = $modx->prepare("SHOW INDEX FROM {$modx->getTableName($class)}"); 58 | $c->execute(); 59 | while ($row = $c->fetch(PDO::FETCH_ASSOC)) { 60 | $name = $row['Key_name']; 61 | if (!isset($indexes[$name])) { 62 | $indexes[$name] = [$row['Column_name']]; 63 | } else { 64 | $indexes[$name][] = $row['Column_name']; 65 | } 66 | } 67 | foreach ($indexes as $name => $values) { 68 | sort($values); 69 | $indexes[$name] = implode(':', $values); 70 | } 71 | $map = $modx->getIndexMeta($class); 72 | // Remove old indexes 73 | foreach ($indexes as $key => $index) { 74 | if (!isset($map[$key])) { 75 | if ($manager->removeIndex($class, $key)) { 76 | $modx->log(modX::LOG_LEVEL_INFO, "Removed index \"{$key}\" of the table \"{$class}\""); 77 | } 78 | } 79 | } 80 | // Add or alter existing 81 | foreach ($map as $key => $index) { 82 | ksort($index['columns']); 83 | $index = implode(':', array_keys($index['columns'])); 84 | if (!isset($indexes[$key])) { 85 | if ($manager->addIndex($class, $key)) { 86 | $modx->log(modX::LOG_LEVEL_INFO, "Added index \"{$key}\" in the table \"{$class}\""); 87 | } 88 | } else { 89 | if ($index != $indexes[$key]) { 90 | if ($manager->removeIndex($class, $key) && $manager->addIndex($class, $key)) { 91 | $modx->log(modX::LOG_LEVEL_INFO, 92 | "Updated index \"{$key}\" of the table \"{$class}\"" 93 | ); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | break; 101 | 102 | case xPDOTransport::ACTION_UNINSTALL: 103 | break; 104 | } 105 | } 106 | 107 | return true; -------------------------------------------------------------------------------- /assets/components/modextra/connector.php: -------------------------------------------------------------------------------- 1 | getService('modExtra', 'modExtra', MODX_CORE_PATH . 'components/modextra/model/'); 14 | $modx->lexicon->load('modextra:default'); 15 | 16 | // handle request 17 | $corePath = $modx->getOption('modextra_core_path', null, $modx->getOption('core_path') . 'components/modextra/'); 18 | $path = $modx->getOption('processorsPath', $modExtra->config, $corePath . 'processors/'); 19 | $modx->getRequest(); 20 | 21 | /** @var modConnectorRequest $request */ 22 | $request = $modx->request; 23 | $request->handleRequest([ 24 | 'processors_path' => $path, 25 | 'location' => '', 26 | ]); -------------------------------------------------------------------------------- /assets/components/modextra/css/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modx-pro/modExtra/8295c2a6e987a0427c40ade90acb594be28b5696/assets/components/modextra/css/index.html -------------------------------------------------------------------------------- /assets/components/modextra/css/mgr/main.css: -------------------------------------------------------------------------------- 1 | /* Grid */ 2 | .modextra-modextra-row-disabled { font-style: italic; opacity: .6; color: #555; } 3 | .x-grid3-col-actions { padding: 3px 0 3px 5px; } 4 | 5 | /* Actions */ 6 | .action-red { color: darkred !important; } 7 | .action-green { color: darkgreen !important; } 8 | .action-blue { color: cadetblue !important; } 9 | .action-yellow { color: goldenrod !important; } 10 | .action-gray { color: dimgray !important; } 11 | ul.modextra-row-actions { margin: 0; padding: 0; list-style: none; } 12 | ul.modextra-row-actions li { float: left; } 13 | ul.modextra-row-actions .modextra-btn { padding: 5px; margin-right: 2px; min-width: 26px; font-size: inherit; } 14 | 15 | /* Action menu */ 16 | a.x-menu-item .x-menu-item-text, a.x-menu-item .x-menu-item-text .icon { cursor: pointer; } 17 | a.x-menu-item .x-menu-item-text .icon { line-height: 16px; top: auto; } 18 | .x-menu-list .icon { min-width: 1em; text-align: center; } 19 | .x-menu-list-item:hover .icon { color: inherit !important; } 20 | ul.modextra-row-actions .actions-menu { width: 40px; } 21 | ul.modextra-row-actions .actions-menu:after { content: " \f107"; } 22 | 23 | /* Search field */ 24 | .x-field-search-clear, 25 | .x-field-search-go { border-left: 1px solid #e4e4e4 !important; } 26 | .x-field-search-clear:hover, 27 | .x-field-search-go:hover { border-left-color: transparent !important; } 28 | .x-field-search-clear:before { content: '\f00d' !important; } 29 | .x-field-search-go { right: 31px !important; border-radius: 0 !important; } 30 | .x-field-search-go:before { content: '\f002' !important; } 31 | 32 | /* Buttons */ 33 | .modextra-btn { display: inline-block; margin-bottom: 0; font-weight: normal; text-align: center; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; white-space: nowrap; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; border-radius: 4px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none } 34 | .modextra-btn:focus, .modextra-btn:active:focus, .modextra-btn.active:focus, .modextra-btn.focus, .modextra-btn:active.focus, .modextra-btn.active.focus { outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px } 35 | .modextra-btn:hover, .modextra-btn:focus, .modextra-btn.focus { color: #333; text-decoration: none } 36 | .modextra-btn:active, .modextra-btn.active { outline: 0; background-image: none; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125) } 37 | .modextra-btn.disabled, .modextra-btn[disabled], fieldset[disabled] .modextra-btn { cursor: not-allowed; opacity: .65; filter: alpha(opacity=65); -webkit-box-shadow: none; box-shadow: none } 38 | a.modextra-btn.disabled, fieldset[disabled] a.modextra-btn { pointer-events: none } 39 | .modextra-btn-default { color: #333; background-color: #fff; border-color: #ccc } 40 | .modextra-btn-default:focus, .modextra-btn-default.focus { color: #333; background-color: #e6e6e6; border-color: #8c8c8c } 41 | .modextra-btn-default:hover { color: #333; background-color: #e6e6e6; border-color: #adadad } 42 | .modextra-btn-default:active, .modextra-btn-default.active, .open > .dropdown-toggle.modextra-btn-default { color: #333; background-color: #e6e6e6; border-color: #adadad } 43 | .modextra-btn-default:active:hover, .modextra-btn-default.active:hover, .open > .dropdown-toggle.modextra-btn-default:hover, .modextra-btn-default:active:focus, .modextra-btn-default.active:focus, .open > .dropdown-toggle.modextra-btn-default:focus, .modextra-btn-default:active.focus, .modextra-btn-default.active.focus, .open > .dropdown-toggle.modextra-btn-default.focus { color: #333; background-color: #d4d4d4; border-color: #8c8c8c } 44 | .modextra-btn-default:active, .modextra-btn-default.active, .open > .dropdown-toggle.modextra-btn-default { background-image: none } 45 | .modextra-btn-default.disabled:hover, .modextra-btn-default[disabled]:hover, fieldset[disabled] .modextra-btn-default:hover, .modextra-btn-default.disabled:focus, .modextra-btn-default[disabled]:focus, fieldset[disabled] .modextra-btn-default:focus, .modextra-btn-default.disabled.focus, .modextra-btn-default[disabled].focus, fieldset[disabled] .modextra-btn-default.focus { background-color: #fff; border-color: #ccc } 46 | .modextra-btn-default .badge { color: #fff; background-color: #333 } 47 | .modextra-btn-primary { color: #fff; background-color: #337ab7; border-color: #2e6da4 } 48 | .modextra-btn-primary:focus, .modextra-btn-primary.focus { color: #fff; background-color: #286090; border-color: #122b40 } 49 | .modextra-btn-primary:hover { color: #fff; background-color: #286090; border-color: #204d74 } 50 | .modextra-btn-primary:active, .modextra-btn-primary.active, .open > .dropdown-toggle.modextra-btn-primary { color: #fff; background-color: #286090; border-color: #204d74 } 51 | .modextra-btn-primary:active:hover, .modextra-btn-primary.active:hover, .open > .dropdown-toggle.modextra-btn-primary:hover, .modextra-btn-primary:active:focus, .modextra-btn-primary.active:focus, .open > .dropdown-toggle.modextra-btn-primary:focus, .modextra-btn-primary:active.focus, .modextra-btn-primary.active.focus, .open > .dropdown-toggle.modextra-btn-primary.focus { color: #fff; background-color: #204d74; border-color: #122b40 } 52 | .modextra-btn-primary:active, .modextra-btn-primary.active, .open > .dropdown-toggle.modextra-btn-primary { background-image: none } 53 | .modextra-btn-primary.disabled:hover, .modextra-btn-primary[disabled]:hover, fieldset[disabled] .modextra-btn-primary:hover, .modextra-btn-primary.disabled:focus, .modextra-btn-primary[disabled]:focus, fieldset[disabled] .modextra-btn-primary:focus, .modextra-btn-primary.disabled.focus, .modextra-btn-primary[disabled].focus, fieldset[disabled] .modextra-btn-primary.focus { background-color: #337ab7; border-color: #2e6da4 } 54 | .modextra-btn-primary .badge { color: #337ab7; background-color: #fff } 55 | .modextra-btn-success { color: #fff; background-color: #5cb85c; border-color: #4cae4c } 56 | .modextra-btn-success:focus, .modextra-btn-success.focus { color: #fff; background-color: #449d44; border-color: #255625 } 57 | .modextra-btn-success:hover { color: #fff; background-color: #449d44; border-color: #398439 } 58 | .modextra-btn-success:active, .modextra-btn-success.active, .open > .dropdown-toggle.modextra-btn-success { color: #fff; background-color: #449d44; border-color: #398439 } 59 | .modextra-btn-success:active:hover, .modextra-btn-success.active:hover, .open > .dropdown-toggle.modextra-btn-success:hover, .modextra-btn-success:active:focus, .modextra-btn-success.active:focus, .open > .dropdown-toggle.modextra-btn-success:focus, .modextra-btn-success:active.focus, .modextra-btn-success.active.focus, .open > .dropdown-toggle.modextra-btn-success.focus { color: #fff; background-color: #398439; border-color: #255625 } 60 | .modextra-btn-success:active, .modextra-btn-success.active, .open > .dropdown-toggle.modextra-btn-success { background-image: none } 61 | .modextra-btn-success.disabled:hover, .modextra-btn-success[disabled]:hover, fieldset[disabled] .modextra-btn-success:hover, .modextra-btn-success.disabled:focus, .modextra-btn-success[disabled]:focus, fieldset[disabled] .modextra-btn-success:focus, .modextra-btn-success.disabled.focus, .modextra-btn-success[disabled].focus, fieldset[disabled] .modextra-btn-success.focus { background-color: #5cb85c; border-color: #4cae4c } 62 | .modextra-btn-success .badge { color: #5cb85c; background-color: #fff } 63 | .modextra-btn-info { color: #fff; background-color: #5bc0de; border-color: #46b8da } 64 | .modextra-btn-info:focus, .modextra-btn-info.focus { color: #fff; background-color: #31b0d5; border-color: #1b6d85 } 65 | .modextra-btn-info:hover { color: #fff; background-color: #31b0d5; border-color: #269abc } 66 | .modextra-btn-info:active, .modextra-btn-info.active, .open > .dropdown-toggle.modextra-btn-info { color: #fff; background-color: #31b0d5; border-color: #269abc } 67 | .modextra-btn-info:active:hover, .modextra-btn-info.active:hover, .open > .dropdown-toggle.modextra-btn-info:hover, .modextra-btn-info:active:focus, .modextra-btn-info.active:focus, .open > .dropdown-toggle.modextra-btn-info:focus, .modextra-btn-info:active.focus, .modextra-btn-info.active.focus, .open > .dropdown-toggle.modextra-btn-info.focus { color: #fff; background-color: #269abc; border-color: #1b6d85 } 68 | .modextra-btn-info:active, .modextra-btn-info.active, .open > .dropdown-toggle.modextra-btn-info { background-image: none } 69 | .modextra-btn-info.disabled:hover, .modextra-btn-info[disabled]:hover, fieldset[disabled] .modextra-btn-info:hover, .modextra-btn-info.disabled:focus, .modextra-btn-info[disabled]:focus, fieldset[disabled] .modextra-btn-info:focus, .modextra-btn-info.disabled.focus, .modextra-btn-info[disabled].focus, fieldset[disabled] .modextra-btn-info.focus { background-color: #5bc0de; border-color: #46b8da } 70 | .modextra-btn-info .badge { color: #5bc0de; background-color: #fff } 71 | .modextra-btn-warning { color: #fff; background-color: #f0ad4e; border-color: #eea236 } 72 | .modextra-btn-warning:focus, .modextra-btn-warning.focus { color: #fff; background-color: #ec971f; border-color: #985f0d } 73 | .modextra-btn-warning:hover { color: #fff; background-color: #ec971f; border-color: #d58512 } 74 | .modextra-btn-warning:active, .modextra-btn-warning.active, .open > .dropdown-toggle.modextra-btn-warning { color: #fff; background-color: #ec971f; border-color: #d58512 } 75 | .modextra-btn-warning:active:hover, .modextra-btn-warning.active:hover, .open > .dropdown-toggle.modextra-btn-warning:hover, .modextra-btn-warning:active:focus, .modextra-btn-warning.active:focus, .open > .dropdown-toggle.modextra-btn-warning:focus, .modextra-btn-warning:active.focus, .modextra-btn-warning.active.focus, .open > .dropdown-toggle.modextra-btn-warning.focus { color: #fff; background-color: #d58512; border-color: #985f0d } 76 | .modextra-btn-warning:active, .modextra-btn-warning.active, .open > .dropdown-toggle.modextra-btn-warning { background-image: none } 77 | .modextra-btn-warning.disabled:hover, .modextra-btn-warning[disabled]:hover, fieldset[disabled] .modextra-btn-warning:hover, .modextra-btn-warning.disabled:focus, .modextra-btn-warning[disabled]:focus, fieldset[disabled] .modextra-btn-warning:focus, .modextra-btn-warning.disabled.focus, .modextra-btn-warning[disabled].focus, fieldset[disabled] .modextra-btn-warning.focus { background-color: #f0ad4e; border-color: #eea236 } 78 | .modextra-btn-warning .badge { color: #f0ad4e; background-color: #fff } 79 | .modextra-btn-danger { color: #fff; background-color: #d9534f; border-color: #d43f3a } 80 | .modextra-btn-danger:focus, .modextra-btn-danger.focus { color: #fff; background-color: #c9302c; border-color: #761c19 } 81 | .modextra-btn-danger:hover { color: #fff; background-color: #c9302c; border-color: #ac2925 } 82 | .modextra-btn-danger:active, .modextra-btn-danger.active, .open > .dropdown-toggle.modextra-btn-danger { color: #fff; background-color: #c9302c; border-color: #ac2925 } 83 | .modextra-btn-danger:active:hover, .modextra-btn-danger.active:hover, .open > .dropdown-toggle.modextra-btn-danger:hover, .modextra-btn-danger:active:focus, .modextra-btn-danger.active:focus, .open > .dropdown-toggle.modextra-btn-danger:focus, .modextra-btn-danger:active.focus, .modextra-btn-danger.active.focus, .open > .dropdown-toggle.modextra-btn-danger.focus { color: #fff; background-color: #ac2925; border-color: #761c19 } 84 | .modextra-btn-danger:active, .modextra-btn-danger.active, .open > .dropdown-toggle.modextra-btn-danger { background-image: none } 85 | .modextra-btn-danger.disabled:hover, .modextra-btn-danger[disabled]:hover, fieldset[disabled] .modextra-btn-danger:hover, .modextra-btn-danger.disabled:focus, .modextra-btn-danger[disabled]:focus, fieldset[disabled] .modextra-btn-danger:focus, .modextra-btn-danger.disabled.focus, .modextra-btn-danger[disabled].focus, fieldset[disabled] .modextra-btn-danger.focus { background-color: #d9534f; border-color: #d43f3a } 86 | .modextra-btn-danger .badge { color: #d9534f; background-color: #fff } 87 | .modextra-btn-link { color: #337ab7; font-weight: normal; border-radius: 0 } 88 | .modextra-btn-link, .modextra-btn-link:active, .modextra-btn-link.active, .modextra-btn-link[disabled], fieldset[disabled] .modextra-btn-link { background-color: transparent; -webkit-box-shadow: none; box-shadow: none } 89 | .modextra-btn-link, .modextra-btn-link:hover, .modextra-btn-link:focus, .modextra-btn-link:active { border-color: transparent } 90 | .modextra-btn-link:hover, .modextra-btn-link:focus { color: #23527c; text-decoration: underline; background-color: transparent } 91 | .modextra-btn-link[disabled]:hover, fieldset[disabled] .modextra-btn-link:hover, .modextra-btn-link[disabled]:focus, fieldset[disabled] .modextra-btn-link:focus { color: #777; text-decoration: none } 92 | .modextra-btn-lg { padding: 10px 16px; font-size: 18px; line-height: 1.3333333; border-radius: 6px } 93 | .modextra-btn-sm { padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px } 94 | .modextra-btn-xs { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px } 95 | .modextra-btn-block { display: block; width: 100% } 96 | .modextra-btn-block + .modextra-btn-block { margin-top: 5px } -------------------------------------------------------------------------------- /assets/components/modextra/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modx-pro/modExtra/8295c2a6e987a0427c40ade90acb594be28b5696/assets/components/modextra/index.html -------------------------------------------------------------------------------- /assets/components/modextra/js/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modx-pro/modExtra/8295c2a6e987a0427c40ade90acb594be28b5696/assets/components/modextra/js/index.html -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/misc/combo.js: -------------------------------------------------------------------------------- 1 | modExtra.combo.Search = function (config) { 2 | config = config || {}; 3 | Ext.applyIf(config, { 4 | xtype: 'twintrigger', 5 | ctCls: 'x-field-search', 6 | allowBlank: true, 7 | msgTarget: 'under', 8 | emptyText: _('search'), 9 | name: 'query', 10 | triggerAction: 'all', 11 | clearBtnCls: 'x-field-search-clear', 12 | searchBtnCls: 'x-field-search-go', 13 | onTrigger1Click: this._triggerSearch, 14 | onTrigger2Click: this._triggerClear, 15 | }); 16 | modExtra.combo.Search.superclass.constructor.call(this, config); 17 | this.on('render', function () { 18 | this.getEl().addKeyListener(Ext.EventObject.ENTER, function () { 19 | this._triggerSearch(); 20 | }, this); 21 | }); 22 | this.addEvents('clear', 'search'); 23 | }; 24 | Ext.extend(modExtra.combo.Search, Ext.form.TwinTriggerField, { 25 | 26 | initComponent: function () { 27 | Ext.form.TwinTriggerField.superclass.initComponent.call(this); 28 | this.triggerConfig = { 29 | tag: 'span', 30 | cls: 'x-field-search-btns', 31 | cn: [ 32 | {tag: 'div', cls: 'x-form-trigger ' + this.searchBtnCls}, 33 | {tag: 'div', cls: 'x-form-trigger ' + this.clearBtnCls} 34 | ] 35 | }; 36 | }, 37 | 38 | _triggerSearch: function () { 39 | this.fireEvent('search', this); 40 | }, 41 | 42 | _triggerClear: function () { 43 | this.fireEvent('clear', this); 44 | }, 45 | 46 | }); 47 | Ext.reg('modextra-combo-search', modExtra.combo.Search); 48 | Ext.reg('modextra-field-search', modExtra.combo.Search); -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/misc/utils.js: -------------------------------------------------------------------------------- 1 | modExtra.utils.renderBoolean = function (value) { 2 | return value 3 | ? String.format('{0}', _('yes')) 4 | : String.format('{0}', _('no')); 5 | }; 6 | 7 | modExtra.utils.getMenu = function (actions, grid, selected) { 8 | var menu = []; 9 | var cls, icon, title, action; 10 | 11 | var has_delete = false; 12 | for (var i in actions) { 13 | if (!actions.hasOwnProperty(i)) { 14 | continue; 15 | } 16 | 17 | var a = actions[i]; 18 | if (!a['menu']) { 19 | if (a == '-') { 20 | menu.push('-'); 21 | } 22 | continue; 23 | } 24 | else if (menu.length > 0 && !has_delete && (/^remove/i.test(a['action']) || /^delete/i.test(a['action']))) { 25 | menu.push('-'); 26 | has_delete = true; 27 | } 28 | 29 | if (selected.length > 1) { 30 | if (!a['multiple']) { 31 | continue; 32 | } 33 | else if (typeof(a['multiple']) == 'string') { 34 | a['title'] = a['multiple']; 35 | } 36 | } 37 | 38 | icon = a['icon'] ? a['icon'] : ''; 39 | if (typeof(a['cls']) == 'object') { 40 | if (typeof(a['cls']['menu']) != 'undefined') { 41 | icon += ' ' + a['cls']['menu']; 42 | } 43 | } 44 | else { 45 | cls = a['cls'] ? a['cls'] : ''; 46 | } 47 | title = a['title'] ? a['title'] : a['title']; 48 | action = a['action'] ? grid[a['action']] : ''; 49 | 50 | menu.push({ 51 | handler: action, 52 | text: String.format( 53 | '{2}', 54 | cls, icon, title 55 | ), 56 | scope: grid 57 | }); 58 | } 59 | 60 | return menu; 61 | }; 62 | 63 | modExtra.utils.renderActions = function (value, props, row) { 64 | var res = []; 65 | var cls, icon, title, action, item; 66 | for (var i in row.data.actions) { 67 | if (!row.data.actions.hasOwnProperty(i)) { 68 | continue; 69 | } 70 | var a = row.data.actions[i]; 71 | if (!a['button']) { 72 | continue; 73 | } 74 | 75 | icon = a['icon'] ? a['icon'] : ''; 76 | if (typeof(a['cls']) == 'object') { 77 | if (typeof(a['cls']['button']) != 'undefined') { 78 | icon += ' ' + a['cls']['button']; 79 | } 80 | } 81 | else { 82 | cls = a['cls'] ? a['cls'] : ''; 83 | } 84 | action = a['action'] ? a['action'] : ''; 85 | title = a['title'] ? a['title'] : ''; 86 | 87 | item = String.format( 88 | '
  • ', 89 | cls, icon, action, title 90 | ); 91 | 92 | res.push(item); 93 | } 94 | 95 | return String.format( 96 | '', 97 | res.join('') 98 | ); 99 | }; -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/modextra.js: -------------------------------------------------------------------------------- 1 | var modExtra = function (config) { 2 | config = config || {}; 3 | modExtra.superclass.constructor.call(this, config); 4 | }; 5 | Ext.extend(modExtra, Ext.Component, { 6 | page: {}, window: {}, grid: {}, tree: {}, panel: {}, combo: {}, config: {}, view: {}, utils: {} 7 | }); 8 | Ext.reg('modextra', modExtra); 9 | 10 | modExtra = new modExtra(); -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/sections/home.js: -------------------------------------------------------------------------------- 1 | modExtra.page.Home = function (config) { 2 | config = config || {}; 3 | Ext.applyIf(config, { 4 | components: [{ 5 | xtype: 'modextra-panel-home', 6 | renderTo: 'modextra-panel-home-div' 7 | }] 8 | }); 9 | modExtra.page.Home.superclass.constructor.call(this, config); 10 | }; 11 | Ext.extend(modExtra.page.Home, MODx.Component); 12 | Ext.reg('modextra-page-home', modExtra.page.Home); -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/widgets/home.panel.js: -------------------------------------------------------------------------------- 1 | modExtra.panel.Home = function (config) { 2 | config = config || {}; 3 | Ext.apply(config, { 4 | baseCls: 'modx-formpanel', 5 | layout: 'anchor', 6 | /* 7 | stateful: true, 8 | stateId: 'modextra-panel-home', 9 | stateEvents: ['tabchange'], 10 | getState:function() {return {activeTab:this.items.indexOf(this.getActiveTab())};}, 11 | */ 12 | hideMode: 'offsets', 13 | items: [{ 14 | html: '

    ' + _('modextra') + '

    ', 15 | cls: '', 16 | style: {margin: '15px 0'} 17 | }, { 18 | xtype: 'modx-tabs', 19 | defaults: {border: false, autoHeight: true}, 20 | border: true, 21 | hideMode: 'offsets', 22 | items: [{ 23 | title: _('modextra_items'), 24 | layout: 'anchor', 25 | items: [{ 26 | html: _('modextra_intro_msg'), 27 | cls: 'panel-desc', 28 | }, { 29 | xtype: 'modextra-grid-items', 30 | cls: 'main-wrapper', 31 | }] 32 | }] 33 | }] 34 | }); 35 | modExtra.panel.Home.superclass.constructor.call(this, config); 36 | }; 37 | Ext.extend(modExtra.panel.Home, MODx.Panel); 38 | Ext.reg('modextra-panel-home', modExtra.panel.Home); 39 | -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/widgets/items.grid.js: -------------------------------------------------------------------------------- 1 | modExtra.grid.Items = function (config) { 2 | config = config || {}; 3 | if (!config.id) { 4 | config.id = 'modextra-grid-items'; 5 | } 6 | Ext.applyIf(config, { 7 | url: modExtra.config.connector_url, 8 | fields: this.getFields(config), 9 | columns: this.getColumns(config), 10 | tbar: this.getTopBar(config), 11 | sm: new Ext.grid.CheckboxSelectionModel(), 12 | baseParams: { 13 | action: 'mgr/item/getlist' 14 | }, 15 | listeners: { 16 | rowDblClick: function (grid, rowIndex, e) { 17 | var row = grid.store.getAt(rowIndex); 18 | this.updateItem(grid, e, row); 19 | } 20 | }, 21 | viewConfig: { 22 | forceFit: true, 23 | enableRowBody: true, 24 | autoFill: true, 25 | showPreview: true, 26 | scrollOffset: 0, 27 | getRowClass: function (rec) { 28 | return !rec.data.active 29 | ? 'modextra-grid-row-disabled' 30 | : ''; 31 | } 32 | }, 33 | paging: true, 34 | remoteSort: true, 35 | autoHeight: true, 36 | }); 37 | modExtra.grid.Items.superclass.constructor.call(this, config); 38 | 39 | // Clear selection on grid refresh 40 | this.store.on('load', function () { 41 | if (this._getSelectedIds().length) { 42 | this.getSelectionModel().clearSelections(); 43 | } 44 | }, this); 45 | }; 46 | Ext.extend(modExtra.grid.Items, MODx.grid.Grid, { 47 | windows: {}, 48 | 49 | getMenu: function (grid, rowIndex) { 50 | var ids = this._getSelectedIds(); 51 | 52 | var row = grid.getStore().getAt(rowIndex); 53 | var menu = modExtra.utils.getMenu(row.data['actions'], this, ids); 54 | 55 | this.addContextMenuItem(menu); 56 | }, 57 | 58 | createItem: function (btn, e) { 59 | var w = MODx.load({ 60 | xtype: 'modextra-item-window-create', 61 | id: Ext.id(), 62 | listeners: { 63 | success: { 64 | fn: function () { 65 | this.refresh(); 66 | }, scope: this 67 | } 68 | } 69 | }); 70 | w.reset(); 71 | w.setValues({active: true}); 72 | w.show(e.target); 73 | }, 74 | 75 | updateItem: function (btn, e, row) { 76 | if (typeof(row) != 'undefined') { 77 | this.menu.record = row.data; 78 | } 79 | else if (!this.menu.record) { 80 | return false; 81 | } 82 | var id = this.menu.record.id; 83 | 84 | MODx.Ajax.request({ 85 | url: this.config.url, 86 | params: { 87 | action: 'mgr/item/get', 88 | id: id 89 | }, 90 | listeners: { 91 | success: { 92 | fn: function (r) { 93 | var w = MODx.load({ 94 | xtype: 'modextra-item-window-update', 95 | id: Ext.id(), 96 | record: r, 97 | listeners: { 98 | success: { 99 | fn: function () { 100 | this.refresh(); 101 | }, scope: this 102 | } 103 | } 104 | }); 105 | w.reset(); 106 | w.setValues(r.object); 107 | w.show(e.target); 108 | }, scope: this 109 | } 110 | } 111 | }); 112 | }, 113 | 114 | removeItem: function () { 115 | var ids = this._getSelectedIds(); 116 | if (!ids.length) { 117 | return false; 118 | } 119 | MODx.msg.confirm({ 120 | title: ids.length > 1 121 | ? _('modextra_items_remove') 122 | : _('modextra_item_remove'), 123 | text: ids.length > 1 124 | ? _('modextra_items_remove_confirm') 125 | : _('modextra_item_remove_confirm'), 126 | url: this.config.url, 127 | params: { 128 | action: 'mgr/item/remove', 129 | ids: Ext.util.JSON.encode(ids), 130 | }, 131 | listeners: { 132 | success: { 133 | fn: function () { 134 | this.refresh(); 135 | }, scope: this 136 | } 137 | } 138 | }); 139 | return true; 140 | }, 141 | 142 | disableItem: function () { 143 | var ids = this._getSelectedIds(); 144 | if (!ids.length) { 145 | return false; 146 | } 147 | MODx.Ajax.request({ 148 | url: this.config.url, 149 | params: { 150 | action: 'mgr/item/disable', 151 | ids: Ext.util.JSON.encode(ids), 152 | }, 153 | listeners: { 154 | success: { 155 | fn: function () { 156 | this.refresh(); 157 | }, scope: this 158 | } 159 | } 160 | }) 161 | }, 162 | 163 | enableItem: function () { 164 | var ids = this._getSelectedIds(); 165 | if (!ids.length) { 166 | return false; 167 | } 168 | MODx.Ajax.request({ 169 | url: this.config.url, 170 | params: { 171 | action: 'mgr/item/enable', 172 | ids: Ext.util.JSON.encode(ids), 173 | }, 174 | listeners: { 175 | success: { 176 | fn: function () { 177 | this.refresh(); 178 | }, scope: this 179 | } 180 | } 181 | }) 182 | }, 183 | 184 | getFields: function () { 185 | return ['id', 'name', 'description', 'active', 'actions']; 186 | }, 187 | 188 | getColumns: function () { 189 | return [{ 190 | header: _('modextra_item_id'), 191 | dataIndex: 'id', 192 | sortable: true, 193 | width: 70 194 | }, { 195 | header: _('modextra_item_name'), 196 | dataIndex: 'name', 197 | sortable: true, 198 | width: 200, 199 | }, { 200 | header: _('modextra_item_description'), 201 | dataIndex: 'description', 202 | sortable: false, 203 | width: 250, 204 | }, { 205 | header: _('modextra_item_active'), 206 | dataIndex: 'active', 207 | renderer: modExtra.utils.renderBoolean, 208 | sortable: true, 209 | width: 100, 210 | }, { 211 | header: _('modextra_grid_actions'), 212 | dataIndex: 'actions', 213 | renderer: modExtra.utils.renderActions, 214 | sortable: false, 215 | width: 100, 216 | id: 'actions' 217 | }]; 218 | }, 219 | 220 | getTopBar: function () { 221 | return [{ 222 | text: ' ' + _('modextra_item_create'), 223 | handler: this.createItem, 224 | scope: this 225 | }, '->', { 226 | xtype: 'modextra-field-search', 227 | width: 250, 228 | listeners: { 229 | search: { 230 | fn: function (field) { 231 | this._doSearch(field); 232 | }, scope: this 233 | }, 234 | clear: { 235 | fn: function (field) { 236 | field.setValue(''); 237 | this._clearSearch(); 238 | }, scope: this 239 | }, 240 | } 241 | }]; 242 | }, 243 | 244 | onClick: function (e) { 245 | var elem = e.getTarget(); 246 | if (elem.nodeName == 'BUTTON') { 247 | var row = this.getSelectionModel().getSelected(); 248 | if (typeof(row) != 'undefined') { 249 | var action = elem.getAttribute('action'); 250 | if (action == 'showMenu') { 251 | var ri = this.getStore().find('id', row.id); 252 | return this._showMenu(this, ri, e); 253 | } 254 | else if (typeof this[action] === 'function') { 255 | this.menu.record = row.data; 256 | return this[action](this, e); 257 | } 258 | } 259 | } 260 | return this.processEvent('click', e); 261 | }, 262 | 263 | _getSelectedIds: function () { 264 | var ids = []; 265 | var selected = this.getSelectionModel().getSelections(); 266 | 267 | for (var i in selected) { 268 | if (!selected.hasOwnProperty(i)) { 269 | continue; 270 | } 271 | ids.push(selected[i]['id']); 272 | } 273 | 274 | return ids; 275 | }, 276 | 277 | _doSearch: function (tf) { 278 | this.getStore().baseParams.query = tf.getValue(); 279 | this.getBottomToolbar().changePage(1); 280 | }, 281 | 282 | _clearSearch: function () { 283 | this.getStore().baseParams.query = ''; 284 | this.getBottomToolbar().changePage(1); 285 | }, 286 | }); 287 | Ext.reg('modextra-grid-items', modExtra.grid.Items); 288 | -------------------------------------------------------------------------------- /assets/components/modextra/js/mgr/widgets/items.windows.js: -------------------------------------------------------------------------------- 1 | modExtra.window.CreateItem = function (config) { 2 | config = config || {}; 3 | if (!config.id) { 4 | config.id = 'modextra-item-window-create'; 5 | } 6 | Ext.applyIf(config, { 7 | title: _('modextra_item_create'), 8 | width: 550, 9 | autoHeight: true, 10 | url: modExtra.config.connector_url, 11 | action: 'mgr/item/create', 12 | fields: this.getFields(config), 13 | keys: [{ 14 | key: Ext.EventObject.ENTER, shift: true, fn: function () { 15 | this.submit() 16 | }, scope: this 17 | }] 18 | }); 19 | modExtra.window.CreateItem.superclass.constructor.call(this, config); 20 | }; 21 | Ext.extend(modExtra.window.CreateItem, MODx.Window, { 22 | 23 | getFields: function (config) { 24 | return [{ 25 | xtype: 'textfield', 26 | fieldLabel: _('modextra_item_name'), 27 | name: 'name', 28 | id: config.id + '-name', 29 | anchor: '99%', 30 | allowBlank: false, 31 | }, { 32 | xtype: 'textarea', 33 | fieldLabel: _('modextra_item_description'), 34 | name: 'description', 35 | id: config.id + '-description', 36 | height: 150, 37 | anchor: '99%' 38 | }, { 39 | xtype: 'xcheckbox', 40 | boxLabel: _('modextra_item_active'), 41 | name: 'active', 42 | id: config.id + '-active', 43 | checked: true, 44 | }]; 45 | }, 46 | 47 | loadDropZones: function () { 48 | } 49 | 50 | }); 51 | Ext.reg('modextra-item-window-create', modExtra.window.CreateItem); 52 | 53 | 54 | modExtra.window.UpdateItem = function (config) { 55 | config = config || {}; 56 | if (!config.id) { 57 | config.id = 'modextra-item-window-update'; 58 | } 59 | Ext.applyIf(config, { 60 | title: _('modextra_item_update'), 61 | width: 550, 62 | autoHeight: true, 63 | url: modExtra.config.connector_url, 64 | action: 'mgr/item/update', 65 | fields: this.getFields(config), 66 | keys: [{ 67 | key: Ext.EventObject.ENTER, shift: true, fn: function () { 68 | this.submit() 69 | }, scope: this 70 | }] 71 | }); 72 | modExtra.window.UpdateItem.superclass.constructor.call(this, config); 73 | }; 74 | Ext.extend(modExtra.window.UpdateItem, MODx.Window, { 75 | 76 | getFields: function (config) { 77 | return [{ 78 | xtype: 'hidden', 79 | name: 'id', 80 | id: config.id + '-id', 81 | }, { 82 | xtype: 'textfield', 83 | fieldLabel: _('modextra_item_name'), 84 | name: 'name', 85 | id: config.id + '-name', 86 | anchor: '99%', 87 | allowBlank: false, 88 | }, { 89 | xtype: 'textarea', 90 | fieldLabel: _('modextra_item_description'), 91 | name: 'description', 92 | id: config.id + '-description', 93 | anchor: '99%', 94 | height: 150, 95 | }, { 96 | xtype: 'xcheckbox', 97 | boxLabel: _('modextra_item_active'), 98 | name: 'active', 99 | id: config.id + '-active', 100 | }]; 101 | }, 102 | 103 | loadDropZones: function () { 104 | } 105 | 106 | }); 107 | Ext.reg('modextra-item-window-update', modExtra.window.UpdateItem); -------------------------------------------------------------------------------- /assets/components/modextra/js/office/default.js: -------------------------------------------------------------------------------- 1 | Ext.onReady(function () { 2 | modExtra.config.connector_url = OfficeConfig.actionUrl; 3 | 4 | var grid = new modExtra.panel.Home(); 5 | grid.render('office-modextra-wrapper'); 6 | 7 | var preloader = document.getElementById('office-preloader'); 8 | if (preloader) { 9 | preloader.parentNode.removeChild(preloader); 10 | } 11 | }); -------------------------------------------------------------------------------- /assets/components/modextra/js/office/home.panel.js: -------------------------------------------------------------------------------- 1 | modExtra.panel.Home = function (config) { 2 | config = config || {}; 3 | Ext.apply(config, { 4 | baseCls: 'modx-formpanel', 5 | layout: 'anchor', 6 | /* 7 | stateful: true, 8 | stateId: 'modextra-panel-home', 9 | stateEvents: ['tabchange'], 10 | getState:function() {return {activeTab:this.items.indexOf(this.getActiveTab())};}, 11 | */ 12 | hideMode: 'offsets', 13 | items: [{ 14 | xtype: 'modx-tabs', 15 | defaults: {border: false, autoHeight: true}, 16 | border: false, 17 | hideMode: 'offsets', 18 | items: [{ 19 | title: _('modextra_items'), 20 | layout: 'anchor', 21 | items: [{ 22 | html: _('modextra_intro_msg'), 23 | cls: 'panel-desc', 24 | }, { 25 | xtype: 'modextra-grid-items', 26 | cls: 'main-wrapper', 27 | }] 28 | }] 29 | }] 30 | }); 31 | modExtra.panel.Home.superclass.constructor.call(this, config); 32 | }; 33 | Ext.extend(modExtra.panel.Home, MODx.Panel); 34 | Ext.reg('modextra-panel-home', modExtra.panel.Home); 35 | -------------------------------------------------------------------------------- /assets/components/modextra/js/office/items.grid.js: -------------------------------------------------------------------------------- 1 | modExtra.grid.Items = function (config) { 2 | config = config || {}; 3 | if (!config.id) { 4 | config.id = 'modextra-grid-items'; 5 | } 6 | Ext.applyIf(config, { 7 | url: modExtra.config.connector_url, 8 | baseParams: { 9 | action: 'modextra/processor', 10 | method: 'item/getlist', 11 | }, 12 | multi_select: true, 13 | viewConfig: { 14 | forceFit: true, 15 | enableRowBody: true, 16 | autoFill: true, 17 | showPreview: true, 18 | scrollOffset: 0, 19 | getRowClass: function (rec, ri, p) { 20 | return !rec.data.active 21 | ? 'office-grid-row-disabled' 22 | : ''; 23 | } 24 | }, 25 | }); 26 | modExtra.grid.Items.superclass.constructor.call(this, config); 27 | 28 | // Clear selection on grid refresh 29 | this.store.on('load', function () { 30 | if (this._getSelectedIds().length) { 31 | this.getSelectionModel().clearSelections(); 32 | } 33 | }, this); 34 | }; 35 | Ext.extend(modExtra.grid.Items, OfficeExt.grid.Default, { 36 | windows: {}, 37 | 38 | createItem: function (btn, e) { 39 | var w = MODx.load({ 40 | xtype: 'modextra-item-window-create', 41 | id: Ext.id(), 42 | listeners: { 43 | success: { 44 | fn: function () { 45 | this.refresh(); 46 | }, scope: this 47 | } 48 | } 49 | }); 50 | w.reset(); 51 | w.setValues({active: true}); 52 | w.show(e.target); 53 | }, 54 | 55 | updateItem: function (btn, e, row) { 56 | if (typeof(row) != 'undefined') { 57 | this.menu.record = row.data; 58 | } 59 | else if (!this.menu.record) { 60 | return false; 61 | } 62 | var id = this.menu.record.id; 63 | 64 | MODx.Ajax.request({ 65 | url: this.config.url, 66 | params: { 67 | action: 'modextra/processor', 68 | method: 'item/get', 69 | id: id, 70 | }, 71 | listeners: { 72 | success: { 73 | fn: function (r) { 74 | var w = MODx.load({ 75 | xtype: 'modextra-item-window-update', 76 | id: Ext.id(), 77 | record: r, 78 | listeners: { 79 | success: { 80 | fn: function () { 81 | this.refresh(); 82 | }, scope: this 83 | } 84 | } 85 | }); 86 | w.reset(); 87 | w.setValues(r.object); 88 | w.show(e.target); 89 | }, scope: this 90 | } 91 | } 92 | }); 93 | }, 94 | 95 | removeItem: function (act, btn, e) { 96 | var ids = this._getSelectedIds(); 97 | if (!ids.length) { 98 | return false; 99 | } 100 | MODx.msg.confirm({ 101 | title: ids.length > 1 102 | ? _('modextra_items_remove') 103 | : _('modextra_item_remove'), 104 | text: ids.length > 1 105 | ? _('modextra_items_remove_confirm') 106 | : _('modextra_item_remove_confirm'), 107 | url: this.config.url, 108 | params: { 109 | action: 'modextra/processor', 110 | method: 'item/remove', 111 | ids: Ext.util.JSON.encode(ids), 112 | }, 113 | listeners: { 114 | success: { 115 | fn: function (r) { 116 | this.refresh(); 117 | }, scope: this 118 | } 119 | } 120 | }); 121 | return true; 122 | }, 123 | 124 | disableItem: function (act, btn, e) { 125 | var ids = this._getSelectedIds(); 126 | if (!ids.length) { 127 | return false; 128 | } 129 | MODx.Ajax.request({ 130 | url: this.config.url, 131 | params: { 132 | action: 'modextra/processor', 133 | method: 'item/disable', 134 | ids: Ext.util.JSON.encode(ids), 135 | }, 136 | listeners: { 137 | success: { 138 | fn: function () { 139 | this.refresh(); 140 | }, scope: this 141 | } 142 | } 143 | }) 144 | }, 145 | 146 | enableItem: function (act, btn, e) { 147 | var ids = this._getSelectedIds(); 148 | if (!ids.length) { 149 | return false; 150 | } 151 | MODx.Ajax.request({ 152 | url: this.config.url, 153 | params: { 154 | action: 'modextra/processor', 155 | method: 'item/enable', 156 | ids: Ext.util.JSON.encode(ids), 157 | }, 158 | listeners: { 159 | success: { 160 | fn: function () { 161 | this.refresh(); 162 | }, scope: this 163 | } 164 | } 165 | }) 166 | }, 167 | 168 | getFields: function (config) { 169 | return ['id', 'name', 'description', 'active', 'actions']; 170 | }, 171 | 172 | getColumns: function (config) { 173 | return [{ 174 | header: _('modextra_item_id'), 175 | dataIndex: 'id', 176 | sortable: true, 177 | width: 70 178 | }, { 179 | header: _('modextra_item_name'), 180 | dataIndex: 'name', 181 | sortable: true, 182 | width: 200, 183 | }, { 184 | header: _('modextra_item_description'), 185 | dataIndex: 'description', 186 | sortable: false, 187 | width: 250, 188 | }, { 189 | header: _('modextra_item_active'), 190 | dataIndex: 'active', 191 | renderer: OfficeExt.utils.renderBoolean, 192 | sortable: true, 193 | width: 100, 194 | }, { 195 | header: _('modextra_grid_actions'), 196 | dataIndex: 'actions', 197 | renderer: OfficeExt.utils.renderActions, 198 | sortable: false, 199 | width: 100, 200 | id: 'actions' 201 | }]; 202 | }, 203 | 204 | getTopBar: function (config) { 205 | return [{ 206 | text: ' ' + _('modextra_item_create'), 207 | handler: this.createItem, 208 | scope: this 209 | }, '->', this.getSearchField()]; 210 | }, 211 | 212 | getListeners: function () { 213 | return { 214 | rowDblClick: function (grid, rowIndex, e) { 215 | var row = grid.store.getAt(rowIndex); 216 | this.updateItem(grid, e, row); 217 | } 218 | }; 219 | }, 220 | 221 | }); 222 | Ext.reg('modextra-grid-items', modExtra.grid.Items); 223 | -------------------------------------------------------------------------------- /assets/components/modextra/js/office/items.windows.js: -------------------------------------------------------------------------------- 1 | modExtra.window.CreateItem = function (config) { 2 | config = config || {}; 3 | if (!config.id) { 4 | config.id = 'modextra-item-window-create'; 5 | } 6 | Ext.applyIf(config, { 7 | title: _('modextra_item_create'), 8 | width: 550, 9 | autoHeight: true, 10 | url: modExtra.config.connector_url, 11 | baseParams: { 12 | action: 'modextra/processor', 13 | method: 'item/create', 14 | }, 15 | fields: this.getFields(config), 16 | keys: this.getKeys(config), 17 | }); 18 | modExtra.window.CreateItem.superclass.constructor.call(this, config); 19 | this.on('hide', function () { 20 | var w = this; 21 | window.setTimeout(function () { 22 | w.close(); 23 | }, 200); 24 | }); 25 | }; 26 | Ext.extend(modExtra.window.CreateItem, MODx.Window, { 27 | 28 | getFields: function (config) { 29 | return [{ 30 | xtype: 'textfield', 31 | fieldLabel: _('modextra_item_name'), 32 | name: 'name', 33 | id: config.id + '-name', 34 | anchor: '99%', 35 | allowBlank: false, 36 | }, { 37 | xtype: 'textarea', 38 | fieldLabel: _('modextra_item_description'), 39 | name: 'description', 40 | id: config.id + '-description', 41 | height: 150, 42 | anchor: '99%' 43 | }, { 44 | xtype: 'xcheckbox', 45 | boxLabel: _('modextra_item_active'), 46 | name: 'active', 47 | id: config.id + '-active', 48 | checked: true, 49 | }]; 50 | }, 51 | 52 | getKeys: function (config) { 53 | return [{ 54 | key: Ext.EventObject.ENTER, shift: true, fn: function () { 55 | this.submit() 56 | }, scope: this 57 | }]; 58 | }, 59 | 60 | }); 61 | Ext.reg('modextra-item-window-create', modExtra.window.CreateItem); 62 | 63 | 64 | modExtra.window.UpdateItem = function (config) { 65 | config = config || {}; 66 | if (!config.id) { 67 | config.id = 'modextra-item-window-update'; 68 | } 69 | Ext.applyIf(config, { 70 | title: _('modextra_item_update'), 71 | width: 550, 72 | autoHeight: true, 73 | url: modExtra.config.connector_url, 74 | baseParams: { 75 | action: 'modextra/processor', 76 | method: 'item/update', 77 | }, 78 | fields: this.getFields(config), 79 | keys: this.getKeys(config), 80 | }); 81 | modExtra.window.UpdateItem.superclass.constructor.call(this, config); 82 | this.on('hide', function () { 83 | var w = this; 84 | window.setTimeout(function () { 85 | w.close(); 86 | }, 200); 87 | }); 88 | }; 89 | Ext.extend(modExtra.window.UpdateItem, MODx.Window, { 90 | 91 | getFields: function (config) { 92 | return [{ 93 | xtype: 'hidden', 94 | name: 'id', 95 | id: config.id + '-id', 96 | }, { 97 | xtype: 'textfield', 98 | fieldLabel: _('modextra_item_name'), 99 | name: 'name', 100 | id: config.id + '-name', 101 | anchor: '99%', 102 | allowBlank: false, 103 | }, { 104 | xtype: 'textarea', 105 | fieldLabel: _('modextra_item_description'), 106 | name: 'description', 107 | id: config.id + '-description', 108 | anchor: '99%', 109 | height: 150, 110 | }, { 111 | xtype: 'xcheckbox', 112 | boxLabel: _('modextra_item_active'), 113 | name: 'active', 114 | id: config.id + '-active', 115 | }]; 116 | }, 117 | 118 | getKeys: function () { 119 | return [{ 120 | key: Ext.EventObject.ENTER, shift: true, fn: function () { 121 | this.submit() 122 | }, scope: this 123 | }]; 124 | }, 125 | 126 | }); 127 | Ext.reg('modextra-item-window-update', modExtra.window.UpdateItem); -------------------------------------------------------------------------------- /core/components/modextra/controllers/home.class.php: -------------------------------------------------------------------------------- 1 | modExtra = $this->modx->getService('modExtra', 'modExtra', MODX_CORE_PATH . 'components/modextra/model/'); 19 | parent::initialize(); 20 | } 21 | 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function getLanguageTopics() 27 | { 28 | return ['modextra:default']; 29 | } 30 | 31 | 32 | /** 33 | * @return bool 34 | */ 35 | public function checkPermissions() 36 | { 37 | return true; 38 | } 39 | 40 | 41 | /** 42 | * @return null|string 43 | */ 44 | public function getPageTitle() 45 | { 46 | return $this->modx->lexicon('modextra'); 47 | } 48 | 49 | 50 | /** 51 | * @return void 52 | */ 53 | public function loadCustomCssJs() 54 | { 55 | $this->addCss($this->modExtra->config['cssUrl'] . 'mgr/main.css'); 56 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/modextra.js'); 57 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/misc/utils.js'); 58 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/misc/combo.js'); 59 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/widgets/items.grid.js'); 60 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/widgets/items.windows.js'); 61 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/widgets/home.panel.js'); 62 | $this->addJavascript($this->modExtra->config['jsUrl'] . 'mgr/sections/home.js'); 63 | 64 | $this->addHtml(''); 69 | } 70 | 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getTemplateFile() 76 | { 77 | $this->content .= '
    '; 78 | 79 | return ''; 80 | } 81 | } -------------------------------------------------------------------------------- /core/components/modextra/controllers/office/modextra.class.php: -------------------------------------------------------------------------------- 1 | config = $_SESSION['Office']['modExtra']; 13 | $this->config['json_response'] = true; 14 | } else { 15 | $this->config = array_merge([ 16 | 'tplOuter' => 'tpl.modExtra.office', 17 | ], $config); 18 | 19 | $_SESSION['Office']['modExtra'] = $this->config; 20 | } 21 | 22 | $this->office->config['processorsPath'] = MODX_CORE_PATH . 'components/modextra/processors/office/'; 23 | } 24 | 25 | 26 | /** 27 | * @return array 28 | */ 29 | public function getLanguageTopics() 30 | { 31 | return ['modextra:default']; 32 | } 33 | 34 | 35 | /** 36 | * @param string $ctx 37 | * 38 | * @return bool 39 | */ 40 | public function initialize($ctx = 'web') 41 | { 42 | $this->modx->error->errors = []; 43 | $this->modx->error->message = ''; 44 | 45 | return $this->loadPackage(); 46 | } 47 | 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function defaultAction() 53 | { 54 | /* 55 | // Check for authorized user 56 | if (!$this->modx->user->isAuthenticated($this->modx->context->key)) { 57 | return $this->modx->user->isAuthenticated('mgr') 58 | ? $this->modx->lexicon('office_err_mgr_auth') 59 | : ''; 60 | } 61 | */ 62 | 63 | $config = $this->office->makePlaceholders($this->office->config); 64 | $css = trim($this->modx->getOption('office_modextra_frontend_css', null, 65 | MODX_ASSETS_URL . 'components/office/css/main/default.css', true)); 66 | if (!empty($css)) { 67 | $this->modx->regClientCSS(str_replace($config['pl'], $config['vl'], $css)); 68 | } 69 | 70 | $js = trim($this->modx->getOption('office_modextra_frontend_js', null, 71 | MODX_ASSETS_URL . 'components/modextra/js/office/default.js')); 72 | if (!empty($js)) { 73 | $this->office->addClientExtJS(); 74 | $this->office->addClientLexicon([ 75 | 'modextra:default', 76 | ], 'modextra/lexicon'); 77 | 78 | $this->office->addClientJs([ 79 | MODX_ASSETS_URL . 'components/modextra/js/mgr/modextra.js', 80 | MODX_ASSETS_URL . 'components/modextra/js/mgr/misc/utils.js', 81 | MODX_ASSETS_URL . 'components/modextra/js/office/home.panel.js', 82 | MODX_ASSETS_URL . 'components/modextra/js/office/items.grid.js', 83 | MODX_ASSETS_URL . 'components/modextra/js/office/items.windows.js', 84 | str_replace($config['pl'], $config['vl'], $js), 85 | ], 'modextra/all'); 86 | } 87 | 88 | return $this->modx->getChunk($this->config['tplOuter']); 89 | } 90 | 91 | 92 | /** 93 | * @return bool 94 | */ 95 | public function loadPackage() 96 | { 97 | $corePath = $this->modx->getOption('modextra.core_path', null, 98 | $this->modx->getOption('core_path') . 'components/modextra/'); 99 | $modelPath = $corePath . 'model/'; 100 | 101 | return $this->modx->addPackage('modextra', $modelPath); 102 | } 103 | 104 | 105 | /** 106 | * @param array $data 107 | * 108 | * @return array|string 109 | */ 110 | public function Processor(array $data) 111 | { 112 | if (empty($data['method'])) { 113 | return $this->error('You need to specify processor method'); 114 | } 115 | $method = $data['method']; 116 | unset($data['method']); 117 | 118 | /** @var modProcessorResponse|array $response */ 119 | $response = $this->office->runProcessor($method, $data)->getResponse(); 120 | 121 | if (is_array($response)) { 122 | if (!isset($response['data'])) { 123 | $response['data'] = []; 124 | } 125 | if ($response['errors'] === null) { 126 | $response['errors'] = []; 127 | } 128 | if ($response['message'] === null) { 129 | $response['message'] = ''; 130 | } 131 | 132 | return json_encode($response); 133 | } else { 134 | return $response; 135 | } 136 | } 137 | 138 | } 139 | 140 | return 'officeModExtraController'; -------------------------------------------------------------------------------- /core/components/modextra/docs/changelog.txt: -------------------------------------------------------------------------------- 1 | Changelog for modExtra. 2 | 3 | 2.0.0-pl 4 | ============== 5 | - New build script. 6 | - [#4] Ability to specify the exact repository to install package from. 7 | - [#6] Renaming script works in any environment. 8 | - Updated tables resolver. 9 | - All tables now InnoDB by default. 10 | - Removed shell renaming script. 11 | 12 | 1.0.1-beta 13 | ============== 14 | - PSR-2 15 | - Requires at least MODX 2.3 16 | - Disabling "office" resolver will clear its files from built package. 17 | - Fixed some typos. 18 | 19 | 1.0.0-beta 20 | ============== 21 | - Optimized for MODX 2.3 22 | - Improved processors 23 | - Disabled plugin and system settings 24 | - Improved UI 25 | - Added grid actions 26 | - Added icons in menu 27 | - Added search in grid 28 | - Grid sorting 29 | - Enable and disable actions -------------------------------------------------------------------------------- /core/components/modextra/docs/license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | -------------------------- 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | License is intended to guarantee your freedom to share and change free 17 | software--to make sure the software is free for all its users. This 18 | General Public License applies to most of the Free Software 19 | Foundation's software and to any other program whose authors commit to 20 | using it. (Some other Free Software Foundation software is covered by 21 | the GNU Library General Public License instead.) You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if you 34 | distribute copies of the software, or if you modify it. 35 | 36 | For example, if you distribute copies of such a program, whether 37 | gratis or for a fee, you must give the recipients all the rights that 38 | you have. You must make sure that they, too, receive or can get the 39 | source code. And you must show them these terms so they know their 40 | rights. 41 | 42 | We protect your rights with two steps: (1) copyright the software, and 43 | (2) offer you this license which gives you legal permission to copy, 44 | distribute and/or modify the software. 45 | 46 | Also, for each author's protection and ours, we want to make certain 47 | that everyone understands that there is no warranty for this free 48 | software. If the software is modified by someone else and passed on, we 49 | want its recipients to know that what they have is not the original, so 50 | that any problems introduced by others will not reflect on the original 51 | authors' reputations. 52 | 53 | Finally, any free program is threatened constantly by software 54 | patents. We wish to avoid the danger that redistributors of a free 55 | program will individually obtain patent licenses, in effect making the 56 | program proprietary. To prevent this, we have made it clear that any 57 | patent must be licensed for everyone's free use or not licensed at all. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | 63 | GNU GENERAL PUBLIC LICENSE 64 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 65 | --------------------------------------------------------------- 66 | 67 | 0. This License applies to any program or other work which contains 68 | a notice placed by the copyright holder saying it may be distributed 69 | under the terms of this General Public License. The "Program", below, 70 | refers to any such program or work, and a "work based on the Program" 71 | means either the Program or any derivative work under copyright law: 72 | that is to say, a work containing the Program or a portion of it, 73 | either verbatim or with modifications and/or translated into another 74 | language. (Hereinafter, translation is included without limitation in 75 | the term "modification".) Each licensee is addressed as "you". 76 | 77 | Activities other than copying, distribution and modification are not 78 | covered by this License; they are outside its scope. The act of 79 | running the Program is not restricted, and the output from the Program 80 | is covered only if its contents constitute a work based on the 81 | Program (independent of having been made by running the Program). 82 | Whether that is true depends on what the Program does. 83 | 84 | 1. You may copy and distribute verbatim copies of the Program's 85 | source code as you receive it, in any medium, provided that you 86 | conspicuously and appropriately publish on each copy an appropriate 87 | copyright notice and disclaimer of warranty; keep intact all the 88 | notices that refer to this License and to the absence of any warranty; 89 | and give any other recipients of the Program a copy of this License 90 | along with the Program. 91 | 92 | You may charge a fee for the physical act of transferring a copy, and 93 | you may at your option offer warranty protection in exchange for a fee. 94 | 95 | 2. You may modify your copy or copies of the Program or any portion 96 | of it, thus forming a work based on the Program, and copy and 97 | distribute such modifications or work under the terms of Section 1 98 | above, provided that you also meet all of these conditions: 99 | 100 | a) You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | b) You must cause any work that you distribute or publish, that in 104 | whole or in part contains or is derived from the Program or any 105 | part thereof, to be licensed as a whole at no charge to all third 106 | parties under the terms of this License. 107 | 108 | c) If the modified program normally reads commands interactively 109 | when run, you must cause it, when started running for such 110 | interactive use in the most ordinary way, to print or display an 111 | announcement including an appropriate copyright notice and a 112 | notice that there is no warranty (or else, saying that you provide 113 | a warranty) and that users may redistribute the program under 114 | these conditions, and telling the user how to view a copy of this 115 | License. (Exception: if the Program itself is interactive but 116 | does not normally print such an announcement, your work based on 117 | the Program is not required to print an announcement.) 118 | 119 | These requirements apply to the modified work as a whole. If 120 | identifiable sections of that work are not derived from the Program, 121 | and can be reasonably considered independent and separate works in 122 | themselves, then this License, and its terms, do not apply to those 123 | sections when you distribute them as separate works. But when you 124 | distribute the same sections as part of a whole which is a work based 125 | on the Program, the distribution of the whole must be on the terms of 126 | this License, whose permissions for other licensees extend to the 127 | entire whole, and thus to each and every part regardless of who wrote it. 128 | 129 | Thus, it is not the intent of this section to claim rights or contest 130 | your rights to work written entirely by you; rather, the intent is to 131 | exercise the right to control the distribution of derivative or 132 | collective works based on the Program. 133 | 134 | In addition, mere aggregation of another work not based on the Program 135 | with the Program (or with a work based on the Program) on a volume of 136 | a storage or distribution medium does not bring the other work under 137 | the scope of this License. 138 | 139 | 3. You may copy and distribute the Program (or a work based on it, 140 | under Section 2) in object code or executable form under the terms of 141 | Sections 1 and 2 above provided that you also do one of the following: 142 | 143 | a) Accompany it with the complete corresponding machine-readable 144 | source code, which must be distributed under the terms of Sections 145 | 1 and 2 above on a medium customarily used for software interchange; or, 146 | 147 | b) Accompany it with a written offer, valid for at least three 148 | years, to give any third party, for a charge no more than your 149 | cost of physically performing source distribution, a complete 150 | machine-readable copy of the corresponding source code, to be 151 | distributed under the terms of Sections 1 and 2 above on a medium 152 | customarily used for software interchange; or, 153 | 154 | c) Accompany it with the information you received as to the offer 155 | to distribute corresponding source code. (This alternative is 156 | allowed only for noncommercial distribution and only if you 157 | received the program in object code or executable form with such 158 | an offer, in accord with Subsection b above.) 159 | 160 | The source code for a work means the preferred form of the work for 161 | making modifications to it. For an executable work, complete source 162 | code means all the source code for all modules it contains, plus any 163 | associated interface definition files, plus the scripts used to 164 | control compilation and installation of the executable. However, as a 165 | special exception, the source code distributed need not include 166 | anything that is normally distributed (in either source or binary 167 | form) with the major components (compiler, kernel, and so on) of the 168 | operating system on which the executable runs, unless that component 169 | itself accompanies the executable. 170 | 171 | If distribution of executable or object code is made by offering 172 | access to copy from a designated place, then offering equivalent 173 | access to copy the source code from the same place counts as 174 | distribution of the source code, even though third parties are not 175 | compelled to copy the source along with the object code. 176 | 177 | 4. You may not copy, modify, sublicense, or distribute the Program 178 | except as expressly provided under this License. Any attempt 179 | otherwise to copy, modify, sublicense or distribute the Program is 180 | void, and will automatically terminate your rights under this License. 181 | However, parties who have received copies, or rights, from you under 182 | this License will not have their licenses terminated so long as such 183 | parties remain in full compliance. 184 | 185 | 5. You are not required to accept this License, since you have not 186 | signed it. However, nothing else grants you permission to modify or 187 | distribute the Program or its derivative works. These actions are 188 | prohibited by law if you do not accept this License. Therefore, by 189 | modifying or distributing the Program (or any work based on the 190 | Program), you indicate your acceptance of this License to do so, and 191 | all its terms and conditions for copying, distributing or modifying 192 | the Program or works based on it. 193 | 194 | 6. Each time you redistribute the Program (or any work based on the 195 | Program), the recipient automatically receives a license from the 196 | original licensor to copy, distribute or modify the Program subject to 197 | these terms and conditions. You may not impose any further 198 | restrictions on the recipients' exercise of the rights granted herein. 199 | You are not responsible for enforcing compliance by third parties to 200 | this License. 201 | 202 | 7. If, as a consequence of a court judgment or allegation of patent 203 | infringement or for any other reason (not limited to patent issues), 204 | conditions are imposed on you (whether by court order, agreement or 205 | otherwise) that contradict the conditions of this License, they do not 206 | excuse you from the conditions of this License. If you cannot 207 | distribute so as to satisfy simultaneously your obligations under this 208 | License and any other pertinent obligations, then as a consequence you 209 | may not distribute the Program at all. For example, if a patent 210 | license would not permit royalty-free redistribution of the Program by 211 | all those who receive copies directly or indirectly through you, then 212 | the only way you could satisfy both it and this License would be to 213 | refrain entirely from distribution of the Program. 214 | 215 | If any portion of this section is held invalid or unenforceable under 216 | any particular circumstance, the balance of the section is intended to 217 | apply and the section as a whole is intended to apply in other 218 | circumstances. 219 | 220 | It is not the purpose of this section to induce you to infringe any 221 | patents or other property right claims or to contest validity of any 222 | such claims; this section has the sole purpose of protecting the 223 | integrity of the free software distribution system, which is 224 | implemented by public license practices. Many people have made 225 | generous contributions to the wide range of software distributed 226 | through that system in reliance on consistent application of that 227 | system; it is up to the author/donor to decide if he or she is willing 228 | to distribute software through any other system and a licensee cannot 229 | impose that choice. 230 | 231 | This section is intended to make thoroughly clear what is believed to 232 | be a consequence of the rest of this License. 233 | 234 | 8. If the distribution and/or use of the Program is restricted in 235 | certain countries either by patents or by copyrighted interfaces, the 236 | original copyright holder who places the Program under this License 237 | may add an explicit geographical distribution limitation excluding 238 | those countries, so that distribution is permitted only in or among 239 | countries not thus excluded. In such case, this License incorporates 240 | the limitation as if written in the body of this License. 241 | 242 | 9. The Free Software Foundation may publish revised and/or new versions 243 | of the General Public License from time to time. Such new versions will 244 | be similar in spirit to the present version, but may differ in detail to 245 | address new problems or concerns. 246 | 247 | Each version is given a distinguishing version number. If the Program 248 | specifies a version number of this License which applies to it and "any 249 | later version", you have the option of following the terms and conditions 250 | either of that version or of any later version published by the Free 251 | Software Foundation. If the Program does not specify a version number of 252 | this License, you may choose any version ever published by the Free Software 253 | Foundation. 254 | 255 | 10. If you wish to incorporate parts of the Program into other free 256 | programs whose distribution conditions are different, write to the author 257 | to ask for permission. For software which is copyrighted by the Free 258 | Software Foundation, write to the Free Software Foundation; we sometimes 259 | make exceptions for this. Our decision will be guided by the two goals 260 | of preserving the free status of all derivatives of our free software and 261 | of promoting the sharing and reuse of software generally. 262 | 263 | NO WARRANTY 264 | ----------- 265 | 266 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 267 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 268 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 269 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 270 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 271 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 272 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 273 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 274 | REPAIR OR CORRECTION. 275 | 276 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 277 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 278 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 279 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 280 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 281 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 282 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 283 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 284 | POSSIBILITY OF SUCH DAMAGES. 285 | 286 | --------------------------- 287 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /core/components/modextra/docs/readme.txt: -------------------------------------------------------------------------------- 1 | -------------------- 2 | modExtra 3 | -------------------- 4 | Author: John Doe 5 | -------------------- 6 | 7 | A basic Extra for MODx Revolution. -------------------------------------------------------------------------------- /core/components/modextra/elements/chunks/item.tpl: -------------------------------------------------------------------------------- 1 |

    2 | [[+name]] - 3 | [[+description]] 4 |

    -------------------------------------------------------------------------------- /core/components/modextra/elements/chunks/office.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | -------------------------------------------------------------------------------- /core/components/modextra/elements/plugins/modextra.php: -------------------------------------------------------------------------------- 1 | event->name) { 4 | 5 | } -------------------------------------------------------------------------------- /core/components/modextra/elements/snippets/modextra.php: -------------------------------------------------------------------------------- 1 | getService('modExtra', 'modExtra', MODX_CORE_PATH . 'components/modextra/model/', $scriptProperties); 6 | if (!$modExtra) { 7 | return 'Could not load modExtra class!'; 8 | } 9 | 10 | // Do your snippet code here. This demo grabs 5 items from our custom table. 11 | $tpl = $modx->getOption('tpl', $scriptProperties, 'Item'); 12 | $sortby = $modx->getOption('sortby', $scriptProperties, 'name'); 13 | $sortdir = $modx->getOption('sortbir', $scriptProperties, 'ASC'); 14 | $limit = $modx->getOption('limit', $scriptProperties, 5); 15 | $outputSeparator = $modx->getOption('outputSeparator', $scriptProperties, "\n"); 16 | $toPlaceholder = $modx->getOption('toPlaceholder', $scriptProperties, false); 17 | 18 | // Build query 19 | $c = $modx->newQuery('modExtraItem'); 20 | $c->sortby($sortby, $sortdir); 21 | $c->where(['active' => 1]); 22 | $c->limit($limit); 23 | $items = $modx->getIterator('modExtraItem', $c); 24 | 25 | // Iterate through items 26 | $list = []; 27 | /** @var modExtraItem $item */ 28 | foreach ($items as $item) { 29 | $list[] = $modx->getChunk($tpl, $item->toArray()); 30 | } 31 | 32 | // Output 33 | $output = implode($outputSeparator, $list); 34 | if (!empty($toPlaceholder)) { 35 | // If using a placeholder, output nothing and set output to specified placeholder 36 | $modx->setPlaceholder($toPlaceholder, $output); 37 | 38 | return ''; 39 | } 40 | // By default just return output 41 | return $output; 42 | -------------------------------------------------------------------------------- /core/components/modextra/elements/templates/base.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [[*pagetitle]] / [[++site_name]] 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    Just an example

    12 | 13 | 14 | -------------------------------------------------------------------------------- /core/components/modextra/lexicon/en/default.inc.php: -------------------------------------------------------------------------------- 1 | modx =& $modx; 16 | $corePath = MODX_CORE_PATH . 'components/modextra/'; 17 | $assetsUrl = MODX_ASSETS_URL . 'components/modextra/'; 18 | 19 | $this->config = array_merge([ 20 | 'corePath' => $corePath, 21 | 'modelPath' => $corePath . 'model/', 22 | 'processorsPath' => $corePath . 'processors/', 23 | 24 | 'connectorUrl' => $assetsUrl . 'connector.php', 25 | 'assetsUrl' => $assetsUrl, 26 | 'cssUrl' => $assetsUrl . 'css/', 27 | 'jsUrl' => $assetsUrl . 'js/', 28 | ], $config); 29 | 30 | $this->modx->addPackage('modextra', $this->config['modelPath']); 31 | $this->modx->lexicon->load('modextra:default'); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /core/components/modextra/model/modextra/metadata.mysql.php: -------------------------------------------------------------------------------- 1 | 5 | array ( 6 | 0 => 'modExtraItem', 7 | ), 8 | ); -------------------------------------------------------------------------------- /core/components/modextra/model/modextra/modextraitem.class.php: -------------------------------------------------------------------------------- 1 | 'modextra', 4 | 'version' => '1.1', 5 | 'table' => 'modextra_items', 6 | 'extends' => 'xPDOSimpleObject', 7 | 'tableMeta' => 8 | array ( 9 | 'engine' => 'InnoDB', 10 | ), 11 | 'fields' => 12 | array ( 13 | 'name' => '', 14 | 'description' => '', 15 | 'active' => 1, 16 | ), 17 | 'fieldMeta' => 18 | array ( 19 | 'name' => 20 | array ( 21 | 'dbtype' => 'varchar', 22 | 'precision' => '100', 23 | 'phptype' => 'string', 24 | 'null' => false, 25 | 'default' => '', 26 | ), 27 | 'description' => 28 | array ( 29 | 'dbtype' => 'text', 30 | 'phptype' => 'string', 31 | 'null' => true, 32 | 'default' => '', 33 | ), 34 | 'active' => 35 | array ( 36 | 'dbtype' => 'tinyint', 37 | 'precision' => '1', 38 | 'phptype' => 'boolean', 39 | 'null' => true, 40 | 'default' => 1, 41 | ), 42 | ), 43 | 'indexes' => 44 | array ( 45 | 'name' => 46 | array ( 47 | 'alias' => 'name', 48 | 'primary' => false, 49 | 'unique' => false, 50 | 'type' => 'BTREE', 51 | 'columns' => 52 | array ( 53 | 'name' => 54 | array ( 55 | 'length' => '', 56 | 'collation' => 'A', 57 | 'null' => false, 58 | ), 59 | ), 60 | ), 61 | 'active' => 62 | array ( 63 | 'alias' => 'active', 64 | 'primary' => false, 65 | 'unique' => false, 66 | 'type' => 'BTREE', 67 | 'columns' => 68 | array ( 69 | 'active' => 70 | array ( 71 | 'length' => '', 72 | 'collation' => 'A', 73 | 'null' => false, 74 | ), 75 | ), 76 | ), 77 | ), 78 | ); 79 | -------------------------------------------------------------------------------- /core/components/modextra/model/schema/modextra.mysql.schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/create.class.php: -------------------------------------------------------------------------------- 1 | getProperty('name')); 17 | if (empty($name)) { 18 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name')); 19 | } elseif ($this->modx->getCount($this->classKey, ['name' => $name])) { 20 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae')); 21 | } 22 | 23 | return parent::beforeSet(); 24 | } 25 | 26 | } 27 | 28 | return 'modExtraItemCreateProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/disable.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->set('active', false); 32 | $object->save(); 33 | } 34 | 35 | return $this->success(); 36 | } 37 | 38 | } 39 | 40 | return 'modExtraItemDisableProcessor'; 41 | -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/enable.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->set('active', true); 32 | $object->save(); 33 | } 34 | 35 | return $this->success(); 36 | } 37 | 38 | } 39 | 40 | return 'modExtraItemEnableProcessor'; 41 | -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/get.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 20 | return $this->failure($this->modx->lexicon('access_denied')); 21 | } 22 | 23 | return parent::process(); 24 | } 25 | 26 | } 27 | 28 | return 'modExtraItemGetProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/getlist.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 21 | return $this->modx->lexicon('access_denied'); 22 | } 23 | 24 | return true; 25 | } 26 | 27 | 28 | /** 29 | * @param xPDOQuery $c 30 | * 31 | * @return xPDOQuery 32 | */ 33 | public function prepareQueryBeforeCount(xPDOQuery $c) 34 | { 35 | $query = trim($this->getProperty('query')); 36 | if ($query) { 37 | $c->where([ 38 | 'name:LIKE' => "%{$query}%", 39 | 'OR:description:LIKE' => "%{$query}%", 40 | ]); 41 | } 42 | 43 | return $c; 44 | } 45 | 46 | 47 | /** 48 | * @param xPDOObject $object 49 | * 50 | * @return array 51 | */ 52 | public function prepareRow(xPDOObject $object) 53 | { 54 | $array = $object->toArray(); 55 | $array['actions'] = []; 56 | 57 | // Edit 58 | $array['actions'][] = [ 59 | 'cls' => '', 60 | 'icon' => 'icon icon-edit', 61 | 'title' => $this->modx->lexicon('modextra_item_update'), 62 | //'multiple' => $this->modx->lexicon('modextra_items_update'), 63 | 'action' => 'updateItem', 64 | 'button' => true, 65 | 'menu' => true, 66 | ]; 67 | 68 | if (!$array['active']) { 69 | $array['actions'][] = [ 70 | 'cls' => '', 71 | 'icon' => 'icon icon-power-off action-green', 72 | 'title' => $this->modx->lexicon('modextra_item_enable'), 73 | 'multiple' => $this->modx->lexicon('modextra_items_enable'), 74 | 'action' => 'enableItem', 75 | 'button' => true, 76 | 'menu' => true, 77 | ]; 78 | } else { 79 | $array['actions'][] = [ 80 | 'cls' => '', 81 | 'icon' => 'icon icon-power-off action-gray', 82 | 'title' => $this->modx->lexicon('modextra_item_disable'), 83 | 'multiple' => $this->modx->lexicon('modextra_items_disable'), 84 | 'action' => 'disableItem', 85 | 'button' => true, 86 | 'menu' => true, 87 | ]; 88 | } 89 | 90 | // Remove 91 | $array['actions'][] = [ 92 | 'cls' => '', 93 | 'icon' => 'icon icon-trash-o action-red', 94 | 'title' => $this->modx->lexicon('modextra_item_remove'), 95 | 'multiple' => $this->modx->lexicon('modextra_items_remove'), 96 | 'action' => 'removeItem', 97 | 'button' => true, 98 | 'menu' => true, 99 | ]; 100 | 101 | return $array; 102 | } 103 | 104 | } 105 | 106 | return 'modExtraItemGetListProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/remove.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->remove(); 32 | } 33 | 34 | return $this->success(); 35 | } 36 | 37 | } 38 | 39 | return 'modExtraItemRemoveProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/mgr/item/update.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 20 | return $this->modx->lexicon('access_denied'); 21 | } 22 | 23 | return true; 24 | } 25 | 26 | 27 | /** 28 | * @return bool 29 | */ 30 | public function beforeSet() 31 | { 32 | $id = (int)$this->getProperty('id'); 33 | $name = trim($this->getProperty('name')); 34 | if (empty($id)) { 35 | return $this->modx->lexicon('modextra_item_err_ns'); 36 | } 37 | 38 | if (empty($name)) { 39 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name')); 40 | } elseif ($this->modx->getCount($this->classKey, ['name' => $name, 'id:!=' => $id])) { 41 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae')); 42 | } 43 | 44 | return parent::beforeSet(); 45 | } 46 | } 47 | 48 | return 'modExtraItemUpdateProcessor'; 49 | -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/create.class.php: -------------------------------------------------------------------------------- 1 | getProperty('name')); 17 | if (empty($name)) { 18 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name')); 19 | } elseif ($this->modx->getCount($this->classKey, ['name' => $name])) { 20 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae')); 21 | } 22 | 23 | return parent::beforeSet(); 24 | } 25 | 26 | } 27 | 28 | return 'modExtraOfficeItemCreateProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/disable.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->set('active', false); 32 | $object->save(); 33 | } 34 | 35 | return $this->success(); 36 | } 37 | 38 | } 39 | 40 | return 'modExtraOfficeItemDisableProcessor'; 41 | -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/enable.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->set('active', true); 32 | $object->save(); 33 | } 34 | 35 | return $this->success(); 36 | } 37 | 38 | } 39 | 40 | return 'modExtraOfficeItemEnableProcessor'; 41 | -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/get.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 20 | return $this->failure($this->modx->lexicon('access_denied')); 21 | } 22 | 23 | return parent::process(); 24 | } 25 | 26 | } 27 | 28 | return 'modExtraOfficeItemGetProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/getlist.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 21 | return $this->modx->lexicon('access_denied'); 22 | } 23 | 24 | return true; 25 | } 26 | 27 | 28 | /** 29 | * @param xPDOQuery $c 30 | * 31 | * @return xPDOQuery 32 | */ 33 | public function prepareQueryBeforeCount(xPDOQuery $c) 34 | { 35 | $query = trim($this->getProperty('query')); 36 | if ($query) { 37 | $c->where([ 38 | 'name:LIKE' => "%{$query}%", 39 | 'OR:description:LIKE' => "%{$query}%", 40 | ]); 41 | } 42 | 43 | return $c; 44 | } 45 | 46 | 47 | /** 48 | * @param xPDOObject $object 49 | * 50 | * @return array 51 | */ 52 | public function prepareRow(xPDOObject $object) 53 | { 54 | $array = $object->toArray(); 55 | $array['actions'] = []; 56 | 57 | // Edit 58 | $array['actions'][] = [ 59 | 'cls' => '', 60 | 'icon' => 'fa fa-edit', 61 | 'title' => $this->modx->lexicon('modextra_item_update'), 62 | //'multiple' => $this->modx->lexicon('modextra_items_update'), 63 | 'action' => 'updateItem', 64 | 'button' => true, 65 | 'menu' => true, 66 | ]; 67 | 68 | if (!$array['active']) { 69 | $array['actions'][] = [ 70 | 'cls' => '', 71 | 'icon' => 'fa fa-power-off action-green', 72 | 'title' => $this->modx->lexicon('modextra_item_enable'), 73 | 'multiple' => $this->modx->lexicon('modextra_items_enable'), 74 | 'action' => 'enableItem', 75 | 'button' => true, 76 | 'menu' => true, 77 | ]; 78 | } else { 79 | $array['actions'][] = [ 80 | 'cls' => '', 81 | 'icon' => 'fa fa-power-off action-gray', 82 | 'title' => $this->modx->lexicon('modextra_item_disable'), 83 | 'multiple' => $this->modx->lexicon('modextra_items_disable'), 84 | 'action' => 'disableItem', 85 | 'button' => true, 86 | 'menu' => true, 87 | ]; 88 | } 89 | 90 | // Remove 91 | $array['actions'][] = [ 92 | 'cls' => '', 93 | 'icon' => 'fa fa-trash-o action-red', 94 | 'title' => $this->modx->lexicon('modextra_item_remove'), 95 | 'multiple' => $this->modx->lexicon('modextra_items_remove'), 96 | 'action' => 'removeItem', 97 | 'button' => true, 98 | 'menu' => true, 99 | ]; 100 | 101 | return $array; 102 | } 103 | 104 | } 105 | 106 | return 'modExtraOfficeItemGetListProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/remove.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 17 | return $this->failure($this->modx->lexicon('access_denied')); 18 | } 19 | 20 | $ids = $this->modx->fromJSON($this->getProperty('ids')); 21 | if (empty($ids)) { 22 | return $this->failure($this->modx->lexicon('modextra_item_err_ns')); 23 | } 24 | 25 | foreach ($ids as $id) { 26 | /** @var modExtraItem $object */ 27 | if (!$object = $this->modx->getObject($this->classKey, $id)) { 28 | return $this->failure($this->modx->lexicon('modextra_item_err_nf')); 29 | } 30 | 31 | $object->remove(); 32 | } 33 | 34 | return $this->success(); 35 | } 36 | 37 | } 38 | 39 | return 'modExtraOfficeItemRemoveProcessor'; -------------------------------------------------------------------------------- /core/components/modextra/processors/office/item/update.class.php: -------------------------------------------------------------------------------- 1 | checkPermissions()) { 20 | return $this->modx->lexicon('access_denied'); 21 | } 22 | 23 | return true; 24 | } 25 | 26 | 27 | /** 28 | * @return bool 29 | */ 30 | public function beforeSet() 31 | { 32 | $id = (int)$this->getProperty('id'); 33 | $name = trim($this->getProperty('name')); 34 | if (empty($id)) { 35 | return $this->modx->lexicon('modextra_item_err_ns'); 36 | } 37 | 38 | if (empty($name)) { 39 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_name')); 40 | } elseif ($this->modx->getCount($this->classKey, ['name' => $name, 'id:!=' => $id])) { 41 | $this->modx->error->addField('name', $this->modx->lexicon('modextra_item_err_ae')); 42 | } 43 | 44 | return parent::beforeSet(); 45 | } 46 | } 47 | 48 | return 'modExtraOfficeItemUpdateProcessor'; 49 | -------------------------------------------------------------------------------- /rename_it.php: -------------------------------------------------------------------------------- 1 |