├── doc
├── use
│ ├── push.md
│ ├── workspace
│ │ └── gc.md
│ ├── extract
│ │ ├── tpl
│ │ │ └── resource_children.md
│ │ └── tpl.md
│ ├── packages
│ │ └── gc.md
│ ├── server.md
│ ├── user-create.md
│ ├── inject.md
│ ├── profile.md
│ └── extract.md
├── contribute.md
├── extend
│ ├── custom-actions.md
│ └── custom-extract-tpls.md
├── install
│ ├── phar.md
│ ├── releases.md
│ └── git-clone.md
└── index.md
├── bin
├── compile
├── server
├── endpoint
├── teleport
├── teleport.php
├── compile.php
├── server.php
└── endpoint.php
├── profile
└── .gitignore
├── vendor
└── .gitignore
├── workspace
└── .gitignore
├── tpl
├── scripts
│ ├── resolve
│ │ ├── modtransportpackage
│ │ │ └── install.php
│ │ └── modsystemsetting
│ │ │ └── extension_packages.php
│ ├── remove.object.php
│ ├── truncate.tables.php
│ ├── exec.xpdoquery.php
│ └── extract
│ │ ├── modcategory.php
│ │ ├── modaction.php
│ │ ├── modaccess.php
│ │ ├── modmenu.php
│ │ ├── basepathassets.php
│ │ ├── modresourcechildren.php
│ │ ├── modaccesspolicytemplategroup.php
│ │ ├── modtransportpackage.php
│ │ ├── userextensionpackages.php
│ │ └── changeset.php
├── changeset.tpl.json
├── packages.tpl.json
├── settings.tpl.json
├── template.tpl.json
├── user.tpl.json
├── users.tpl.json
├── form_customizations.tpl.json
├── resources.tpl.json
└── resource_children.tpl.json
├── tests
├── Teleport
│ ├── TestCase.php
│ └── Test
│ │ ├── Request
│ │ ├── CLIRequestTest.php
│ │ ├── MockRequest.php
│ │ ├── RequestTestCase.php
│ │ └── AbstractRequestTest.php
│ │ ├── Action
│ │ ├── MockAction.php
│ │ ├── ActionTestCase.php
│ │ └── AbstractActionTest.php
│ │ ├── ConfigTest.php
│ │ └── TeleportTest.php
├── bootstrap.php
├── complete.phpunit.xml
└── teleport.php
├── src
├── Teleport
│ ├── InvalidMODXException.php
│ ├── InvalidProfileException.php
│ ├── ConfigException.php
│ ├── Action
│ │ ├── Version.php
│ │ ├── ActionInterface.php
│ │ ├── Help.php
│ │ ├── Pull.php
│ │ ├── ActionException.php
│ │ ├── Push.php
│ │ ├── Workspace
│ │ │ └── GC.php
│ │ ├── UserCreate.php
│ │ ├── Inject.php
│ │ ├── Profile.php
│ │ ├── Packages
│ │ │ └── GC.php
│ │ └── Action.php
│ ├── Request
│ │ ├── RequestException.php
│ │ ├── InvalidRequestException.php
│ │ ├── RequestInterface.php
│ │ ├── APIRequest.php
│ │ ├── CLIRequest.php
│ │ └── Request.php
│ ├── Transport
│ │ ├── xPDOObjectVehicle.php
│ │ ├── FileVehicle.php
│ │ ├── MySQLVehicle.php
│ │ ├── xPDOCollectionVehicle.php
│ │ └── Transport.php
│ ├── Parser
│ │ └── Parser.php
│ ├── Composer.php
│ ├── Config.php
│ └── Beam
│ │ ├── HttpServer.php
│ │ └── Endpoint.php
└── bootstrap.php
├── .gitignore
├── mkdocs.yml
├── LICENSE
├── composer.json
├── CHANGELOG.md
└── README.md
/doc/use/push.md:
--------------------------------------------------------------------------------
1 | # Push
2 |
--------------------------------------------------------------------------------
/doc/contribute.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
--------------------------------------------------------------------------------
/doc/extend/custom-actions.md:
--------------------------------------------------------------------------------
1 | # Custom Actions
2 |
--------------------------------------------------------------------------------
/bin/compile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | install();
10 | }
11 | return $result;
12 |
--------------------------------------------------------------------------------
/tpl/scripts/remove.object.php:
--------------------------------------------------------------------------------
1 | xpdo->getObject($object['class'], $object['pk']);
9 | $result = $instance ? $instance->remove() : true;
10 | }
11 | return $result;
12 |
--------------------------------------------------------------------------------
/tests/Teleport/TestCase.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Teleport;
12 |
13 |
14 | class InvalidMODXException extends \Exception
15 | {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/Teleport/InvalidProfileException.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Teleport;
12 |
13 |
14 | class InvalidProfileException extends \Exception
15 | {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | add('Teleport\\Test', __DIR__);
15 |
16 | require __DIR__.'/Teleport/TestCase.php';
17 |
--------------------------------------------------------------------------------
/tpl/scripts/truncate.tables.php:
--------------------------------------------------------------------------------
1 | xpdo->exec('TRUNCATE TABLE ' . $transport->xpdo->getTableName($class));
7 | }
8 | }
9 | $transport->xpdo->log(xPDO::LOG_LEVEL_INFO, "Table truncation results: " . print_r($results, true));
10 | return !array_search(false, $results, true);
11 |
--------------------------------------------------------------------------------
/doc/use/workspace/gc.md:
--------------------------------------------------------------------------------
1 | # Workspace/GC
2 |
3 |
4 | ## Workspace Garbage Collection
5 |
6 | You can empty the contents of the Teleport workspace with the command:
7 |
8 | php teleport.phar --action=Workspace/GC
9 |
10 | *WARNING: this will delete __everything__ in the `workspace/` directory!*
11 |
12 |
13 | ## Garbage Collection for the Teleport Workspace
14 |
15 | ### Required Arguments
16 |
17 | None required.
18 |
19 | ### Optional Arguments
20 |
21 | No optional arguments.
22 |
--------------------------------------------------------------------------------
/src/Teleport/ConfigException.php:
--------------------------------------------------------------------------------
1 | request->log("Teleport version " . Teleport::VERSION . " " . Teleport::RELEASE_DATE, false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Request/CLIRequestTest.php:
--------------------------------------------------------------------------------
1 | fixture = new CLIRequest;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tpl/changeset.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "changeset",
3 | "vehicles": [
4 | {
5 | "vehicle_class": "xPDOObjectVehicle",
6 | "object": {
7 | "class": "xPDOObject",
8 | "criteria": [],
9 | "script": "extract\/changeset.php",
10 | "changeset": "{+changeSet}",
11 | "remove_read": "{+removeRead}"
12 | },
13 | "attributes": {
14 | "preserve_keys": true,
15 | "update_object": true
16 | }
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/doc/use/extract/tpl/resource_children.md:
--------------------------------------------------------------------------------
1 | # resource_children.tpl.json
2 |
3 | This tpl extracts all Children of a specified parent Resource recursively.
4 |
5 | The following example command would extract all Children of the Resource with an id of 2.
6 |
7 | php teleport.phar --action=Extract --profile=profile/mysite.profile.json --tpl=phar://teleport.phar/tpl/resource_children.tpl.json --parent=2
8 |
9 | This does not include the parent Resource in the Extract.
10 |
11 |
12 | ## Arguments
13 |
14 | ### Required
15 |
16 | * `--parent=id` - Indicates the id of the parent Resource to extract children from.
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore config.php
2 | /config.php
3 |
4 | # ignore all .htaccess
5 | .htaccess
6 |
7 | # ignore all IIS files
8 | web.config
9 |
10 | # ignore all config.core.php
11 | config.core.php
12 |
13 | # ignore various IDE project files and folders
14 | .idea/
15 | .buildpath
16 | .project
17 | .settings/
18 | nbproject/
19 |
20 | # ignore PHP files beginning with test-
21 | test-*.php
22 | test-*.json
23 |
24 | # ignore mac files
25 | .DS_Store
26 |
27 | # ignore phar archives
28 | *.phar
29 |
30 | # ignore composer.lock
31 | composer.lock
32 |
33 | # ignore /site/ for generated documentation
34 | /site/
35 |
--------------------------------------------------------------------------------
/doc/use/packages/gc.md:
--------------------------------------------------------------------------------
1 | # Packages/GC
2 |
3 |
4 | ## Package Garbage Collection
5 |
6 | You can remove outdated packages from a profiled MODX deployment using the following command:
7 |
8 | php teleport.phar --action=Packages/GC --profile=profile/mysite.profile.json
9 |
10 |
11 | ## Garbage Collection for MODX Package Management
12 |
13 | ### Required Arguments
14 |
15 | * `--profile=path` - A valid stream path to a Teleport [Profile](../profile.md) defining the MODX instance this action is to be performed against.
16 |
17 | ### Optional Arguments
18 |
19 | * `--preserveZip` - Prevents removal of the zip files for the outdated packages, removing only the extracted package directories and database records.
20 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Action/MockAction.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | ./Teleport/
17 |
18 |
19 |
20 |
21 |
22 | ../src/Teleport/
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tpl/packages.tpl.json:
--------------------------------------------------------------------------------
1 | {"name":"packages", "vehicles":[
2 | {
3 | "vehicle_class":"xPDOObjectVehicle",
4 | "object":{
5 | "class":"transport.modTransportProvider",
6 | "criteria":["1 = 1"],
7 | "package":"modx"
8 | },
9 | "attributes":{
10 | "preserve_keys":true,
11 | "update_object":true
12 | }
13 | },
14 | {
15 | "vehicle_class":"xPDOObjectVehicle",
16 | "object":{
17 | "class":"transport.modTransportPackage",
18 | "criteria":["1 = 1"],
19 | "script":"extract\/modtransportpackage.php",
20 | "package":"modx",
21 | "install":true
22 | },
23 | "attributes":{
24 | "preserve_keys":true,
25 | "update_object":true
26 | }
27 | }
28 | ]}
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Teleport for MODX
2 | docs_dir: doc
3 | nav:
4 | - Introduction: index.md
5 | - Installation:
6 | - Install from Release: install/releases.md
7 | - Install from Git: install/git-clone.md
8 | - Install Phar: install/phar.md
9 | - Actions:
10 | - Profile: use/profile.md
11 | - Extract: use/extract.md
12 | - Inject: use/inject.md
13 | - Push: use/push.md
14 | - User Create: use/user-create.md
15 | - Package Garbage Collection: use/packages/gc.md
16 | - Workspace Garbage Collection: use/workspace/gc.md
17 | - Extract Tpls:
18 | - Intro to Extract Tpls: use/extract/tpl.md
19 | - resource_children.tpl: use/extract/tpl/resource_children.md
20 | - Server: use/server.md
21 | - Extend:
22 | - Custom Actions: extend/custom-actions.md
23 | - Custom Extract Tpls: extend/custom-extract-tpls.md
24 | - Contributing: contribute.md
25 | theme: readthedocs
26 | repo_name: modxcms/teleport
27 | repo_url: https://github.com/modxcms/teleport/
28 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Action/ActionTestCase.php:
--------------------------------------------------------------------------------
1 | fixture->getMODX();
24 | }
25 |
26 | public function testGetRequest()
27 | {
28 | $this->fixture->getRequest();
29 | }
30 |
31 | public function testProcess()
32 | {
33 | $this->fixture->process();
34 | }
35 |
36 | public function testValidate()
37 | {
38 | $this->fixture->validate();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tpl/settings.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "settings",
3 | "vehicles": [
4 | {
5 | "vehicle_class": "xPDOObjectVehicle",
6 | "object": {
7 | "class": "modSystemSetting",
8 | "criteria": [
9 | "1 = 1"
10 | ],
11 | "package": "modx"
12 | },
13 | "attributes": {
14 | "preserve_keys": true,
15 | "update_object": true
16 | }
17 | },
18 | {
19 | "vehicle_class": "xPDOObjectVehicle",
20 | "object": {
21 | "class": "modContextSetting",
22 | "criteria": [
23 | "1 = 1"
24 | ],
25 | "package": "modx"
26 | },
27 | "attributes": {
28 | "preserve_keys": true,
29 | "update_object": true
30 | }
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Action/AbstractActionTest.php:
--------------------------------------------------------------------------------
1 | fixture = new MockAction(new MockRequest);
24 | }
25 |
26 | public function testInstanceOf()
27 | {
28 | $this->assertInstanceOf('Teleport\\Action\\ActionInterface', $this->fixture);
29 | $this->assertInstanceOf('Teleport\\Action\\Action', $this->fixture);
30 | $this->assertInstanceOf('Teleport\\Test\\Action\\MockAction', $this->fixture);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tpl/scripts/exec.xpdoquery.php:
--------------------------------------------------------------------------------
1 | xpdo->newQuery($object['class']);
9 | $query->setClassAlias($object['criteria']['alias']);
10 | $query->query = $object['criteria']['query'];
11 | if ($query->prepare()) {
12 | $affected = $query->stmt->execute();
13 | if ($affected === false) {
14 | $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not execute PDOStatement from xPDOQuery: " . print_r($query->stmt->errorInfo(), true));
15 | }
16 | } else {
17 | $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not prepare PDOStatement from xPDOQuery: {$query->toSQL()}");
18 | }
19 | } else {
20 | $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "No valid class or criteria provided to extract script " . basename(__FILE__));
21 | }
22 | return $result;
23 |
--------------------------------------------------------------------------------
/doc/use/server.md:
--------------------------------------------------------------------------------
1 | # Teleport HTTP Server
2 |
3 | The Teleport HTTP Server is an HTTP server you can run to listen to requests for Teleport Actions on a particular port.
4 |
5 |
6 | ## Running the HTTP Server
7 |
8 | You can run the Teleport HTTP Server very easily by executing the `bin/server.php` or `bin/server` scripts included with Teleport and specifying the port you want to run the server on.
9 |
10 | bin/server 8082
11 |
12 | Or...
13 |
14 | php bin/server.php 1337 --verbose
15 |
16 | This starts the Teleport HTTP listener and allows execution of Teleport Actions over the HTTP protocol. The `--verbose` option makes Teleport output useful information to stdout from the server.
17 |
18 | ## Executing Teleport Actions on the Server
19 |
20 | Calling a Teleport Action on the server takes the form:
21 |
22 | http://hostname:port/Action?arg1=value&arg2=1&arg3=value2
23 |
24 | For example, to run an Extract:
25 |
26 | http://localhost:1337/Extract?profile=profile/test_profile.profile.json&tpl=tpl/complete.tpl.json
27 |
28 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/ConfigTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('Teleport\\Config', new Config($config));
29 | }
30 | public function providerConstruct()
31 | {
32 | return array(
33 | array(
34 | array(),
35 | ),
36 | array(
37 | array('foo' => 1, 'bar' => false),
38 | ),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modcategory.php:
--------------------------------------------------------------------------------
1 | modx->newQuery('modCategory', $criteria, false);
11 |
12 | $iterator = $this->modx->getIterator('modCategory', $query, false);
13 | foreach ($iterator as $object) {
14 | /** @var modCategory $object */
15 | modcategory_populate_category_children($object);
16 | if ($this->package->put($object, $vehicle['attributes'])) {
17 | $vehicleCount++;
18 | }
19 | }
20 |
21 | function modcategory_populate_category_children(modCategory &$object) {
22 | $children = $object->getMany('Children', null, false);
23 | if ($children) {
24 | foreach ($children as &$child) {
25 | if ($child->get('id') == $object->get('id')) continue;
26 | modcategory_populate_category_children($child);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modaction.php:
--------------------------------------------------------------------------------
1 | modx->newQuery('modAction', $criteria, false);
13 |
14 | $where = array(
15 | "NOT EXISTS (SELECT 1 FROM {$this->modx->getTableName('modMenu')} menu WHERE menu.action = modAction.id)"
16 | );
17 | $query->where($where);
18 |
19 | $iterator = $this->modx->getIterator('modAction', $query, false);
20 | foreach ($iterator as $object) {
21 | /** @var xPDOObject $object */
22 | if (!empty($graph)) {
23 | $object->getGraph($graph, $graphCriteria, false);
24 | }
25 | if ($this->package->put($object, $vehicle['attributes'])) {
26 | $vehicleCount++;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 MODX, LLC
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modaccess.php:
--------------------------------------------------------------------------------
1 | modx->newQuery($class, $criteria, false);
14 |
15 | $iterator = $this->modx->getIterator($class, $query, false);
16 | foreach ($iterator as $object) {
17 | /** @var xPDOObject $object */
18 | $principal = $this->modx->getObject($object->get('principal_class'), $object->get('principal'), false);
19 | $object->_relatedObjects['Principal'] = $principal;
20 | if (!empty($graph)) {
21 | $object->getGraph($graph, $graphCriteria, false);
22 | }
23 | if ($this->package->put($object, $vehicle['attributes'])) {
24 | $vehicleCount++;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/doc/use/user-create.md:
--------------------------------------------------------------------------------
1 | # UserCreate
2 |
3 |
4 | ## Create a MODX User Account
5 |
6 | You can create a user in a profiled MODX site using the following command:
7 |
8 | php teleport.phar --action=UserCreate --profile=profile/mysite.profile.json --username=superuser --password=password --sudo --active --fullname="Test User" --email=testuser@example.com
9 |
10 |
11 | ## The UserCreate Action
12 |
13 | ### Required Arguments
14 |
15 | * `--profile=path` - A valid stream path to a Teleport [Profile](profile.md). This defines the MODX instance the UserCreate is to be performed against.
16 | * `--username=string` - A valid MODX username.
17 | * `--email=email` - A valid email address for the user.
18 |
19 | ### Optional Arguments
20 |
21 | * `--password=string` - A valid MODX password for the user. If not specified, MODX will generate one and return the value from the command.
22 | * `--fullname='string'` - An optional full name for the user.
23 | * `--active` - Indicates if the user should be marked active when created.
24 | * `--sudo` - Indicates if the user should be created as a sudo user.
25 |
26 | _NOTE: This Action uses the security/user/create processor from the MODX site in the specified profile to create a user. It accepts any additional arguments that the processor does._
27 |
--------------------------------------------------------------------------------
/doc/install/releases.md:
--------------------------------------------------------------------------------
1 | # Teleport Source Installation via _Releases_
2 |
3 | ## Create a Working Directory for the Application
4 |
5 | First, create a directory where you will run the Teleport application. Teleport will create subdirectories for it's work when being used, and it is best if you create it's own directory where it can live in isolation. A typical location might be `~/teleport/`.
6 |
7 | ## Download a Release of Teleport
8 |
9 | Download a [release of Teleport](http://github.com/modxcms/teleport/releases) and extract it into your Teleport working directory.
10 |
11 | ## Install Dependencies with Composer
12 |
13 | You will need to install the dependencies for using and optionally for developing Teleport using [Composer](http://getcomposer.org/).
14 |
15 | Within the root of your cloned repository's working directory:
16 |
17 | composer install --dev
18 |
19 | ## Running Teleport from Source
20 |
21 | The documentation assumes you have installed the phar distribution of Teleport. Since you have chosen to install from source, when you see `php teleport.phar` in the documentation examples you should substitute `bin/teleport` to run the application instead.
22 |
23 | ## Get Started
24 |
25 | Get started using Teleport by [generating a Profile](../use/profile.md) of a local MODX installation.
26 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Request/MockRequest.php:
--------------------------------------------------------------------------------
1 | action = null;
35 | $this->arguments = array();
36 | if (!isset($args['action'])) {
37 | throw new RequestException($this, "No action argument provided");
38 | }
39 | $this->action = $args['action'];
40 | unset($args['action']);
41 | return $args;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/TeleportTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('Teleport\\Teleport', Teleport::instance());
22 | $this->assertInstanceOf('Teleport\\Teleport', Teleport::instance(array()));
23 | $this->assertInstanceOf('Teleport\\Teleport', Teleport::instance(array('foo' => 'bar')));
24 | }
25 |
26 | public function testSetConfig()
27 | {
28 | $instance = Teleport::instance();
29 | $instance->setConfig(array('foo' => 'bar'));
30 | $this->assertEquals('bar', $instance->getConfig()->get('foo'));
31 | }
32 |
33 | public function testGetConfig()
34 | {
35 | $instance = Teleport::instance(array('foo' => 'bar'));
36 | $config = $instance->getConfig();
37 | $this->assertInstanceOf('Teleport\\Config', $config);
38 | $this->assertEquals('bar', $config->get('foo'));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modmenu.php:
--------------------------------------------------------------------------------
1 | modx->newQuery('modMenu', $criteria, false);
11 |
12 | $iterator = $this->modx->getIterator('modMenu', $query, false);
13 | foreach ($iterator as $object) {
14 | /** @var modMenu $object */
15 | modmenu_populate_menu_action($object);
16 | modmenu_populate_menu_children($object);
17 | if ($this->package->put($object, $vehicle['attributes'])) {
18 | $vehicleCount++;
19 | }
20 | }
21 | function modmenu_populate_menu_action(modMenu &$object) {
22 | $action = $object->getOne('Action', null, false);
23 | if ($action) {
24 | $object->Action->getMany('Fields', null, false);
25 | }
26 | }
27 | function modmenu_populate_menu_children(modMenu &$object) {
28 | $children = $object->getMany('Children', null, false);
29 | if ($children) {
30 | foreach ($children as &$child) {
31 | if ($child->get('text') == $object->get('text')) continue;
32 | modmenu_populate_menu_action($child);
33 | modmenu_populate_menu_children($child);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Teleport/Action/ActionInterface.php:
--------------------------------------------------------------------------------
1 | request->log("Teleport version " . Teleport::VERSION . " " . Teleport::RELEASE_DATE, false);
21 | $help = <<request->log($help, false);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Pull.php:
--------------------------------------------------------------------------------
1 | overwriteTarget && file_exists($this->target)) {
33 | throw new ActionException($this, "{$this->target} exists; use --overwriteTarget to Pull anyway");
34 | }
35 | try {
36 | $pulled = copy($this->source, $this->target);
37 | if (!$pulled) {
38 | throw new ActionException($this, "copy failed");
39 | }
40 | $this->request->log("Successfully pulled {$this->source} to {$this->target}");
41 | } catch (\Exception $e) {
42 | throw new ActionException($this, "Error pulling {$this->source} to {$this->target}", $e);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Request/RequestTestCase.php:
--------------------------------------------------------------------------------
1 | fixture->addResult('result added');
24 | $result = $this->fixture->getResults();
25 | $result = array_pop($result);
26 | $this->assertEquals('result added', $result);
27 | }
28 |
29 | public function testArgs()
30 | {
31 | $this->assertEquals(array(), $this->fixture->args());
32 | }
33 |
34 | public function testGetResults()
35 | {
36 | $this->assertEquals(array(), $this->fixture->getResults());
37 | }
38 |
39 | public function testHandle()
40 | {
41 | $this->setExpectedException('Teleport\\Request\\RequestException');
42 | $this->fixture->handle(array('foo' => 'bar'));
43 | }
44 |
45 | public function testLog()
46 | {
47 | $this->fixture->log('logging message', false);
48 | $result = $this->fixture->getResults();
49 | $result = array_pop($result);
50 | $this->assertEquals('logging message', $result);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Teleport/Request/RequestException.php:
--------------------------------------------------------------------------------
1 | request = & $request;
33 | $code = E_USER_ERROR;
34 | if ($previous instanceof \Exception) {
35 | if (!is_string($message) || $message === '') {
36 | $message = $previous->getMessage();
37 | }
38 | $code = $previous->getCode();
39 | }
40 | parent::__construct($message, $code, $previous);
41 | }
42 |
43 | /**
44 | * Get the results reference provided to this exception.
45 | *
46 | * @return array The results array.
47 | */
48 | public function getResults()
49 | {
50 | return $this->request->getResults();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Teleport/Action/ActionException.php:
--------------------------------------------------------------------------------
1 | action = & $action;
33 | $code = E_USER_ERROR;
34 | if ($previous instanceof \Exception) {
35 | if (!is_string($message) || $message === '') {
36 | $message = $previous->getMessage();
37 | }
38 | $code = $previous->getCode();
39 | }
40 | parent::__construct($message, $code, $previous);
41 | }
42 |
43 | /**
44 | * Get the results of the action provided to this exception.
45 | *
46 | * @return array The results array.
47 | */
48 | public function getResults()
49 | {
50 | return $this->action->getRequest()->getResults();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Push.php:
--------------------------------------------------------------------------------
1 | overwriteTarget && file_exists($this->target)) {
34 | throw new ActionException($this, "{$this->target} exists; use --overwriteTarget to Push anyway");
35 | }
36 | try {
37 | $pushed = copy($this->source, $this->target);
38 | if (!$pushed) {
39 | throw new ActionException($this, "copy failed");
40 | }
41 | if ($this->removeSource) {
42 | unlink($this->source);
43 | }
44 | $this->request->log("Successfully pushed {$this->source} to {$this->target}");
45 | } catch (\Exception $e) {
46 | throw new ActionException($this, "Error pushing {$this->source} to {$this->target}", $e);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "modxcms/teleport",
3 | "description": "An extensible scripting and packaging toolkit for MODX Revolution",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Jason Coward",
8 | "email": "jason@opengeek.com",
9 | "homepage": "http://www.opengeek.com/",
10 | "role": "Developer"
11 | }
12 | ],
13 | "require": {
14 | "symfony/finder": "^2.2.0",
15 | "symfony/filesystem": "^2.4.0",
16 | "react/event-loop": "^0.4",
17 | "react/http": "^0.4",
18 | "react/promise": "^2.1",
19 | "react/child-process": "^0.4"
20 | },
21 | "require-dev": {
22 | "symfony/process": "^2.1"
23 | },
24 | "suggest": {
25 | "aws/aws-sdk-php": "Require this with ^2.0 version constraint to enable registration of an S3 stream wrapper.",
26 | "ext-libevent": "It is critical to have this or ext-ev if you plan to use the server or endpoint listener.",
27 | "ext-ev": "It is critical to have this or ext-libevent if you plan to use the server or endpoint listener."
28 | },
29 | "bin": [
30 | "bin/compile",
31 | "bin/compile.php",
32 | "bin/endpoint",
33 | "bin/endpoint.php",
34 | "bin/server",
35 | "bin/server.php",
36 | "bin/teleport",
37 | "bin/teleport.php"
38 | ],
39 | "autoload": {
40 | "psr-0": {
41 | "Teleport": "src/"
42 | }
43 | },
44 | "extra": {
45 | "branch-alias": {
46 | "dev-master": "1.x-dev"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Teleport/Request/InvalidRequestException.php:
--------------------------------------------------------------------------------
1 | request = & $request;
33 | $code = E_USER_ERROR;
34 | if ($previous instanceof \Exception) {
35 | if (!is_string($message) || $message === '') {
36 | $message = $previous->getMessage();
37 | }
38 | $code = $previous->getCode();
39 | }
40 | parent::__construct($message, $code, $previous);
41 | }
42 |
43 | /**
44 | * Get the results reference provided to this exception.
45 | *
46 | * @return array The results array.
47 | */
48 | public function getResults()
49 | {
50 | return $this->request->getResults();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/doc/use/inject.md:
--------------------------------------------------------------------------------
1 | # Inject
2 |
3 |
4 | ## Inject a Teleport Package
5 |
6 | You can Inject a Teleport package from any valid stream source into a MODX site using the following command:
7 |
8 | php teleport.phar --action=Inject --profile=profile/mysite.profile.json --source=workspace/mysite_develop-120315.1106.30-2.2.1-dev.transport.zip
9 |
10 | _NOTE: If the source is not within the workspace/ directory a copy will be pulled to that location and then removed after the Inject completes unless --preserveWorkspace is passed._
11 |
12 | ### How Inject Manipulates Snapshots
13 |
14 | To prevent some data from corrupting a target MODX deployment when it is injected, the Inject action takes the following measures:
15 |
16 | * Before Injection
17 | * modSystemSetting vehicles with the following keys are removed from the manifest:
18 | * `session_cookie_domain`
19 | * `session_cookie_path`
20 | * `new_file_permissions`
21 | * `new_folder_permissions`
22 | * After Injection
23 | * modSystemSetting `settings_version` is set to the actual target version.
24 | * modSystemSetting `session_cookie_domain` is set to empty. _<= 1.7.0_
25 | * modSystemSetting `session_cookie_path` is set to `MODX_BASE_PATH`. _<= 1.7.0_
26 |
27 |
28 | ## The Inject Action
29 |
30 | ### Required Arguments
31 |
32 | * `--profile=path` - A valid stream path to a Teleport [Profile](profile.md). This defines the MODX instance the Inject is to be performed against.
33 | * `--source=path` - A valid stream path to a Teleport package to Inject into the MODX instance described by the specified Profile.
34 |
35 | ### Optional Arguments
36 |
37 | * `--preserveWorkspace` - Indicates if the workspace/ copy of the package should be removed after being pushed to a target.
38 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Workspace/GC.php:
--------------------------------------------------------------------------------
1 | deleteTree(TELEPORT_BASE_PATH . 'workspace');
29 |
30 | $this->request->log("Completed cleaning up the Teleport workspace");
31 | } catch (\Exception $e) {
32 | throw new ActionException($this, "Error cleaning up the Teleport workspace: {$e->getMessage()}", $e);
33 | }
34 | }
35 |
36 | private function deleteTree($dir)
37 | {
38 | $iterator = new \RecursiveIteratorIterator(
39 | new \RecursiveDirectoryIterator(
40 | $dir,
41 | \FilesystemIterator::SKIP_DOTS
42 | ),
43 | \RecursiveIteratorIterator::CHILD_FIRST
44 | );
45 | /** @var \SplFileInfo $fileInfo */
46 | foreach ($iterator as $filename => $fileInfo) {
47 | if ($fileInfo->isDir()) {
48 | rmdir($filename);
49 | } elseif ($fileInfo->getPath() === $dir && $fileInfo->getBasename() === '.gitignore') {
50 | continue;
51 | } else {
52 | unlink($filename);
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/basepathassets.php:
--------------------------------------------------------------------------------
1 | MODX_BASE_PATH . $file,
32 | 'target' => 'return MODX_BASE_PATH;'
33 | );
34 | }
35 | closedir($dh);
36 | foreach ($includes as $include) {
37 | if ($this->package->put($include, $vehicle['attributes'])) {
38 | $this->request->log("Packaged 1 {$vehicle['vehicle_class']} from {$include['source']}");
39 | $vehicleCount++;
40 | } else {
41 | $this->request->log("Error packaging {$vehicle['vehicle_class']} from {$include['source']}");
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modresourcechildren.php:
--------------------------------------------------------------------------------
1 | modx->newQuery('modResource', $criteria, false);
15 |
16 | $iterator = $this->modx->getIterator('modResource', $query, false);
17 | foreach ($iterator as $object) {
18 | /** @var modResource $object */
19 | if ($graph !== null) {
20 | $object->getGraph($graph, $graphCriteria, false);
21 | }
22 | if ($this->package->put($object, $vehicle['attributes'])) {
23 | $vehicleCount++;
24 | }
25 | modresource_populate_children($this, $object, $criteria, $graph, $graphCriteria, $vehicle, $vehicleCount);
26 | }
27 | function modresource_populate_children(\Teleport\Action\Extract &$extract, modResource &$object, $criteria, $graph, $graphCriteria, $vehicle, &$vehicleCount) {
28 | unset($criteria['parent']);
29 | $children = $object->getMany('Children', null, false);
30 | if ($children) {
31 | foreach ($children as &$child) {
32 | /** @var modResource $child */
33 | if ($graph !== null) {
34 | $child->getGraph($graph, $graphCriteria);
35 | }
36 | if ($extract->package->put($child, $vehicle['attributes'])) {
37 | $vehicleCount++;
38 | }
39 | modresource_populate_children($extract, $child, $criteria, $graph, $graphCriteria, $vehicle, $vehicleCount);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tpl/template.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"template",
3 | "description":"Package a single Template, along with it's related Category and PropertySets, by templatename. Will overwrite a Template in the target with the same templatename.",
4 | "vehicles":[
5 | {
6 | "vehicle_class":"xPDOObjectVehicle",
7 | "object":{
8 | "class":"modTemplate",
9 | "criteria":{
10 | "templatename":"{+templatename}"
11 | },
12 | "graph":{
13 | "Category":[],
14 | "PropertySets":{
15 | "PropertySet":[]
16 | }
17 | },
18 | "package":"modx"
19 | },
20 | "attributes":{
21 | "preserve_keys":false,
22 | "update_object":true,
23 | "unique_key":"templatename",
24 | "related_objects":true,
25 | "related_object_attributes":{
26 | "Category":{
27 | "preserve_keys":false,
28 | "update_object":true,
29 | "unique_key":"category"
30 | },
31 | "PropertySets":{
32 | "preserve_keys":true,
33 | "update_object":true,
34 | "unique_key":["element", "element_class", "property_set"],
35 | "related_objects":true,
36 | "related_object_attributes":{
37 | "PropertySet":{
38 | "preserve_keys":false,
39 | "update_object":false,
40 | "unique_key":"name"
41 | }
42 | }
43 | }
44 | }
45 | }
46 | }
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/doc/install/git-clone.md:
--------------------------------------------------------------------------------
1 | # Teleport Source Installation via _Git Clone_
2 |
3 | Contributors will want to use this method to install Teleport so they can easily submit pull requests to the project.
4 |
5 | ## Create a Working Directory for the Application
6 |
7 | First, create a directory where you will run the Teleport application. Teleport will create subdirectories for it's work when being used, and it is best if you create it's own directory where it can live in isolation. A typical location might be `~/teleport/`.
8 |
9 | ## Fork modxcms/teleport
10 |
11 | Go to modxcms/teleport and click the Fork button.
12 |
13 | _NOTE: This is only required if you plan on submitting pull requests to the Teleport application._
14 |
15 | ## Clone
16 |
17 | Clone the modxcms/teleport repository into your Teleport working directory:
18 |
19 | git clone git@github.com:modxcms/teleport.git ~/teleport/
20 |
21 | or your fork:
22 |
23 | git clone git@github.com:username/teleport.git ~/teleport/
24 |
25 | ## Add upstream
26 |
27 | If you forked the repository in order to contribute, you will want to add the official modxcms/teleport repository as a remote:
28 |
29 | git remote add upstream git@github.com:modxcms/teleport.git
30 |
31 | ## Install Dependencies with Composer
32 |
33 | You will need to install the dependencies for using and optionally for developing Teleport using [Composer](http://getcomposer.org/).
34 |
35 | Within the root of your cloned repository's working directory:
36 |
37 | composer install --dev
38 |
39 | ## Running Teleport from Source
40 |
41 | The documentation assumes you have installed the phar distribution of Teleport. Since you have chosen to install from source, when you see `php teleport.phar` in the documentation examples you should substitute `bin/teleport` to run the application instead.
42 |
43 | ## Get Started
44 |
45 | Get started using Teleport by [generating a Profile](../use/profile.md) of a local MODX installation.
46 |
--------------------------------------------------------------------------------
/doc/use/profile.md:
--------------------------------------------------------------------------------
1 | # Profile
2 |
3 | Before using Teleport with a MODX site, you will need to create a Teleport Profile from the installed site.
4 |
5 | ## Generate a MODX Site Profile
6 |
7 | You can automatically generate a Teleport Profile of an existing MODX site using the following command:
8 |
9 | php teleport.phar --action=Profile --name="MySite" --code=mysite --core_path=/path/to/mysite/modx/core/ --config_key=config
10 |
11 | The resulting profile will be located at profile/mysite.profile.json and can then be used for Extract, Inject, or other Teleport Actions to target the site represented in the profile.
12 |
13 |
14 | ## The Profile Action
15 |
16 | ### Required Arguments
17 |
18 | * `--name='string'` - A name for the profile.
19 | * `--core_path=path` - The `MODX_CORE_PATH` of the MODX install to generate the profile from.
20 |
21 | ### Optional Arguments
22 |
23 | * `--code=string` - A simple name for the profile. If not provided, a filtered version of the `name` argument is used.
24 | * `--config_key` - The `MODX_CONFIG_KEY` of the MODX install if different than the default `config` value. This is __required__ if the value is not `config` for the MODX install being targeted.
25 |
26 |
27 | ## Sample Profile
28 |
29 | This is a sample Teleport Profile of a MODX site with all the required properties:
30 |
31 | {
32 | "name": "Revo-2.2.x",
33 | "code": "revo_22x",
34 | "properties": {
35 | "modx": {
36 | "core_path": "\/home\/user\/www\/revo-2.2.x\/core\/",
37 | "config_key": "config",
38 | "context_mgr_path": "\/home\/user\/www\/revo-2.2.x\/manager\/",
39 | "context_mgr_url": "\/revo-2.2.x\/manager\/",
40 | "context_connectors_path": "\/home\/user\/www\/revo-2.2.x\/connectors\/",
41 | "context_connectors_url": "\/revo-2.2.x\/connectors\/",
42 | "context_web_path": "\/home\/user\/www\/revo-2.2.x\/",
43 | "context_web_url": "\/revo-2.2.x\/"
44 | }
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modaccesspolicytemplategroup.php:
--------------------------------------------------------------------------------
1 | modx->newQuery('modAccessPolicyTemplateGroup', $criteria, false);
13 |
14 | $iterator = $this->modx->getIterator('modAccessPolicyTemplateGroup', $query, false);
15 | foreach ($iterator as $object) {
16 | /** @var modAccessPolicyTemplateGroup $object */
17 | if (!empty($graph)) {
18 | $object->getGraph($graph, $graphCriteria, false);
19 | }
20 | $templates = $object->getMany('Templates');
21 | if (!empty($templates)) {
22 | foreach ($templates as &$template) {
23 | /** @var modAccessPolicyTemplate $template */
24 | $template->getGraph(array('Permissions' => array(), 'Policies' => array()), null, false);
25 | $policies = $template->getMany('Policies');
26 | if (!empty($policies)) {
27 | foreach ($policies as &$policy) {
28 | /** @var modAccessPolicy $policy */
29 | modaccesspolicytemplategroup_populate_policy_children($policy);
30 | }
31 | }
32 | }
33 | }
34 | if ($this->package->put($object, $vehicle['attributes'])) {
35 | $vehicleCount++;
36 | }
37 | }
38 |
39 | function modaccesspolicytemplategroup_populate_policy_children(modAccessPolicy &$object) {
40 | $children = $object->getMany('Children', null, false);
41 | if ($children) {
42 | foreach ($children as &$child) {
43 | if ($child->get('id') == $object->get('id')) continue;
44 | modaccesspolicytemplategroup_populate_policy_children($child);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/modtransportpackage.php:
--------------------------------------------------------------------------------
1 | modx->loadClass('transport.modTransportPackage');
11 | foreach ($this->modx->getIterator('modWorkspace') as $workspace) {
12 | $packagesDir = $workspace->get('path') . 'packages/';
13 | $this->request->log("Packaging transport packages for workspace {$workspace->get('name')} using dir {$packagesDir}");
14 | $response = $this->modx->call('modTransportPackage', 'listPackages', array(&$this->modx, $workspace->get('id')));
15 | if (isset($response['collection'])) {
16 | foreach ($response['collection'] as $object) {
17 | $attributes = $vehicle['attributes'];
18 | $pkgSource = $object->get('source');
19 | $folderPos = strrpos($pkgSource, '/');
20 | $sourceDir = $folderPos > 1 ? substr($pkgSource, 0, $folderPos + 1) : '';
21 | $source = realpath($packagesDir . $pkgSource);
22 | $target = 'MODX_CORE_PATH . "packages/' . $sourceDir . '"';
23 | if (!isset($attributes['resolve'])) $attributes['resolve'] = array();
24 | $attributes['resolve'][] = array(
25 | 'type' => 'file',
26 | 'source' => $source,
27 | 'target' => 'return ' . $target . ';'
28 | );
29 | if (isset($vehicle['object']['install']) && !empty($vehicle['object']['install'])) {
30 | $attributes['resolve'][] = array(
31 | 'type' => 'php',
32 | 'source' => TELEPORT_BASE_PATH . 'tpl/scripts/resolve/modtransportpackage/install.php'
33 | );
34 | }
35 | if ($this->package->put($object, $attributes)) {
36 | $this->request->log("Packaged modTransportPackage {$object->get('signature')} with file {$source}");
37 | $vehicleCount++;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tpl/scripts/resolve/modsystemsetting/extension_packages.php:
--------------------------------------------------------------------------------
1 | get('key') === 'extension_packages') {
8 | $extPackages = $object->get('value');
9 | $extPackages = $transport->xpdo->fromJSON($extPackages);
10 | if (!is_array($extPackages)) $extPackages = array();
11 | if (is_array($options) && array_key_exists('extension_packages', $options)) {
12 | $optPackages = $transport->xpdo->fromJSON($options['extension_packages']);
13 | if (is_array($optPackages)) {
14 | $extPackages = array_merge($extPackages, $optPackages);
15 | }
16 | }
17 | if (!empty($extPackages)) {
18 | foreach ($extPackages as $extPackage) {
19 | if (!is_array($extPackage)) continue;
20 |
21 | foreach ($extPackage as $packageName => $package) {
22 | if (!empty($package) && !empty($package['path'])) {
23 | $package['tablePrefix'] = !empty($package['tablePrefix']) ? $package['tablePrefix'] : null;
24 | $package['path'] = str_replace(array(
25 | '[[++core_path]]',
26 | '[[++base_path]]',
27 | '[[++assets_path]]',
28 | '[[++manager_path]]',
29 | ),array(
30 | $transport->xpdo->config['core_path'],
31 | $transport->xpdo->config['base_path'],
32 | $transport->xpdo->config['assets_path'],
33 | $transport->xpdo->config['manager_path'],
34 | ),$package['path']);
35 | $transport->xpdo->addPackage($packageName,$package['path'],$package['tablePrefix']);
36 | if (!empty($package['serviceName']) && !empty($package['serviceClass'])) {
37 | $packagePath = str_replace('//','/',$package['path'].$packageName.'/');
38 | $transport->xpdo->getService($package['serviceName'],$package['serviceClass'],$packagePath);
39 | }
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/bin/teleport.php:
--------------------------------------------------------------------------------
1 | $debug
49 | );
50 | if (is_readable('config.php')) {
51 | $options = include 'config.php';
52 | }
53 |
54 | $teleport = \Teleport\Teleport::instance($options);
55 | $request = $teleport->getRequest();
56 |
57 | array_shift($argv);
58 |
59 | $request->handle($argv);
60 | $results = implode(PHP_EOL, $request->getResults());
61 | echo trim($results) . PHP_EOL;
62 | if ($debug) {
63 | printf("execution finished with exit code 0 in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
64 | }
65 | exit(0);
66 | } catch (\Exception $e) {
67 | echo $e->getMessage() . PHP_EOL;
68 | if ($debug) {
69 | printf("execution failed with exit code {$e->getCode()} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
70 | }
71 | exit($e->getCode());
72 | }
73 |
--------------------------------------------------------------------------------
/tests/Teleport/Test/Request/AbstractRequestTest.php:
--------------------------------------------------------------------------------
1 | fixture = new MockRequest;
23 | }
24 |
25 | public function testInstanceOf()
26 | {
27 | $this->assertInstanceOf('Teleport\\Request\\RequestInterface', $this->fixture);
28 | $this->assertInstanceOf('Teleport\\Request\\Request', $this->fixture);
29 | $this->assertInstanceOf('Teleport\\Test\\Request\\MockRequest', $this->fixture);
30 | }
31 |
32 | /**
33 | * Test the mock parseArguments implementation.
34 | *
35 | * @param array|string $expected
36 | * @param array $arguments
37 | *
38 | * @dataProvider providerParseArguments
39 | */
40 | public function testParseArguments($expected, $arguments)
41 | {
42 | $this->assertEquals($expected, $this->fixture->parseArguments($arguments));
43 | }
44 | public function providerParseArguments()
45 | {
46 | return array(
47 | array(array(), array('action' => 'Test')),
48 | array(array('id' => 'Test', 'attribute' => 'value'), array('action' => 'Test', 'id' => 'Test', 'attribute' => 'value')),
49 | );
50 | }
51 |
52 | /**
53 | * Test the mock parseArguments implementation throws appropriate exceptions.
54 | *
55 | * @param array $arguments
56 | *
57 | * @expectedException \Teleport\Request\RequestException
58 | * @dataProvider providerParseArgumentsThrows
59 | */
60 | public function testParseArgumentsThrows($arguments)
61 | {
62 | $this->fixture->parseArguments($arguments);
63 | }
64 | public function providerParseArgumentsThrows()
65 | {
66 | return array(
67 | array(array()),
68 | array(array('id' => 'Test')),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Teleport/Action/UserCreate.php:
--------------------------------------------------------------------------------
1 | profile = Teleport::loadProfile($this->profile);
41 | if (empty($this->passwordnotifymethod)) {
42 | $this->passwordnotifymethod = 's';
43 | }
44 | if (!empty($this->password)) {
45 | $this->passwordgenmethod = '';
46 | $this->newpassword = $this->password;
47 | $this->specifiedpassword = $this->password;
48 | $this->confirmpassword = $this->password;
49 | }
50 |
51 | $this->getMODX($this->profile);
52 | $this->modx->getService('error', 'error.modError');
53 | $this->modx->error->message = '';
54 | $this->modx->setOption(\xPDO::OPT_SETUP, true);
55 |
56 | /** @var \modProcessorResponse $response */
57 | $response = $this->modx->runProcessor('security/user/create', $this->request->args());
58 | if ($response->isError()) {
59 | throw new ActionException($this, implode("\n", $response->getAllErrors()) . "\n0");
60 | } else {
61 | $this->request->log("Created user for {$this->profile->name} with username {$this->username}: {$response->getMessage()}");
62 | }
63 | $this->request->log('1', false);
64 | } catch (\Exception $e) {
65 | throw new ActionException($this, "Error creating MODX user: {$e->getMessage()}", $e);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/teleport.php:
--------------------------------------------------------------------------------
1 | $debug
60 | );
61 | if (is_readable('config.php')) {
62 | $options = include 'config.php';
63 | }
64 |
65 | $teleport = \Teleport\Teleport::instance($options);
66 | $request = $teleport->getRequest();
67 |
68 | array_shift($argv);
69 |
70 | $request->handle($argv);
71 | $results = implode(PHP_EOL, $request->getResults());
72 | echo trim($results) . PHP_EOL;
73 | if ($debug) {
74 | printf("execution finished with exit code 0 in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
75 | }
76 | exit(0);
77 | } catch (\Exception $e) {
78 | echo $e->getMessage() . PHP_EOL;
79 | if ($debug) {
80 | printf("execution failed with exit code {$e->getCode()} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
81 | }
82 | exit($e->getCode());
83 | }
84 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/userextensionpackages.php:
--------------------------------------------------------------------------------
1 | modx->getObject('modSystemSetting', $criteria);
11 | if ($object) {
12 | $extPackages = $object->get('value');
13 | $extPackages = $this->modx->fromJSON($extPackages);
14 | if (is_array($extPackages) && !empty($extPackages)) {
15 | $extUserPackages = array();
16 | $extUsers = $this->modx->getIterator('modUser', array('class_key:!=' => 'modUser'));
17 | /** @var modUser $user */
18 | foreach ($extUsers as $user) {
19 | $extUserClass = $user->_class;
20 | $extUserPkg = $user->_package;
21 | if (!array_key_exists($extUserPkg, $extUserPackages) && array_key_exists($extUserPkg, $extPackages)) {
22 | $extUserPackages[$extUserPkg] = $extPackages[$extUserPkg];
23 | }
24 | }
25 | if (!empty($extUserPackages)) {
26 | foreach ($extUserPackages as $pkgKey => $pkg) {
27 | if (array_key_exists($pkgKey, $this->modx->packages) && isset($this->modx->packages[$pkgKey]['path'])) {
28 | $pkgModelDir = $this->modx->packages[$pkgKey]['path'];
29 | $source = realpath(dirname($pkgModelDir));
30 | $target = 'MODX_CORE_PATH . "components/"';
31 | if (strpos($source, realpath(MODX_CORE_PATH)) === 0) {
32 | $target = 'MODX_CORE_PATH . "' . str_replace(realpath(MODX_CORE_PATH), '', $source) . '"';
33 | } elseif (strpos($source, realpath(MODX_BASE_PATH)) === 0) {
34 | $target = 'MODX_BASE_PATH . "' . str_replace(realpath(MODX_BASE_PATH), '', $source) . '"';
35 | }
36 | if (!isset($vehicle['attributes']['validate'])) $vehicle['attributes']['validate'] = array();
37 | $vehicle['attributes']['validate'][] = array(
38 | 'type' => 'file',
39 | 'source' => $source,
40 | 'target' => 'return ' . $target . ';'
41 | );
42 | }
43 | }
44 | $object->set('value', $this->modx->toJSON($extUserPackages));
45 | if ($this->package->put($object, $vehicle['attributes'])) {
46 | $vehicleCount++;
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/doc/use/extract.md:
--------------------------------------------------------------------------------
1 | # Extract
2 |
3 | The Teleport Extract Action is an extremely flexible transport packaging tool for MODX Revolution.
4 |
5 |
6 | ## Extract a Teleport Package from a MODX Site
7 |
8 | You can Extract a Teleport package from a MODX site using the following command:
9 |
10 | php teleport.phar --action=Extract --profile=profile/mysite.profile.json --tpl=phar://teleport.phar/tpl/complete.tpl.json
11 |
12 | The package will be located in the workspace/ directory if it is created successfully.
13 |
14 | You can also Extract a Teleport package and push it to any valid stream target using the following command:
15 |
16 | php teleport.phar --action=Extract --profile=profile/mysite.profile.json --tpl=phar://teleport.phar/tpl/complete.tpl.json --target=s3://mybucket/snapshots/ --push
17 |
18 | In either case, the absolute path to the package is returned by the process as the final output. You can use this as the path for an Inject source.
19 |
20 | _NOTE: The workspace copy is removed after it is pushed unless you pass --preserveWorkspace to the CLI command._
21 |
22 |
23 | ## The Extract Action
24 |
25 | ### Required Arguments
26 |
27 | * `--profile=path` - A valid stream path to a Teleport [Profile](profile.md). This defines the MODX instance the Extract is to be performed against.
28 | * `--tpl=path` - A valid stream path to a Teleport [Extract tpl](extract/tpl.md) which defines the extraction process.
29 |
30 | ### Optional Arguments
31 |
32 | * `--name=name` - Override the name of the created Teleport package signature. By default the profile `code` and tpl `name` are concatenated with an `_` to form the package name.
33 | * `--version=version` - A valid version string (not including the release data) for the created Teleport package signature. By default the current time is used in the format `%y%m%d.%H%M.%S`.
34 | * `--release=release` - A valid release string for the created Teleport package signature. By default the `full_version` of the site identified by the profile is used. If a `version` is provided, but no release is, no release is added to the package signature.
35 | * `--target=path` - A valid stream path to a folder where the extracted package should be pushed.
36 | * `--push` - Indicates if the extracted package should be pushed to the target.
37 | * `--preserveWorkspace` - Indicates if the workspace/ copy of the package should be removed after being pushed to a target.
38 |
39 | _NOTE: Individual Extract tpls can use any additional arguments passed to the command line as value replacements in the tpl. See documentation for specific tpls to see what additional arguments, if any, that tpl supports._
40 |
--------------------------------------------------------------------------------
/tpl/user.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "user",
3 | "description": "Package a single User by id with Profile, Settings, UserGroup Memberships, UserGroups, UserGroup Roles, and Primary UserGroup. WARNING: This looks up existing users by id and overwrites them in the target, not by username.",
4 | "vehicles": [
5 | {
6 | "vehicle_class": "xPDOObjectVehicle",
7 | "object": {
8 | "class": "modUser",
9 | "criteria": {
10 | "id": "{+user-id}"
11 | },
12 | "graph": {
13 | "Profile": [],
14 | "UserSettings": [],
15 | "UserGroupMembers": {
16 | "UserGroup": [],
17 | "UserGroupRole": []
18 | },
19 | "PrimaryGroup": []
20 | },
21 | "package": "modx"
22 | },
23 | "attributes": {
24 | "preserve_keys": true,
25 | "update_object": true,
26 | "related_objects": true,
27 | "related_object_attributes": {
28 | "Profile": {
29 | "preserve_keys": false,
30 | "update_object": true,
31 | "unique_key": "internalKey"
32 | },
33 | "UserSettings": {
34 | "preserve_keys": true,
35 | "update_object": true
36 | },
37 | "UserGroupMembers": {
38 | "preserve_keys": false,
39 | "update_object": true,
40 | "unique_key": ["user_group", "member"],
41 | "related_objects": true,
42 | "related_object_attributes": {
43 | "UserGroup": {
44 | "preserve_keys": false,
45 | "update_object": true,
46 | "unique_key": "name"
47 | },
48 | "UserGroupRole": {
49 | "preserve_keys": false,
50 | "update_object": true,
51 | "unique_key": "name"
52 | }
53 | }
54 | },
55 | "PrimaryGroup": {
56 | "preserve_keys": false,
57 | "update_object": true,
58 | "unique_key": "name"
59 | }
60 | }
61 | }
62 | }
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/bin/compile.php:
--------------------------------------------------------------------------------
1 | compile($arg(1, 'teleport.phar'));
79 |
80 | printf("execution finished with exit code 0 in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
81 | exit(0);
82 | } catch (\Exception $e) {
83 | echo 'fatal: could not compile phar [' . get_class($e) . '] ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . PHP_EOL;
84 | printf("execution failed with exit code {$e->getCode()} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
85 | exit($e->getCode());
86 | }
87 |
--------------------------------------------------------------------------------
/tpl/users.tpl.json:
--------------------------------------------------------------------------------
1 | {"name":"users", "vehicles":[
2 | {
3 | "vehicle_class":"xPDOObjectVehicle",
4 | "object":{
5 | "class":"modUserGroup",
6 | "criteria":["1 = 1"],
7 | "package":"modx"
8 | },
9 | "attributes":{
10 | "preserve_keys":false,
11 | "update_object":true,
12 | "unique_key":"name"
13 | }
14 | },
15 | {
16 | "vehicle_class":"xPDOObjectVehicle",
17 | "object":{
18 | "class":"modUserGroupRole",
19 | "criteria":["1 = 1"],
20 | "package":"modx"
21 | },
22 | "attributes":{
23 | "preserve_keys":false,
24 | "update_object":true,
25 | "unique_key":"name"
26 | }
27 | },
28 | {
29 | "vehicle_class":"xPDOObjectVehicle",
30 | "object":{
31 | "class":"modUser",
32 | "criteria":["1 = 1"],
33 | "graph":{
34 | "Profile":[],
35 | "UserSettings":[],
36 | "UserGroupMembers":{
37 | "UserGroup":[],
38 | "UserGroupRole":[]
39 | },
40 | "PrimaryGroup":[]
41 | },
42 | "package":"modx"
43 | },
44 | "attributes":{
45 | "preserve_keys":false,
46 | "update_object":true,
47 | "unique_key":"username",
48 | "related_objects":true,
49 | "related_object_attributes":{
50 | "Profile":{
51 | "preserve_keys":false,
52 | "update_object":true,
53 | "unique_key":"internalKey"
54 | },
55 | "UserSettings":{
56 | "preserve_keys":true,
57 | "update_object":true
58 | },
59 | "UserGroupMembers":{
60 | "preserve_keys":false,
61 | "update_object":true,
62 | "unique_key":["user_group", "member"],
63 | "related_objects":true,
64 | "related_object_attributes":{
65 | "UserGroup":{
66 | "preserve_keys":false,
67 | "update_object":true,
68 | "unique_key":"name"
69 | },
70 | "UserGroupRole":{
71 | "preserve_keys":false,
72 | "update_object":true,
73 | "unique_key":"name"
74 | }
75 | }
76 | },
77 | "PrimaryGroup":{
78 | "preserve_keys":false,
79 | "update_object":true,
80 | "unique_key":"name"
81 | }
82 | }
83 | }
84 | }
85 | ]}
--------------------------------------------------------------------------------
/src/Teleport/Transport/xPDOObjectVehicle.php:
--------------------------------------------------------------------------------
1 | xpdo->packages);
24 | if ($pkgFound = in_array($pkgPrefix, $pkgKeys)) {
25 | $pkgPrefix = '';
26 | }
27 | elseif ($pos = strpos($pkgPrefix, '.')) {
28 | $prefixParts = explode('.', $pkgPrefix);
29 | $prefix = '';
30 | foreach ($prefixParts as $prefixPart) {
31 | $prefix .= $prefixPart;
32 | $pkgPrefix = substr($pkgPrefix, $pos +1);
33 | if ($pkgFound = in_array($prefix, $pkgKeys))
34 | break;
35 | $prefix .= '.';
36 | $pos = strpos($pkgPrefix, '.');
37 | }
38 | if (!$pkgFound)
39 | $pkgPrefix = strtolower($element['package']);
40 | }
41 | $vClass = (!empty ($pkgPrefix) ? $pkgPrefix . '.' : '') . $vClass;
42 | }
43 | $object = $transport->xpdo->newObject($vClass);
44 | if (is_object($object) && $object instanceof \xPDOObject) {
45 | $options = array_merge($options, $element);
46 | $setKeys = false;
47 | if (isset ($options[\xPDOTransport::PRESERVE_KEYS])) {
48 | $setKeys = (boolean) $options[\xPDOTransport::PRESERVE_KEYS];
49 | }
50 | $object->fromJSON($element['object'], '', $setKeys, true);
51 | }
52 | }
53 | return $object;
54 | }
55 |
56 | public function put(& $transport, & $object, $attributes = array ())
57 | {
58 | parent::put($transport, $object, $attributes);
59 | if (isset($this->payload['package'])) {
60 | $this->payload['package'] = strtolower($this->payload['package']);
61 | }
62 | if (isset($this->payload['object']) && isset($this->payload['object']['package'])) {
63 | $this->payload['object']['package'] = strtolower($this->payload['object']['package']);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Teleport/Request/RequestInterface.php:
--------------------------------------------------------------------------------
1 | _processingTag = true;
18 | $this->_processingUncacheable = (boolean)$processUncacheable;
19 | $this->_removingUnprocessed = (boolean)$removeUnprocessed;
20 | $depth = $depth > 0 ? $depth - 1 : 0;
21 | $processed = 0;
22 | $tags = array();
23 | if ($collected = $this->collectElementTags($content, $tags, $prefix, $suffix, $tokens)) {
24 | $tagMap = array();
25 | foreach ($tags as $tag) {
26 | if ($tag[0] === $parentTag) {
27 | $tagMap[$tag[0]] = '';
28 | $processed++;
29 | continue;
30 | }
31 | $tagOutput = $this->processTag($tag, $processUncacheable);
32 | if ($tagOutput !== null && $tagOutput !== false) {
33 | $tagMap[$tag[0]] = $tagOutput;
34 | if ($tag[0] !== $tagOutput) $processed++;
35 | }
36 | }
37 | $this->mergeTagOutput($tagMap, $content);
38 | if ($depth > 0) {
39 | $processed += $this->processElementTags($parentTag, $content, $processUncacheable, $removeUnprocessed, $prefix, $suffix, $tokens, $depth);
40 | }
41 | }
42 | $this->_processingTag = false;
43 | return $processed;
44 | }
45 |
46 | public function processTag($tag, $processUncacheable = true)
47 | {
48 | $this->_processingTag = true;
49 | $element = null;
50 | $elementOutput = null;
51 |
52 | $outerTag = $tag[0];
53 | $innerTag = $tag[1];
54 |
55 | /* collect any nested element tags in the innerTag and process them */
56 | $this->processElementTags($outerTag, $innerTag, $processUncacheable);
57 | $this->_processingTag = true;
58 | $outerTag = '{+' . $innerTag . '}';
59 |
60 | $elementOutput = (string)$this->modx->getPlaceholder($innerTag);
61 |
62 | if ($elementOutput === null || $elementOutput === false) {
63 | $elementOutput = $outerTag;
64 | }
65 | if ($this->modx->getDebug() === true) {
66 | $this->modx->log(\xPDO::LOG_LEVEL_DEBUG, "Processing {$outerTag} as {$innerTag}:\n" . print_r($elementOutput, 1) . "\n\n");
67 | /* $this->modx->cacheManager->writeFile(MODX_BASE_PATH . 'parser.log', "Processing {$outerTag} as {$innerTag}:\n" . print_r($elementOutput, 1) . "\n\n", 'a'); */
68 | }
69 | $this->_processingTag = false;
70 | return $elementOutput;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Teleport 1.6.3 (2022-03-18)
2 |
3 | * Change phar compilation to include all vendor php files
4 |
5 | ### Teleport 1.6.2 (2022-03-15)
6 |
7 | * Update phar compilation to add missing composer files
8 |
9 | ### Teleport 1.6.1 (2022-03-14)
10 |
11 | * Replace each() with foreach() for PHP 8 compatibility (#38)
12 | * Prevent Transport::postInstall() from clearing session settings
13 |
14 | ### Teleport 1.6.0 (2019-11-21)
15 |
16 | * Allow base option to override current working directory
17 |
18 | ### Teleport 1.5.0 (2016-03-01)
19 |
20 | * Make sure xPDOObjectCollection sets are ordered
21 | * Add form_customizations Extract tpl
22 | * Skip MySQLVehicle if SELECT stmt fails on Extract
23 | * Add missing modAccessNamespace to promote.tpl.json
24 | * Fix invalid paths in promote.tpl.json
25 |
26 | ### Teleport 1.4.0 (2016-01-17)
27 |
28 | * Add template Extract tpl to package a Template by templatename
29 | * Add ability to provide signature for package created by Extract
30 | * Add Workspace/GC Action for cleaning up workspace/ directory
31 |
32 | ### Teleport 1.3.0 (2015-08-10)
33 |
34 | * Fix Extract warnings when no attributes exist in the tpl
35 | * Make AWS a suggested package and include only needed react packages
36 | * Allow a MODX instance to be explicitly set on the Teleport instance
37 | * Switch order of posix user and group switching attempts
38 |
39 | ### Teleport 1.2.0 (2015-08-09)
40 |
41 | * Add ability to include package attributes in an Extract tpl
42 |
43 | ### Teleport 1.1.0 (2015-07-19)
44 |
45 | * Skip vehicles referencing classes not available in specific MODX releases
46 | * Add promote Extract tpl
47 |
48 | ### Teleport 1.0.0 (2015-02-17)
49 |
50 | * Ensure MODX available in changeset callback functions
51 | * Use DIRECTORY_SEPARATOR for Windows compatibility
52 | * Add Teleport\Transport\FileVehicle using Finder and Filesystem
53 | * Add vehicle_parent_class support to Teleport\Transport\Transport
54 | * Refactor posix user switching to use user argument
55 |
56 | ### Teleport 1.0.0-alpha4 (2013-12-10)
57 |
58 | * Add resource_children Extract tpl
59 | * Refactor tpl parsing to occur before json_decode
60 | * Add Packages/GC Action
61 | * Allow tplBase arg to override value for Extract
62 | * Run APIRequests in sub-process to avoid constant conflicts
63 | * Add HTTP server to handle teleport web requests
64 | * Add Pull Action
65 | * Remove dependency on MODX in Push Action
66 | * Add support for Actions from other namespaces via namespace argument
67 | * Add RequestInterface::request() to call actions as sub-requests
68 |
69 | ### Teleport 1.0.0-alpha3 (2013-11-05)
70 |
71 | * Add teleport-tpl-update option to toggle update of tpl copies in projects using as library
72 | * Add teleport-tpl-dir option to composer.json extra section
73 | * Add missing dependencies in phar Compilation
74 | * Refactor Compiler for creating phars when using Teleport as a library
75 |
76 |
77 | ### Teleport 1.0.0-alpha2 (2013-11-02)
78 |
79 | * Attempt to create profile/ and workspace/ dirs automatically
80 | * Fix tpl script paths by using a tplBase from the specified tpl arg
81 |
82 |
83 | ### Teleport 1.0.0-alpha1 (2013-11-02)
84 |
85 | * Initial release
86 |
--------------------------------------------------------------------------------
/src/Teleport/Request/APIRequest.php:
--------------------------------------------------------------------------------
1 | parseArguments($arguments);
18 |
19 | $start = microtime(true);
20 |
21 | $loop = \React\EventLoop\Factory::create();
22 |
23 | $process = new \React\ChildProcess\Process($this->getCLICommand());
24 |
25 | $message = '';
26 | $request =& $this;
27 | $teleport = \Teleport\Teleport::instance();
28 |
29 | $process->on('exit', function($exitCode, $termSignal) use ($teleport, $request, $start, &$message) {
30 | $request->results = explode(PHP_EOL, rtrim($message, PHP_EOL));
31 | if ($request->args('debug') || $request->args('verbose')) {
32 | array_push($request->results, sprintf("request finished with exit code {$exitCode} in %2.4f seconds" . PHP_EOL, microtime(true) - $start));
33 | }
34 | if ($teleport->getConfig()->get('verbose', null, false) || $teleport->getConfig()->get('debug', null, false)) {
35 | echo sprintf("process finished with exit code {$exitCode} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
36 | }
37 | });
38 |
39 | $loop->addTimer(0.001, function($timer) use ($teleport, $request, $process, &$message) {
40 | if ($teleport->getConfig()->get('verbose', null, false) || $teleport->getConfig()->get('debug', null, false)) {
41 | echo "process started using cmd: {$process->getCommand()}" . PHP_EOL;
42 | }
43 | $process->start($timer->getLoop());
44 |
45 | $process->stdout->on('data', function($output) use ($teleport, $request, &$message) {
46 | $message .= $output;
47 | if ($teleport->getConfig()->get('verbose', null, false) || $teleport->getConfig()->get('debug', null, false)) {
48 | echo $output;
49 | }
50 | });
51 | $process->stderr->on('data', function($output) use ($teleport, $request, &$message) {
52 | $message .= $output;
53 | if ($teleport->getConfig()->get('verbose', null, false) || $teleport->getConfig()->get('debug', null, false)) {
54 | echo $output;
55 | }
56 | });
57 | });
58 |
59 | $loop->run();
60 | }
61 |
62 | protected function getCLICommand()
63 | {
64 | $command = "exec ";
65 | $command .= "php bin/teleport.php --action={$this->action} ";
66 | foreach ($this->args() as $argKey => $argVal) {
67 | if (is_bool($argVal)) {
68 | $command .= " --{$argKey}";
69 | $command .= $argVal !== true ? "=0 " : " ";
70 | } elseif (is_string($argVal)) {
71 | $command .= " --{$argKey}=" . escapeshellarg($argVal) . " ";
72 | }
73 | }
74 | return trim($command);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Teleport/Composer.php:
--------------------------------------------------------------------------------
1 | files()->ignoreVCS(true)->ignoreDotFiles(true)->name('*.tpl.json')->name('*.php')->in(dirname(dirname(__DIR__)) . '/tpl');
55 |
56 | foreach ($sources as $source) {
57 | self::copyFile($event, dirname(dirname(__DIR__)) . '/tpl', $source, $config);
58 | }
59 | }
60 |
61 | /**
62 | * Copy a source file from teleport/tpl to the teleport-tpl-dir.
63 | *
64 | * @param Event $event The composer Event object.
65 | * @param string $base The base path of the installation.
66 | * @param \SplFileInfo $source The source file info.
67 | * @param array $config Project config options.
68 | */
69 | protected static function copyFile($event, $base, $source, $config)
70 | {
71 | $filename = $source->getPathname();
72 | $relative = substr($filename, strlen($base) + 1);
73 | $target = $config['teleport-tpl-dir'] . '/' . $relative;
74 | if (!is_dir(dirname($target))) {
75 | @mkdir(dirname($target), 0777, true);
76 | }
77 | if ($config['teleport-tpl-update'] && file_exists($target)) {
78 | unlink($target);
79 | }
80 | if (!file_exists($target)) {
81 | copy($filename, $target);
82 | }
83 | }
84 |
85 | /**
86 | * Get the config data from the extra definition in composer.json.
87 | *
88 | * @param Event $event The composer Event object.
89 | *
90 | * @return array An array of options from the package extra.
91 | */
92 | protected static function getOptions(Event $event)
93 | {
94 | $options = array_merge(
95 | array(
96 | 'teleport-tpl-dir' => 'tpl',
97 | 'teleport-tpl-update' => true
98 | ),
99 | $event->getComposer()->getPackage()->getExtra()
100 | );
101 | return $options;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/tpl/form_customizations.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "form_customizations",
3 | "vehicles": [
4 | {
5 | "vehicle_class":"xPDOObjectVehicle",
6 | "object":{
7 | "class":"modFormCustomizationProfile",
8 | "criteria":["1 = 1"],
9 | "graph":{
10 | "Sets":{
11 | "Action":[],
12 | "Template":[],
13 | "Rules":{
14 | "Action":[]
15 | }
16 | },
17 | "UserGroups":{
18 | "UserGroup":[]
19 | }
20 | },
21 | "package":"modx"
22 | },
23 | "attributes":{
24 | "preserve_keys":false,
25 | "update_object":true,
26 | "unique_key":"name",
27 | "related_objects":true,
28 | "related_object_attributes":{
29 | "Sets":{
30 | "preserve_keys":false,
31 | "update_object":true,
32 | "unique_key":["profile", "action", "template", "constraint", "constraint_field", "constraint_class"],
33 | "related_objects":true,
34 | "related_object_attributes":{
35 | "Action":{
36 | "preserve_keys":false,
37 | "update_object":true,
38 | "unique_key":["namespace", "controller"]
39 | },
40 | "Template":{
41 | "preserve_keys":false,
42 | "update_object":true,
43 | "unique_key":"templatename"
44 | },
45 | "Rules":{
46 | "preserve_keys":false,
47 | "update_object":true,
48 | "unique_key":["set", "action", "name"],
49 | "related_objects":true,
50 | "related_object_attributes":{
51 | "Action":{
52 | "preserve_keys":false,
53 | "update_object":true,
54 | "unique_key":["namespace", "controller"]
55 | }
56 | }
57 | }
58 | }
59 | },
60 | "UserGroups":{
61 | "preserve_keys":false,
62 | "update_object":true,
63 | "unique_key":["usergroup", "profile"],
64 | "related_objects":true,
65 | "related_object_attributes":{
66 | "UserGroup":{
67 | "preserve_keys":false,
68 | "update_object":true,
69 | "unique_key":"name"
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/bin/server.php:
--------------------------------------------------------------------------------
1 | $opt('debug'),
79 | 'verbose' => $opt('verbose', true)
80 | );
81 | if (is_readable('config.php')) {
82 | $config = include 'config.php';
83 | if (is_array($config)) {
84 | $options = array_merge($config, $options);
85 | }
86 | }
87 |
88 | define('TELEPORT_BASE_PATH', rtrim($opt('base', getcwd()), '/') . '/');
89 |
90 | $server = \Teleport\Beam\HttpServer::instance($options);
91 | $server->run($arg(1, 1337));
92 |
93 | printf("teleport HTTP server stopped with exit code 0 in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
94 | exit(0);
95 | } catch (\Exception $e) {
96 | echo 'fatal: server error [' . get_class($e) . '] ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . PHP_EOL;
97 | printf("teleport HTTP server stopped with exit code {$e->getCode()} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
98 | exit($e->getCode());
99 | }
100 |
--------------------------------------------------------------------------------
/bin/endpoint.php:
--------------------------------------------------------------------------------
1 | $opt('debug'),
79 | 'verbose' => $opt('verbose', true)
80 | );
81 | if (is_readable('config.php')) {
82 | $config = include 'config.php';
83 | if (is_array($config)) {
84 | $options = array_merge($config, $options);
85 | }
86 | }
87 |
88 | define('TELEPORT_BASE_PATH', rtrim($opt('base', getcwd()), '/') . '/');
89 |
90 | /** @var \Teleport\Beam\Endpoint $server */
91 | $server = \Teleport\Beam\Endpoint::instance($options);
92 | $server->run($arg(1, TELEPORT_BASE_PATH . 'profile/revo_22.profile.json'));
93 |
94 | printf("teleport endpoint listener stopped with exit code 0 in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
95 | exit(0);
96 | } catch (\Exception $e) {
97 | echo 'fatal: server error [' . get_class($e) . '] ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . PHP_EOL;
98 | printf("teleport endpoint listener stopped with exit code {$e->getCode()} in %2.4f seconds" . PHP_EOL, microtime(true) - $start);
99 | exit($e->getCode());
100 | }
101 |
--------------------------------------------------------------------------------
/src/Teleport/Config.php:
--------------------------------------------------------------------------------
1 | config = $config;
33 | }
34 |
35 | /**
36 | * Get a Config option value.
37 | *
38 | * @param string $key The option key to lookup a value for.
39 | * @param array|null $options An associative array of options to override the persistent config.
40 | * @param mixed $default A default value to return if the lookup fails.
41 | * @param callable|null $filter An optional callback to filter the value with.
42 | *
43 | * @return mixed The value of the option or the specified default.
44 | */
45 | public function get($key, $options = null, $default = null, $filter = null)
46 | {
47 | $value = $default;
48 | if (is_array($options) && array_key_exists($key, $options)) {
49 | $value = $options[$key];
50 | } elseif (array_key_exists($key, $this->config)) {
51 | $value = $this->config[$key];
52 | }
53 | if (is_callable($filter)) {
54 | $filter($value);
55 | }
56 | return $value;
57 | }
58 |
59 | /**
60 | * Set a Config option value.
61 | *
62 | * @param string $key The key identifying the option to set.
63 | * @param mixed $value The value to set for the option.
64 | */
65 | public function set($key, $value)
66 | {
67 | $this->config[(string)$key] = $value;
68 | }
69 |
70 | /**
71 | * Find Config options by searching the keys.
72 | *
73 | * Use null to return all Config options.
74 | *
75 | * @param string|null $search An optional string to search the option keys with, or null to return all options.
76 | *
77 | * @return array An array containing the options matching the search, or all options if null is passed.
78 | */
79 | public function find($search = null)
80 | {
81 | $results = array();
82 | if (empty($search)) {
83 | $results = $this->config;
84 | } elseif (is_scalar($search)) {
85 | $results = array_filter($this->config, function ($value, $key) use ($search) {
86 | return strpos($key, $search) !== false;
87 | });
88 | } elseif (is_array($search)) {
89 | foreach ($search as $key) {
90 | if (!is_array($results)) $results = array();
91 | $results[$key] = $this->get($key);
92 | }
93 | }
94 | return $results;
95 | }
96 |
97 | /**
98 | * Recursively merge an associative array of options into the existing options.
99 | *
100 | * @param array $config An associative array of Config options to merge with the existing options.
101 | */
102 | public function merge(array $config = array())
103 | {
104 | $this->config = array_merge_recursive($this->config, $config);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Inject.php:
--------------------------------------------------------------------------------
1 | profile = Teleport::loadProfile($this->profile);
46 |
47 | $this->getMODX($this->profile);
48 |
49 | $this->modx->setOption(\xPDO::OPT_SETUP, true);
50 |
51 | $this->modx->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);
52 | $this->modx->loadClass('transport.xPDOVehicle', XPDO_CORE_PATH, true, true);
53 | $this->modx->loadClass('transport.xPDOObjectVehicle', XPDO_CORE_PATH, true, true);
54 | $this->modx->loadClass('transport.xPDOFileVehicle', XPDO_CORE_PATH, true, true);
55 |
56 | $transportName = basename($this->source);
57 | if (TELEPORT_BASE_PATH . 'workspace' . DIRECTORY_SEPARATOR . $transportName !== realpath($this->source)) {
58 | if (!$this->pull($this->source, TELEPORT_BASE_PATH . 'workspace' . DIRECTORY_SEPARATOR . $transportName)) {
59 | throw new ActionException($this, "Error pulling {$this->source}");
60 | }
61 | } else {
62 | $this->preserveWorkspace = true;
63 | }
64 |
65 | $this->package = Transport::retrieve($this->modx, TELEPORT_BASE_PATH . 'workspace' . DIRECTORY_SEPARATOR . $transportName, TELEPORT_BASE_PATH . 'workspace' . DIRECTORY_SEPARATOR);
66 | if (!$this->package instanceof Transport) {
67 | throw new ActionException($this, "Error extracting {$transportName} in workspace" . DIRECTORY_SEPARATOR);
68 | }
69 |
70 | $this->package->preInstall();
71 |
72 | if (!$this->package->install(array(\xPDOTransport::PREEXISTING_MODE => \xPDOTransport::REMOVE_PREEXISTING))) {
73 | throw new ActionException($this, "Error installing {$transportName}");
74 | }
75 |
76 | $this->package->postInstall();
77 |
78 | if ($this->modx->getCacheManager()) {
79 | $this->modx->cacheManager->refresh();
80 | }
81 |
82 | if (!$this->preserveWorkspace && $this->modx->getCacheManager()) {
83 | $this->modx->cacheManager->deleteTree($this->package->path . $transportName);
84 | @unlink($this->package->path . $transportName . '.transport.zip');
85 | }
86 |
87 | $this->request->log("Successfully injected {$transportName} into instance {$this->profile->code}");
88 | } catch (\Exception $e) {
89 | throw new ActionException($this, 'Error injecting snapshot: ' . $e->getMessage(), $e);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tpl/resources.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "resources",
3 | "vehicles": [
4 | {
5 | "vehicle_class": "xPDOObjectVehicle",
6 | "object": {
7 | "class": "modResource",
8 | "criteria": [
9 | "1 = 1"
10 | ],
11 | "graph": {
12 | "Template": [
13 |
14 | ],
15 | "ContentType": [
16 |
17 | ],
18 | "TemplateVarResources": {
19 | "TemplateVar": [
20 |
21 | ]
22 | },
23 | "ResourceGroupResources": {
24 | "ResourceGroup": [
25 |
26 | ]
27 | },
28 | "ContextResources": {
29 | "Context": [
30 |
31 | ]
32 | }
33 | },
34 | "package": "modx"
35 | },
36 | "attributes": {
37 | "preserve_keys": false,
38 | "update_object": true,
39 | "unique_key": [
40 | "context_key",
41 | "uri"
42 | ],
43 | "related_objects": true,
44 | "related_object_attributes": {
45 | "Template": {
46 | "preserve_keys": false,
47 | "update_object": true,
48 | "unique_key": "templatename"
49 | },
50 | "ContentType": {
51 | "preserve_keys": false,
52 | "update_object": true,
53 | "unique_key": "name"
54 | },
55 | "TemplateVarResources": {
56 | "preserve_keys": false,
57 | "update_object": true,
58 | "unique_key": [
59 | "tmplvarid",
60 | "contentid"
61 | ],
62 | "related_objects": true,
63 | "related_object_attributes": {
64 | "TemplateVar": {
65 | "preserve_keys": false,
66 | "update_object": true,
67 | "unique_key": "name"
68 | }
69 | }
70 | },
71 | "ResourceGroupResources": {
72 | "preserve_keys": false,
73 | "update_object": true,
74 | "unique_key": [
75 | "document_group",
76 | "document"
77 | ],
78 | "related_objects": true,
79 | "related_object_attributes": {
80 | "ResourceGroup": {
81 | "preserve_keys": false,
82 | "update_object": true,
83 | "unique_key": "name"
84 | }
85 | }
86 | },
87 | "ContextResources": {
88 | "preserve_keys": true,
89 | "update_object": true,
90 | "related_objects": true,
91 | "related_object_attributes": {
92 | "Context": {
93 | "preserve_keys": true,
94 | "update_object": false
95 | }
96 | }
97 | }
98 | }
99 | }
100 | }
101 | ]
102 | }
--------------------------------------------------------------------------------
/src/Teleport/Action/Profile.php:
--------------------------------------------------------------------------------
1 | request->args())) {
40 | $this->config_key = 'config';
41 | }
42 | if (!array_key_exists('code', $this->request->args())) {
43 | $this->code = str_replace(array('-', '.'), array('_', '_'), $this->name);
44 | }
45 |
46 | $profile = new \stdClass();
47 | $profile->properties = new \stdClass();
48 | $profile->properties->modx = new \stdClass();
49 | $profile->properties->modx->core_path = $this->core_path;
50 | $profile->properties->modx->config_key = $this->config_key;
51 |
52 | $this->getMODX($profile);
53 |
54 | $profile = array(
55 | 'name' => $this->name,
56 | 'code' => $this->code,
57 | 'properties' => array(
58 | 'modx' => array(
59 | 'core_path' => $this->core_path,
60 | 'config_key' => $this->config_key,
61 | 'context_mgr_path' => $this->modx->getOption('manager_path', null, MODX_MANAGER_PATH),
62 | 'context_mgr_url' => $this->modx->getOption('manager_url', null, MODX_MANAGER_URL),
63 | 'context_connectors_path' => $this->modx->getOption('connectors_path', null, MODX_CONNECTORS_PATH),
64 | 'context_connectors_url' => $this->modx->getOption('connectors_url', null, MODX_CONNECTORS_URL),
65 | 'context_web_path' => $this->modx->getOption('base_path', null, MODX_BASE_PATH),
66 | 'context_web_url' => $this->modx->getOption('base_url', null, MODX_BASE_URL),
67 | ),
68 | ),
69 | );
70 |
71 | $profileFilename = TELEPORT_BASE_PATH . 'profile' . DIRECTORY_SEPARATOR . $this->code . '.profile.json';
72 | $written = $this->modx->getCacheManager()->writeFile($profileFilename, $this->modx->toJSON($profile));
73 |
74 | if ($written === false) {
75 | throw new ActionException($this, "Error writing profile {$profileFilename}");
76 | }
77 | $this->request->log("Successfully wrote profile to {$profileFilename}");
78 | if ($this->target && $this->push) {
79 | if (!$this->push($profileFilename, $this->target)) {
80 | throw new ActionException($this, "Error pushing profile {$profileFilename} to {$this->target}");
81 | }
82 | $this->request->log("Successfully pushed profile {$profileFilename} to {$this->target}");
83 | $this->request->log("{$this->target}", false);
84 | } else {
85 | $this->request->log("{$profileFilename}", false);
86 | }
87 | } catch (\Exception $e) {
88 | throw new ActionException($this, "Error generating profile: " . $e->getMessage(), $e);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Teleport/Beam/HttpServer.php:
--------------------------------------------------------------------------------
1 | setConfig($options);
31 | }
32 | return self::$instance;
33 | }
34 |
35 | /**
36 | * Run the Teleport HTTP Server on the specified port.
37 | *
38 | * @param int $port A valid port to run the Teleport HTTP Server on.
39 | *
40 | * @throws \RuntimeException If an invalid port is specified.
41 | */
42 | public function run($port)
43 | {
44 | $port = (integer)$port;
45 | if ($port < 1) {
46 | throw new \RuntimeException("Invalid port specified for Teleport HTTP Server", E_USER_ERROR);
47 | }
48 |
49 | /** @var \React\EventLoop\LibEventLoop $loop */
50 | $loop = \React\EventLoop\Factory::create();
51 | $socket = new \React\Socket\Server($loop);
52 | $http = new \React\Http\Server($socket);
53 |
54 | $server =& $this;
55 |
56 | $http->on('request', function ($request, $response) use ($server) {
57 | /** @var \React\Http\Request $request */
58 | /** @var \React\Http\Response $response */
59 |
60 | $arguments = $request->getQuery();
61 | $arguments['action'] = trim($request->getPath(), '/');
62 |
63 | $headers = array(
64 | 'Content-Type' => 'text/javascript'
65 | );
66 | if (isset($arguments['action']) && !empty($arguments['action']) && strpos($arguments['action'], '.') === false) {
67 | try {
68 | /** @var \Teleport\Request\Request $request */
69 | $request = $server->getRequest('Teleport\\Request\\APIRequest');
70 | $request->handle($arguments);
71 | $results = $request->getResults();
72 |
73 | $response->writeHead(200, $headers);
74 | $response->end(json_encode(array('success' => true, 'message' => $results)));
75 | } catch (InvalidRequestException $e) {
76 | $response->writeHead(400, $headers);
77 | $response->end(json_encode(array('success' => false, 'message' => $e->getMessage())));
78 | } catch (\Exception $e) {
79 | $response->writeHead(500, $headers);
80 | $response->end(json_encode(array('success' => false, 'message' => $e->getMessage())));
81 | }
82 | } else {
83 | $response->writeHead(400, $headers);
84 | $response->end(json_encode(array('success' => false, 'message' => 'no valid action was specified')));
85 | }
86 | });
87 |
88 | if ($this->getConfig()->get('verbose', null, false) || $this->getConfig()->get('debug', null, false)) {
89 | echo "teleport server initializing" . PHP_EOL;
90 | }
91 | $socket->listen($port);
92 | if ($this->getConfig()->get('verbose', null, false) || $this->getConfig()->get('debug', null, false)) {
93 | echo "teleport server listening on port {$port}" . PHP_EOL;
94 | }
95 | $loop->run();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Teleport/Transport/FileVehicle.php:
--------------------------------------------------------------------------------
1 | payload['class'])) {
36 | $this->payload['class'] = $this->class;
37 | }
38 | if (!isset($object['in']) || !isset($object['target'])) {
39 |
40 | $transport->xpdo->log(\xPDO::LOG_LEVEL_ERROR, "Processing FileVehicle failed. You have to specify all required Object parameters: in, target");
41 | $object = null;
42 | $this->payload['object'] = $object;
43 | } else {
44 | $finder = new Finder();
45 |
46 | $finder->in($object['in']);
47 |
48 | foreach($object as $method => $data) {
49 | if (in_array($method, $this->skipObjectParams)) continue;
50 |
51 | if (method_exists($finder, $method)) {
52 | if (is_array($data)) {
53 | foreach ($data as $param) {
54 | $finder->$method($param);
55 | }
56 | } else {
57 | $finder->$method($data);
58 | }
59 | }
60 | }
61 |
62 | $object['files'] = iterator_to_array($finder);
63 | $this->payload['object'] = $object;
64 | }
65 |
66 | \xPDOVehicle :: put($transport, $object, $attributes);
67 | }
68 |
69 | /**
70 | * Copies the files into the vehicle and transforms the payload for storage.
71 | */
72 | protected function _compilePayload(& $transport) {
73 | \xPDOVehicle :: _compilePayload($transport);
74 |
75 | if (isset($this->payload['object']['in']) && isset($this->payload['object']['target'])) {
76 | $fs = new Filesystem();
77 |
78 | $rootFolder = explode('/', $this->payload['object']['in']);
79 | $rootFolder = array_pop($rootFolder);
80 | $this->payload['object']['name'] = $rootFolder;
81 | $this->payload['object']['source'] = $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'];
82 |
83 | $filePath = $transport->path . $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'] . '/' . $rootFolder;
84 |
85 | $fs->mkdir($filePath);
86 |
87 | /** @var SplFileInfo $file */
88 | foreach ($this->payload['object']['files'] as $file) {
89 | if ($file->isDir()) {
90 | $fs->mkdir($filePath . '/' . $file->getRelativePathname());
91 | } else {
92 | $fs->copy($file->getRealpath(), $filePath . '/' . $file->getRelativePathname());
93 | }
94 | }
95 |
96 | unset($this->payload['object']['files']);
97 | }
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Packages/GC.php:
--------------------------------------------------------------------------------
1 | profile = Teleport::loadProfile($this->profile);
37 |
38 | $this->getMODX($this->profile);
39 | $this->modx->getService('error', 'error.modError');
40 | $this->modx->error->message = '';
41 | $this->modx->setOption(\xPDO::OPT_SETUP, true);
42 |
43 | $this->modx->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);
44 | $this->modx->loadClass('transport.modTransportPackage');
45 |
46 | $latestPackages = $this->modx->call('modTransportPackage', 'listPackages', array(&$this->modx, 1));
47 | /** @var \modTransportPackage $latestPackage */
48 | foreach ($latestPackages['collection'] as $latestPackage) {
49 | $versions = $this->modx->call('modTransportPackage', 'listPackageVersions', array(
50 | &$this->modx,
51 | array(
52 | 'package_name:LIKE' => $latestPackage->package_name,
53 | 'signature:!=' => $latestPackage->signature)
54 | )
55 | );
56 | if (isset($versions['collection']) && $versions['total'] > 0) {
57 | $this->request->log("Removing {$versions['total']} outdated package versions for {$latestPackage->package_name}");
58 | /** @var \modTransportPackage $version */
59 | foreach ($versions['collection'] as $version) {
60 | $this->request->log("Removing outdated package version {$version->signature} from {$this->profile->name}");
61 | $version->removePackage(true, false);
62 | $this->removePackageFiles($version->signature);
63 | }
64 | }
65 | }
66 |
67 | $this->request->log("Completed Removing outdated packages for {$this->profile->name}");
68 | } catch (\Exception $e) {
69 | throw new ActionException($this, "Error removing outdated packages: {$e->getMessage()}", $e);
70 | }
71 | }
72 |
73 | protected function removePackageFiles($signature)
74 | {
75 | if (!$this->preserveZip && file_exists(MODX_CORE_PATH . "packages/{$signature}.transport.zip")) {
76 | if (@unlink(MODX_CORE_PATH . "packages/{$signature}.transport.zip")) {
77 | $this->request->log('Removing package ' . MODX_CORE_PATH . "packages/{$signature}.transport.zip");
78 | } else {
79 | $this->request->log('Error removing package ' . MODX_CORE_PATH . "packages/{$signature}.transport.zip");
80 | }
81 | }
82 | if (is_dir(MODX_CORE_PATH . "packages/{$signature}")) {
83 | if ($this->modx->getCacheManager()->deleteTree(
84 | MODX_CORE_PATH . "packages/{$signature}",
85 | array('deleteTop' => true, 'skipDirs' => false, 'extensions' => array())
86 | )) {
87 | $this->request->log('Removing package directory ' . MODX_CORE_PATH . "packages/{$signature}");
88 | } else {
89 | $this->request->log('Error removing package directory ' . MODX_CORE_PATH . "packages/{$signature}");
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tpl/resource_children.tpl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "resource_children",
3 | "description": "Package all children of a specified parent Resource. Requires --parent=(int) argument.",
4 | "vehicles": [
5 | {
6 | "vehicle_class": "xPDOObjectVehicle",
7 | "object": {
8 | "class": "modResource",
9 | "criteria": {
10 | "parent": {+parent}
11 | },
12 | "graph": {
13 | "Template": [
14 |
15 | ],
16 | "ContentType": [
17 |
18 | ],
19 | "TemplateVarResources": {
20 | "TemplateVar": [
21 |
22 | ]
23 | },
24 | "ResourceGroupResources": {
25 | "ResourceGroup": [
26 |
27 | ]
28 | },
29 | "ContextResources": {
30 | "Context": [
31 |
32 | ]
33 | }
34 | },
35 | "package": "modx",
36 | "script": "extract\/modresourcechildren.php"
37 | },
38 | "attributes": {
39 | "preserve_keys": false,
40 | "update_object": true,
41 | "unique_key": [
42 | "context_key",
43 | "uri"
44 | ],
45 | "related_objects": true,
46 | "related_object_attributes": {
47 | "Template": {
48 | "preserve_keys": false,
49 | "update_object": true,
50 | "unique_key": "templatename"
51 | },
52 | "ContentType": {
53 | "preserve_keys": false,
54 | "update_object": true,
55 | "unique_key": "name"
56 | },
57 | "TemplateVarResources": {
58 | "preserve_keys": false,
59 | "update_object": true,
60 | "unique_key": [
61 | "tmplvarid",
62 | "contentid"
63 | ],
64 | "related_objects": true,
65 | "related_object_attributes": {
66 | "TemplateVar": {
67 | "preserve_keys": false,
68 | "update_object": true,
69 | "unique_key": "name"
70 | }
71 | }
72 | },
73 | "ResourceGroupResources": {
74 | "preserve_keys": false,
75 | "update_object": true,
76 | "unique_key": [
77 | "document_group",
78 | "document"
79 | ],
80 | "related_objects": true,
81 | "related_object_attributes": {
82 | "ResourceGroup": {
83 | "preserve_keys": false,
84 | "update_object": true,
85 | "unique_key": "name"
86 | }
87 | }
88 | },
89 | "ContextResources": {
90 | "preserve_keys": true,
91 | "update_object": true,
92 | "related_objects": true,
93 | "related_object_attributes": {
94 | "Context": {
95 | "preserve_keys": true,
96 | "update_object": false
97 | }
98 | }
99 | }
100 | }
101 | }
102 | }
103 | ]
104 | }
105 |
--------------------------------------------------------------------------------
/src/Teleport/Request/CLIRequest.php:
--------------------------------------------------------------------------------
1 | action = null;
33 | $this->arguments = array();
34 | $parsed = array();
35 | $argument = reset($args);
36 | while ($argument) {
37 | if (strpos($argument, '=') > 0) {
38 | $arg = explode('=', $argument);
39 | $argKey = ltrim($arg[0], '-');
40 | $argValue = trim($arg[1], '"');
41 | $parsed[$argKey] = $argValue;
42 | } else {
43 | $parsed[ltrim($argument, '-')] = true;
44 | }
45 | $argument = next($args);
46 | }
47 | if (!isset($parsed['action']) || empty($parsed['action'])) {
48 | if (isset($parsed['version']) || isset($parsed['V'])) {
49 | $this->action = 'Version';
50 | $this->arguments = $parsed;
51 | return $this->arguments;
52 | }
53 | if (isset($parsed['help']) || isset($parsed['h'])) {
54 | $this->action = 'Help';
55 | $this->arguments = $parsed;
56 | return $this->arguments;
57 | }
58 | throw new RequestException($this, "No valid action argument specified.");
59 | }
60 | $this->action = $parsed['action'];
61 | unset($parsed['action']);
62 | $this->arguments = $parsed;
63 | return $this->arguments;
64 | }
65 |
66 | public function beforeHandle(Action &$action)
67 | {
68 | if (!$this->switchUser()) {
69 | throw new RequestException($this, 'error switching user for teleport execution');
70 | }
71 | }
72 |
73 | /**
74 | * Switch the user executing the current process.
75 | *
76 | * If user arg is provided and the current user does not match, attempt to
77 | * switch to this user via posix_ functions.
78 | *
79 | * @return bool True if the user and group were successfully switched, the
80 | * process is already running as the requested user, or no user arg was
81 | * provided.
82 | */
83 | private function switchUser()
84 | {
85 | if (isset($this->user) && function_exists('posix_getpwnam')) {
86 | $current_user = @posix_getpwuid(@posix_getuid());
87 | if (!is_array($current_user)) {
88 | $this->log("user switch to {$this->user} failed: could not determine current username");
89 | return false;
90 | }
91 | if ($current_user['name'] !== $this->user) {
92 | $u = @posix_getpwnam($this->user);
93 | if (!is_array($u)) {
94 | $this->log("user switch failed: could not find user {$this->user}");
95 | return false;
96 | }
97 | if (!@posix_setgid($u['gid'])) {
98 | $this->log("warning: error switching group for {$this->user} to gid {$u['gid']}");
99 | }
100 | if (!@posix_setuid($u['uid'])) {
101 | $this->log("user switch failed: could not switch to {$this->user} using uid {$u['uid']}");
102 | return false;
103 | }
104 | $current_user = @posix_getpwuid(@posix_getuid());
105 | if (is_array($current_user) && $current_user['name'] === $this->user) {
106 | $this->log("user switch successful: teleport running as {$this->user}");
107 | }
108 | } else {
109 | $this->log("teleport already running as user {$this->user}...");
110 | }
111 | }
112 | return true;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Teleport/Transport/MySQLVehicle.php:
--------------------------------------------------------------------------------
1 | payload['class'] = $this->class;
32 | if (is_array($object) && isset($object['table']) && isset($object['tableName'])) {
33 | $this->payload['object'] = $object;
34 | }
35 | parent :: put($transport, $object, $attributes);
36 | }
37 |
38 | /**
39 | * Install the vehicle artifact into a transport host.
40 | *
41 | * @param \xPDOTransport &$transport A reference to the transport.
42 | * @param array $options An array of options for altering the installation of the artifact.
43 | *
44 | * @return boolean True if the installation of the vehicle artifact was successful.
45 | */
46 | public function install(& $transport, $options)
47 | {
48 | $installed = false;
49 | $vOptions = $this->get($transport, $options);
50 | if (isset($vOptions['object']) && isset($vOptions['object']['tableName']) && isset($vOptions['object']['table']) && isset($vOptions['object']['data'])) {
51 | $tableName = $vOptions['object']['tableName'];
52 | /* attempt to execute the drop table if exists script */
53 | $dropTableQuery = isset($vOptions['object']['drop']) && !empty($vOptions['object']['drop'])
54 | ? $vOptions['object']['drop']
55 | : "DROP TABLE IF EXISTS {$transport->xpdo->escape('[[++table_prefix]]' . $tableName)}";
56 | $tableDropped = $transport->xpdo->exec(str_replace("[[++table_prefix]]", $transport->xpdo->getOption('table_prefix', $options, ''), $dropTableQuery));
57 | if ($tableDropped === false) {
58 | $transport->xpdo->log(\xPDO::LOG_LEVEL_WARN, "Error executing drop table script for table {$tableName}:\n{$dropTableQuery}");
59 | }
60 | /* attempt to execute the table creation script */
61 | $tableCreationQuery = str_replace("[[++table_prefix]]", $transport->xpdo->getOption('table_prefix', $options, ''), $vOptions['object']['table']);
62 | $tableCreated = $transport->xpdo->exec($tableCreationQuery);
63 | if ($tableCreated !== false) {
64 | /* insert data rows into the table */
65 | if (is_array($vOptions['object']['data'])) {
66 | $rowsCreated = 0;
67 | foreach ($vOptions['object']['data'] as $idx => $row) {
68 | $insertResult = $transport->xpdo->exec(str_replace("[[++table_prefix]]", $transport->xpdo->getOption('table_prefix', $options, ''), $row));
69 | if ($insertResult === false) {
70 | $transport->xpdo->log(\xPDO::LOG_LEVEL_INFO, "Error inserting row {$idx} into table {$tableName}: " . print_r($transport->xpdo->errorInfo(), true));
71 | } else {
72 | $rowsCreated++;
73 | }
74 | }
75 | $transport->xpdo->log(\xPDO::LOG_LEVEL_INFO, "Inserted {$rowsCreated} rows into table {$tableName}");
76 | }
77 | } else {
78 | $transport->xpdo->log(\xPDO::LOG_LEVEL_ERROR, "Could not create table {$tableName}: " . print_r($transport->xpdo->errorInfo(), true));
79 | }
80 | }
81 | return $installed;
82 | }
83 |
84 | /**
85 | * This vehicle implementation does not support uninstall.
86 | *
87 | * @param \xPDOTransport &$transport A reference to the transport.
88 | * @param array $options An array of options for altering the uninstallation of the artifact.
89 | *
90 | * @return boolean True, always.
91 | */
92 | public function uninstall(& $transport, $options)
93 | {
94 | return true;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/doc/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Teleport is an extensible scripting toolkit for working with one or more local MODX Revolution installations. It currently functions primarily as a packaging toolkit which extends the MODX Transport APIs and provides commands for extracting and injecting customizable snapshots of MODX deployments. But it can be extended easily to perform an infinite variety of actions related to MODX.
4 |
5 |
6 | ## Install
7 |
8 | There are various methods for installing Teleport so you can begin using it. Choose the one that best fits your needs:
9 |
10 | * [Phar](install/phar.md) - The easiest way to start using Teleport is to download the [latest `teleport.phar`](http://modx.s3.amazonaws.com/releases/teleport/teleport.phar) and put it in a directory where the application can do it's work. In this distribution, Teleport will attempt to create subdirectories called profile/ and workspace/ where it will store generated site Profiles and Extracted transport packages, respectively. The phar distribution includes all dependencies required to use Teleport. Note however that you cannot run the Teleport HTTP Server from the phar distribution; it is not included in the Phar.
11 |
12 | * [Releases](install/releases.md) - You can download zip and tar.gz formats of any Teleport release, extract them in an appropriate location, and run `composer install` in that location to download the required dependencies. GitHub releases of the project are available [here](https://github.com/modxcms/teleport/releases).
13 |
14 | * [Git Clone](install/git-clone.md) - Contributors can clone the teleport repository or their own fork of it from GitHub, and run `composer install --dev` in that location to download the required dependencies, including those specified in the require-dev section of the `composer.json`.
15 |
16 |
17 | ## Use
18 |
19 | ### Actions
20 |
21 | At the most basic level, you interact with the Teleport application by calling a specific Teleport action with any number of arguments.
22 |
23 | Currently, Teleport provides the following Actions:
24 |
25 | * [Profile](use/profile.md) - Generate a JSON profile of a MODX installation that can be used by other Actions to interact with it.
26 |
27 | * [Extract](use/extract.md) - Extract files, data, and/or other artifacts described by a specified [extract tpl](use/extract/tpl.md), from a MODX installation described by a specified profile, and package them into a transport that can later be Injected into any other MODX installation.
28 |
29 | * [Inject](use/inject.md) - Inject a specified transport package generated by a Teleport Extract, into a MODX installation described by a specified profile.
30 |
31 | * [Push](use/push.md) - Push a specified source file to a specified target location.
32 |
33 | * [UserCreate](use/user-create.md) - Create a User in a MODX installation described by a specified profile.
34 |
35 | * [Packages/GC](use/packages/gc.md) - Remove outdated versions of packages from a MODX installation described by a specified profile.
36 |
37 | * [Workspace/GC](use/workspace/gc.md) - Empty the contents of the Teleport workspace.
38 |
39 | ### CLI Requests
40 |
41 | The simplest way to use Teleport is by calling Teleport Actions on the command line. See documentation for an Action to discover the required and optional arguments for calling that specific Teleport Action.
42 |
43 | ### Teleport HTTP Server
44 |
45 | Teleport comes with a [custom HTTP server](use/server.md), built with [ReactPHP](http://reactphp.org/) that can be run on any server port. This allows access to Teleport Actions over HTTP, bypassing timeout and other issues that can occur when attempting to execute long-running processes using your public web server configuration.
46 |
47 | ## Extend
48 |
49 | Teleport is intended to be easily extensible in a couple of ways. For best results, it is recommended you create your own project using Teleport as a library where you can track your custom additions to the application in the VCS of your choice. The easiest way to get started with a custom Teleport project is by using Composer's create-project command from the modxcms/teleport-project boilerplate package.
50 |
51 | php composer.phar create-project --prefer-source --stability=dev modxcms/teleport-project teleport-opengeek/
52 |
53 | You can find more information about using the boilerplate package at the [GitHub project](https://github.com/modxcms/teleport-project "Teleport boilerplate project").
54 |
55 | ### Custom Extracts
56 |
57 | The Teleport [Extract Action](use/extract.md) uses a JSON [extract tpl](use/extract/tpl.md) to describe how a transport package is created from a variety of MODX site resources. You can easily create your own tpls to quickly build custom packages for an infinite variety of purposes from backing up sites, to building extras, to creating custom deployment workflows. See [Creating Custom Extract Tpls](extend/custom-extract-tpls.md) for more information.
58 |
59 | ### Custom Teleport Actions
60 |
61 | Coming soon: the ability to define [Custom Actions](extend/custom-actions.md) when using Teleport as a library in your application.
62 |
63 |
64 | ## Contribute
65 |
66 | We welcome all contributions to the Teleport application and library. [Learn more](contribute.md) about the variety of ways you can contribute and how.
67 |
--------------------------------------------------------------------------------
/tpl/scripts/extract/changeset.php:
--------------------------------------------------------------------------------
1 | modx;
18 | $this->modx->getService('registry', 'registry.modRegistry');
19 | $this->modx->registry->getRegister('changes', 'registry.modDbRegister', array('directory' => 'changes'));
20 |
21 | $this->modx->registry->changes->connect();
22 | $this->modx->registry->changes->subscribe("/{$changeSet}/");
23 |
24 | $changes = $this->modx->registry->changes->read(
25 | array(
26 | 'remove_read' => $removeRead,
27 | 'msg_limit' => 10000,
28 | 'include_keys' => true
29 | )
30 | );
31 | $count = count($changes);
32 |
33 | if ($count > 0) {
34 | $this->modx->log(modX::LOG_LEVEL_INFO, "Extracting changeset {$changeSet}: {$count} total changes");
35 | foreach ($changes as $key => $change) {
36 | $data = $this->modx->fromJSON($change);
37 | if (!isset($data['action']) || !isset($data['class'])) {
38 | $this->modx->log(modX::LOG_LEVEL_ERROR, "changeset {$changeSet}: invalid data defined in change {$key}:\n" . print_r($data, true));
39 | continue;
40 | }
41 | switch ($data['action']) {
42 | case 'save':
43 | /* create an xPDOObject vehicle */
44 | $object = $this->modx->newObject($data['class']);
45 | if (!$object) {
46 | $this->modx->log(modX::LOG_LEVEL_ERROR, "Error getting instance of class {$data['class']}");
47 | break;
48 | }
49 | $object->fromArray($data['object'], '', true, true);
50 | if ($this->package->put(
51 | $object,
52 | array(
53 | 'vehicle_class' => 'xPDOObjectVehicle',
54 | xPDOTransport::UPDATE_OBJECT => true,
55 | xPDOTransport::PRESERVE_KEYS => true,
56 | )
57 | )) {
58 | $vehicleCount++;
59 | } else {
60 | $this->modx->log(modX::LOG_LEVEL_ERROR, "Error creating vehicle for change {$key} — {$data['action']} - {$data['class']}::" . implode('-', (array)$data['pk']));
61 | }
62 | break;
63 | case 'remove':
64 | /* add an xPDOScript vehicle to remove the object */
65 | if ($this->package->put(
66 | array(
67 | 'vehicle_class' => 'xPDOScriptVehicle',
68 | 'source' => 'tpl/scripts/remove.object.php',
69 | 'class' => $data['class'],
70 | 'pk' => $data['pk']
71 | ),
72 | array(
73 | 'vehicle_class' => 'xPDOScriptVehicle'
74 | )
75 | )) {
76 | $vehicleCount++;
77 | } else {
78 | $this->modx->log(modX::LOG_LEVEL_ERROR, "Error creating vehicle for change {$key} — {$data['action']} - {$data['class']}::" . implode('-', (array)$data['pk']));
79 | }
80 | break;
81 | case 'updateCollection':
82 | case 'removeCollection':
83 | /* add an xPDOScript vehicle to execute an xPDOQuery object */
84 | if ($this->package->put(
85 | array(
86 | 'vehicle_class' => 'xPDOScriptVehicle',
87 | 'source' => 'tpl/scripts/exec.xpdoquery.php',
88 | 'class' => $data['class'],
89 | 'criteria' => $data['criteria']
90 | ),
91 | array(
92 | 'vehicle_class' => 'xPDOScriptVehicle'
93 | )
94 | )) {
95 | $vehicleCount++;
96 | } else {
97 | $this->modx->log(modX::LOG_LEVEL_ERROR, "Error creating vehicle for change {$key} - {$data['action']} - {$data['class']}");
98 | }
99 | break;
100 | default:
101 | /* wth? */
102 | $this->modx->log(modX::LOG_LEVEL_ERROR, "Changeset {$changeSet}: invalid action defined for change {$key}:\n" . print_r($data, true));
103 | break;
104 | }
105 | }
106 | } else {
107 | $this->modx->log(modX::LOG_LEVEL_INFO, "Changeset {$changeSet} is empty");
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/doc/use/extract/tpl.md:
--------------------------------------------------------------------------------
1 | # Extract tpls
2 |
3 | Extract tpls are JSON templates defining what, when, and how various objects, scripts, or file artifacts are to be extracted and packaged into a Teleport transport package.
4 |
5 |
6 | ## The properties of an Extract tpl
7 |
8 | Each Extract tpl defines the attributes and vehicles that make up the manifest of a Teleport transport package. The manifest defines what is packaged and in what order.
9 |
10 | ### name
11 |
12 | Every tpl has a name property which helps identify the tpl used to create a transport package. This name is included in the generated package filename.
13 |
14 | ### attributes
15 |
16 | The attributes of a Teleport transport package can be set to make distributable packages that can be installed from within the MODX package management interface. You can set readme, changelog, and license attributes, as well as the `requires` attribute supported in MODX >=2.4 to specify package dependencies.
17 |
18 | Note that you can also define an attribute as an object with the properties `sourceType` and `source`. For now, `fileContent` is the only supported `sourceType`, which uses the `source` attribute value as a file path or stream URL from which to read the value of the attribute.
19 |
20 | "attributes": {
21 | "changelog": {
22 | "sourceType": "fileContent",
23 | "source": "{+properties.modx.core_path}components/test/changelog.txt"
24 | },
25 | "requires": {
26 | "collections": "~3.0"
27 | }
28 | }
29 |
30 | This makes it possible to use Teleport to create Extras packages that are ready to install from within MODX. Care will just need to be taken not to use any Teleport-specific `vehicle` classes in the packages created for this purpose.
31 |
32 | ### vehicles
33 |
34 | Teleport transport vehicles define artifacts that are to be packaged when the tpl is used in an Extract action. They can be core xPDOVehicle classes or they can be Teleport-specific or even custom implementations which extend any of the core xPDOVehicle classes.
35 |
36 | For example, the following defines a single vehicle that packages files from a specified source into a specified target:
37 |
38 | "vehicles": [
39 | {
40 | "vehicle_class": "xPDOFileVehicle",
41 | "object": {
42 | "source": "{+properties.modx.core_path}components/test",
43 | "target": "return MODX_CORE_PATH . 'components';"
44 | },
45 | "attributes": {"vehicle_class": "xPDOFileVehicle"}
46 | }
47 | ]
48 |
49 | Or here is a more complex definition that packages all system and context settings from the MODX database:
50 |
51 | "vehicles": [
52 | {
53 | "vehicle_class": "xPDOObjectVehicle",
54 | "object": {
55 | "class": "modSystemSetting",
56 | "criteria": [
57 | "1 = 1"
58 | ],
59 | "package": "modx"
60 | },
61 | "attributes": {
62 | "preserve_keys": true,
63 | "update_object": true
64 | }
65 | },
66 | {
67 | "vehicle_class": "xPDOObjectVehicle",
68 | "object": {
69 | "class": "modContextSetting",
70 | "criteria": [
71 | "1 = 1"
72 | ],
73 | "package": "modx"
74 | },
75 | "attributes": {
76 | "preserve_keys": true,
77 | "update_object": true
78 | }
79 | }
80 | ]
81 |
82 |
83 | ## Included tpls
84 |
85 | * `changeset.tpl.json` - Extract a defined set of changes being recorded by callback functions in the MODX configuration.
86 | * `complete.tpl.json` - Extract all core objects, files, and custom database tables from a MODX deployment for replacing an entire deployment.
87 | * `complete_db.tpl.json` - Extract all core objects and custom database tables from a MODX deployment.
88 | * `develop.tpl.json` - Extract all core objects, files, and custom database tables from a MODX deployment to inject into another deployment, supplementing existing objects and custom tables.
89 | * `elements.tpl.json` - Extract all Elements and related data from a MODX deployment to inject into another deployment, updating and supplementing existing Elements.
90 | * `packages.tpl.json` - Extract all Packages registered in a MODX deployment to inject into another deployment.
91 | * `promote.tpl.json` - Extract core objects, files, and custom database tables except settings from a MODX deployment to inject into another deployment.
92 | * [`resource_children.tpl.json`](tpl/resource_children.md) - Extract all Resources that are children of a specified parent Resource.
93 | * `resources.tpl.json` - Extract all Resources from a MODX deployment to inject into another deployment, updating and supplementing existing Resources.
94 | * `settings.tpl.json` - Extract all Settings from a MODX deployment to inject into another deployment, updating and supplementing existing Settings.
95 | * `user.tpl.json` - Extract a single User and related data from a MODX deployment to inject into another deployment.
96 | * `users.tpl.json` - Extract all Users from a MODX deployment to inject into another deployment, updating and supplementing existing Users.
97 |
98 |
99 | ## Custom tpls
100 |
101 | You can create and use your own custom tpls with Teleport. See [Extending Teleport with Custom Extract Tpls](../../extend/custom-extract-tpls.md) to get started.
102 |
--------------------------------------------------------------------------------
/src/Teleport/Action/Action.php:
--------------------------------------------------------------------------------
1 | request =& $request;
38 | }
39 |
40 | /**
41 | * Get a request argument or member variable value from this Action.
42 | *
43 | * @param string $name The name of the argument or variable.
44 | *
45 | * @return array|mixed|null The value of the argument or variable.
46 | */
47 | public function __get($name)
48 | {
49 | return $this->request->args($name);
50 | }
51 |
52 | /**
53 | * Check if a request argument or member variable is set for this Action.
54 | *
55 | * @param string $name The name of the argument of variable.
56 | *
57 | * @return bool TRUE if the argument or member variable is set, FALSE otherwise.
58 | */
59 | public function __isset($name)
60 | {
61 | return isset($this->request->$name);
62 | }
63 |
64 | /**
65 | * Set an argument or member variable value for this Action.
66 | *
67 | * @param string $name
68 | * @param mixed $value
69 | */
70 | public function __set($name, $value)
71 | {
72 | if (is_string($name) && $name !== '') {
73 | $this->request->$name = $value;
74 | }
75 | }
76 |
77 | /**
78 | * Get a MODX reference to operate on.
79 | *
80 | * @param \stdClass $profile An object describing properties of a MODX
81 | * instance.
82 | *
83 | * @return \modX A reference to a MODX instance.
84 | */
85 | public function &getMODX($profile)
86 | {
87 | if (!$this->modx instanceof \modX) {
88 | $results = $this->request->getResults();
89 | $this->modx = Teleport::instance()->getMODX($profile, $this->request->args(), $results);
90 | }
91 | return $this->modx;
92 | }
93 |
94 | /**
95 | * Get a reference to the Teleport request handler.
96 | *
97 | * @return \Teleport\Request\RequestInterface The Teleport request handler.
98 | */
99 | public function &getRequest()
100 | {
101 | return $this->request;
102 | }
103 |
104 | /**
105 | * Process this action.
106 | *
107 | * @throws \Teleport\Action\ActionException If an error is encountered processing this Action.
108 | * @return void
109 | */
110 | public function process()
111 | {
112 | $this->validate();
113 | }
114 |
115 | /**
116 | * Validate the arguments specified for this Action.
117 | *
118 | * @throws \Teleport\Action\ActionException If required arguments are not specified.
119 | * @return bool TRUE if the arguments are valid, FALSE otherwise.
120 | */
121 | public function validate()
122 | {
123 | if (!empty($this->required)) {
124 | $invalid = array_diff($this->required, array_keys($this->request->args()));
125 | if (!empty($invalid)) {
126 | foreach ($invalid as $argKey) {
127 | $this->request->addResult("{$argKey} required for this request.");
128 | }
129 | throw new ActionException($this, "Required arguments " . implode(', ', $invalid) . " not specified.");
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * Pull a source to a specified target location.
136 | *
137 | * @param string $source A valid stream URI or file path to the snapshot source.
138 | * @param string $target A valid stream URI or file path to copy the snapshot to.
139 | *
140 | * @return bool Returns true if the pull was completed successfully.
141 | */
142 | public function pull($source, $target)
143 | {
144 | $pulled = false;
145 | if ($this->modx->getCacheManager()) {
146 | $pulled = $this->modx->cacheManager->copyFile($source, $target, array('copy_preserve_permissions' => true));
147 | }
148 | return $pulled;
149 | }
150 |
151 | /**
152 | * Push a source to a specified target location.
153 | *
154 | * @param string $source A valid file or stream location source.
155 | * @param string $target A valid file or stream location target.
156 | *
157 | * @return bool Returns true if the source was pushed successfully to the target, false otherwise.
158 | */
159 | public function push($source, $target)
160 | {
161 | $pushed = false;
162 | if ($this->modx->getCacheManager()) {
163 | $pushed = $this->modx->cacheManager->copyFile($source, $target, array('copy_preserve_permissions' => true));
164 | }
165 | return $pushed;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/doc/extend/custom-extract-tpls.md:
--------------------------------------------------------------------------------
1 | # Custom Extract tpls
2 |
3 | NOTE: If you want to maintain a custom Teleport project with your own templates so that you can compile your own custom phar or track your project in version control, you should start a new project using the [Teleport boilerplate project](https://github.com/modxcms/teleport-project). This will allow you to maintain your own custom code and update the core Teleport code and artifacts as dictated by your own project requirements.
4 |
5 |
6 | ## Defining a tpl
7 |
8 | An Extract tpl is a JSON file that describes how to build a Teleport transport package. It consists of three main properties: a `name`, some package `attributes`, and a collection of one or more `vehicles`.
9 |
10 | ### Naming the tpl
11 |
12 | An Extract tpl's `name` is used when producing the file name of an Extracted transport package. For this reason, you should avoid the `-` or other special characters in the tpl `name`. Use `_` for word separators if you need them.
13 |
14 | ### Attributes for the package
15 |
16 | *available in teleport >=1.2.0*
17 |
18 | The `attributes` property provides a way to set the attributes of the transport package that is produced by the Extract. For instance, this can be used to set package attributes used by package management when installing Extras, allowing you to use Teleport as an easy way to create installable transport packages for use within any MODX installation. NOTE however that when producing packages to be used as MODX Extras, you must avoid the use of Teleport-specific transport vehicle classes. Limit the vehicles you define for these packages to those available in xPDO itself (`xPDOObjectVehicle`, `xPDOFileVehicle`, `xPDOScriptVehicle`, or `xPDOTransportVehicle`).
19 |
20 | Here is an example use of the `attributes` property that could be used to create an installable Extras package which. This theoretical `test` component located in the MODX `core/components/` directory specifies a dependency on the collections extra at version 3.x (for MODX 2.4 package dependency features) and includes the content of a changelog.txt file located within the component's directory as the `changelog` attribute:
21 |
22 | "attributes": {
23 | "changelog": {
24 | "sourceType": "fileContent",
25 | "source": "{+properties.modx.core_path}components/test/changelog.txt"
26 | },
27 | "requires": {
28 | "collections": "~3.0"
29 | }
30 | },
31 |
32 | As you can see in this example, individual attributes can get their content from a file available to the Extract by setting the value of the attribute to an object with the properties `sourceType` and `source` where the `sourceType` value is set to `fileContent` and the `source` is the full path to the file to use the content from. Other `sourceType`'s may be supported in the future.
33 |
34 | ### Describing the Vehicles
35 |
36 | The `vehicles` property describes a collection of xPDO transport vehicles that will be included in the package in the order they are described. The vehicles can be core xPDOVehicle derivatives, Teleport-specific derivatives, or even custom derivatives.
37 |
38 | A simple example from the `settings.tpl.json` included with Teleport describes two xPDOObject vehicles that will package all System and Context Settings from a MODX installation:
39 |
40 | "vehicles": [
41 | {
42 | "vehicle_class": "xPDOObjectVehicle",
43 | "object": {
44 | "class": "modSystemSetting",
45 | "criteria": [
46 | "1 = 1"
47 | ],
48 | "package": "modx"
49 | },
50 | "attributes": {
51 | "preserve_keys": true,
52 | "update_object": true
53 | }
54 | },
55 | {
56 | "vehicle_class": "xPDOObjectVehicle",
57 | "object": {
58 | "class": "modContextSetting",
59 | "criteria": [
60 | "1 = 1"
61 | ],
62 | "package": "modx"
63 | },
64 | "attributes": {
65 | "preserve_keys": true,
66 | "update_object": true
67 | }
68 | }
69 | ]
70 |
71 | When [Inject](../use/inject.md)ed, the packaged object vehicles will preserve their payload's primary keys and update existing objects in the injection target that match by primary key, as described in the attributes provided for each vehicle in the collection.
72 |
73 | Each vehicle consists of an optional `vehicle_package`, a `vehicle_class`, an `object` defining the vehicle payload, and optional `attributes` of the vehicle. The `vehicle_package` should be omitted when using the core xPDOVehicle implementations. The `object` will be unique to each `vehicle_class` implementation.
74 |
75 | #### xPDOFileVehicle
76 |
77 | * `source`: the absolute path to a file or directory to be packaged
78 | * `target`: a PHP expression that will be `eval()`'d during installation to determine where the file/directory is unpacked
79 |
80 | #### xPDOObjectVehicle
81 |
82 | * `class`: defines the xPDOObject class to be packaged by the vehicle
83 | * `criteria`: an array or object describing the criteria that will be used to select instances of the specified `class`. The default value of `["1 = 1"]` is used to return true for all objects, but can be replaced with other statements like `["id:!=":2]`.
84 | * `graph`: defines an object graph to use to package related xPDOObjects
85 | * `graphCriteria`: defines the criteria for filtering related xPDOObjects selected by a `graph`
86 | * `script`: an optional script to be used to create the vehicle or vehicles for this vehicle definition
87 | * `package`: the xPDO package name for the specified `class`
88 |
89 | #### xPDOScriptVehicle
90 |
91 | * `source`: a script to be executed during installation of the vehicle
92 |
93 |
--------------------------------------------------------------------------------
/src/Teleport/Transport/xPDOCollectionVehicle.php:
--------------------------------------------------------------------------------
1 | payload['guid'] = md5(uniqid(rand(), true));
32 | if (is_array($object)) {
33 | if (!isset($this->payload['object']) || !is_array($this->payload['object'])) {
34 | $this->payload['object'] = array();
35 | }
36 | $obj = reset($object);
37 | if (!isset ($this->payload['package'])) {
38 | if ($obj instanceof \xPDOObject) {
39 | $packageName = $obj->_package;
40 | } else {
41 | $packageName = '';
42 | }
43 | $this->payload['package'] = $packageName;
44 | }
45 | if (!isset($this->payload['class']) && $obj instanceof \xPDOObject) {
46 | $this->payload['class'] = $obj->_class;
47 | }
48 | while ($obj) {
49 | $payload = array_merge($attributes, array(
50 | 'package' => $this->payload['package'],
51 | 'class' => $this->payload['class'],
52 | ));
53 | if ($obj instanceof \xPDOObject) {
54 | $nativeKey = $obj->getPrimaryKey();
55 | $payload['object'] = $obj->toJSON('', true);
56 | $payload['guid'] = md5(uniqid(rand(), true));
57 | $payload['native_key'] = $nativeKey;
58 | $payload['signature'] = md5($payload['class'] . '_' . $payload['guid']);
59 | if (isset ($payload[\xPDOTransport::RELATED_OBJECTS]) && !empty ($payload[\xPDOTransport::RELATED_OBJECTS])) {
60 | $relatedObjects = array();
61 | foreach ($obj->_relatedObjects as $rAlias => $related) {
62 | if (is_array($related)) {
63 | foreach ($related as $rKey => $rObj) {
64 | if (!isset ($relatedObjects[$rAlias])) $relatedObjects[$rAlias] = array();
65 | $guid = md5(uniqid(rand(), true));
66 | $relatedObjects[$rAlias][$guid] = array();
67 | $this->_putRelated($transport, $rAlias, $rObj, $relatedObjects[$rAlias][$guid]);
68 | }
69 | } elseif (is_object($related)) {
70 | if (!isset ($relatedObjects[$rAlias])) $relatedObjects[$rAlias] = array();
71 | $guid = md5(uniqid(rand(), true));
72 | $relatedObjects[$rAlias][$guid] = array();
73 | $this->_putRelated($transport, $rAlias, $related, $relatedObjects[$rAlias][$guid]);
74 | }
75 | }
76 | if (!empty ($relatedObjects)) $payload['related_objects'] = $relatedObjects;
77 | }
78 | } elseif (is_object($obj)) {
79 | $payload['object'] = $transport->xpdo->toJSON(get_object_vars($obj));
80 | $payload['native_key'] = $payload['guid'];
81 | $payload['signature'] = md5($payload['class'] . '_' . $payload['guid']);
82 | }
83 | $this->payload['object'][] = $payload;
84 | $obj = next($object);
85 | }
86 | }
87 | parent :: put($transport, $object, $attributes);
88 | }
89 |
90 | /**
91 | * Install the vehicle artifact into a transport host.
92 | *
93 | * @param \xPDOTransport &$transport A reference to the transport.
94 | * @param array $options An array of options for altering the installation of the artifact.
95 | *
96 | * @return boolean True if the installation of the vehicle artifact was successful.
97 | */
98 | public function install(& $transport, $options)
99 | {
100 | $installed = false;
101 | if (is_array($this->payload['object'])) {
102 | $installed = 0;
103 | foreach ($this->payload['object'] as $payload) {
104 | $parentObj = null;
105 | $parentMeta = null;
106 | if ($this->_installObject($transport, $options, $payload, $parentObj, $parentMeta)) {
107 | $installed++;
108 | }
109 | }
110 | $installed = $installed == count($this->payload['object']) ? true : false;
111 | }
112 | return $installed;
113 | }
114 |
115 | /**
116 | * This vehicle implementation does not yet support uninstall.
117 | *
118 | * @param \xPDOTransport &$transport A reference to the transport.
119 | * @param array $options An array of options for altering the uninstallation of the artifact.
120 | *
121 | * @return boolean True, always.
122 | */
123 | public function uninstall(& $transport, $options)
124 | {
125 | return true;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Teleport/Beam/Endpoint.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Teleport\Beam;
12 |
13 |
14 | use React\Promise\Deferred;
15 | use React\Promise\LazyPromise;
16 | use Teleport\InvalidProfileException;
17 | use Teleport\Teleport;
18 |
19 | /**
20 | * An Endpoint listener that polls a MODX instance for Teleport messages.
21 | *
22 | * @package Teleport\Beam
23 | */
24 | class Endpoint extends Teleport
25 | {
26 | /**
27 | * Get a singleton instance of the Teleport Endpoint listener.
28 | *
29 | * @param array $options An associative array of Teleport Config options for the instance.
30 | * @param bool $forceNew If true, a new instance of Teleport is created and replaces the existing singleton.
31 | *
32 | * @return HttpServer
33 | */
34 | public static function instance(array $options = array(), $forceNew = false)
35 | {
36 | if (self::$instance === null || $forceNew === true) {
37 | self::$instance = new Endpoint($options);
38 | } else {
39 | self::$instance->setConfig($options);
40 | }
41 | return self::$instance;
42 | }
43 |
44 | public static function createTaskFactory(Endpoint $endpoint, $id, $def, $register)
45 | {
46 | return function () use ($endpoint, $id, $def, $register) {
47 | $deferred = new Deferred();
48 |
49 | try {
50 | $request = $endpoint->getRequest('Teleport\\Request\\APIRequest');
51 | $request->handle($def);
52 |
53 | $deferred->resolve($request->getResults());
54 | } catch (\Exception $e) {
55 | $deferred->reject($e->getMessage());
56 | }
57 |
58 | return $deferred->promise();
59 | };
60 | }
61 |
62 | /**
63 | * Bind the Teleport Endpoint listener to the specified MODX profile.
64 | *
65 | * @param string $profile A valid MODX profile describing the instance to
66 | * bind the Endpoint listener to.
67 | * @param array $options An array of options for the Endpoint.
68 | *
69 | * @throws \RuntimeException If an error occurs binding the listener to the
70 | * specified MODX instance.
71 | */
72 | public function run($profile, array $options = array())
73 | {
74 | try {
75 | $profile = self::loadProfile($profile);
76 | $modx = $this->getMODX($profile, array_merge(array('log_target' => 'ECHO'), $options));
77 |
78 | /** @var \modRegistry $registry */
79 | $registry = $modx->getService('registry', 'registry.modRegistry');
80 | $registerName = $this->getConfig()->get('endpoint.registerName', $options, 'endpoint');
81 | $registerClass = $this->getConfig()->get('endpoint.registerClass', $options, 'registry.modFileRegister');
82 | $registerOptions = $this->getConfig()->get('endpoint.registerOptions', $options, array('directory' => $registerName));
83 | $register = $registry->getRegister($registerName, $registerClass, $registerOptions);
84 | $register->connect();
85 | } catch (InvalidProfileException $e) {
86 | throw new \RuntimeException("Could not start Endpoint listener: {$e->getMessage()}", $e->getCode(), $e);
87 | } catch (\Exception $e) {
88 | throw new \RuntimeException("Could not start Endpoint listener: {$e->getMessage()}", $e->getCode(), $e);
89 | }
90 |
91 | $pollInterval = (float)$this->getConfig()->get('endpoint.pollInterval', $options, 1);
92 |
93 | /** @var \React\EventLoop\LibEventLoop $loop */
94 | $loop = \React\EventLoop\Factory::create();
95 |
96 | $self = &$this;
97 |
98 | /* poll MODX registry for Teleportation requests and act on them */
99 | $loop->addPeriodicTimer($pollInterval, function () use ($self, $profile, $modx, $register) {
100 | $register->subscribe('/queue/');
101 | $msgs = $register->read(array(
102 | 'msg_limit' => 1,
103 | 'poll_limit' => 1,
104 | 'remove_read' => true,
105 | 'include_keys' => true
106 | ));
107 | $register->unsubscribe('/queue/');
108 | if (!empty($msgs)) {
109 | $taskMsg = reset($msgs);
110 | $taskId = key($msgs);
111 | if ($self->getConfig()->get('verbose') || $self->getConfig()->get('debug')) {
112 | echo "msg received: {$taskId} => " . print_r($taskMsg, true) . "\n";
113 | }
114 |
115 | $register->subscribe("/tasks/{$taskId}/");
116 |
117 | $task = Endpoint::createTaskFactory($self, $taskId, $taskMsg, $register);
118 |
119 | $promise = new LazyPromise($task);
120 | $promise->then(
121 | function ($value) use ($self, $register, $taskId) {
122 | if ($self->getConfig()->get('verbose') || $self->getConfig()->get('debug')) {
123 | echo "{$taskId} resolved [value => " . print_r($value, true) . "]\n";
124 | }
125 | $register->send("/tasks/{$taskId}/", array(array('completed' => $value)));
126 | },
127 | function ($reason) use ($self, $register, $taskId) {
128 | if ($self->getConfig()->get('verbose') || $self->getConfig()->get('debug')) {
129 | echo "{$taskId} rejected [reason => " . print_r($reason, true) . "]\n";
130 | }
131 | $register->send("/tasks/{$taskId}/", array(array('failed' => $reason)));
132 | },
133 | function ($update) use ($self, $register, $taskId) {
134 | if ($self->getConfig()->get('verbose') || $self->getConfig()->get('debug')) {
135 | echo "{$taskId} progress [update => " . print_r($update, true) . "]\n";
136 | }
137 | $register->send("/tasks/{$taskId}/", array(array('progress' => $update)));
138 | }
139 | );
140 |
141 | $register->unsubscribe("/tasks/{$taskId}/");
142 | }
143 | });
144 |
145 | $loop->run();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Teleport
2 |
3 | Teleport is an extensible scripting toolkit for working with one or more local MODX Revolution installations.
4 |
5 | Teleport currently functions primarily as a packaging toolkit which extends the MODX Transport APIs and provides commands for extracting and injecting customizable snapshots of MODX deployments. But it can be extended easily to perform an infinite variety of actions related to MODX.
6 |
7 |
8 | ## MODX Revolution 3.x
9 |
10 | Please use the [`2.x`](https://github.com/modxcms/teleport/tree/2.x) branch and releases for MODX 3.x support.
11 |
12 |
13 | ## Requirements
14 |
15 | In order to use Teleport, your environment must at least meet the following requirements:
16 |
17 | * PHP >= 5.4
18 | * MODX Revolution >= 2.1 (MySQL)
19 |
20 | You must also be able to run PHP using the CLI SAPI.
21 |
22 | NOTE: At the current time, various Teleport Extract tpls only support MySQL deployments of MODX Revolution.
23 |
24 | Usage on Linux environments with the PHP posix extension can take advantage of advanced user-switching features.
25 |
26 | Teleport strives to be a multi-platform tool, and currently works equally well in Linux and OS X environments. Windows support is unknown at this time; Windows contributors wanted.
27 |
28 |
29 | ## Installation
30 |
31 | There are several methods for installing Teleport. The easiest way to get started is by installing the Teleport Phar distribution.
32 |
33 | _IMPORTANT: Using any of the installation methods, make sure you are running Teleport as the same user PHP runs as when executed by the web server. Failure to do so can corrupt your MODX site by injecting and/or caching files with incorrect file ownership._
34 |
35 | ### Download and Install Phar
36 |
37 | Create a working directory for Teleport and cd to that directory, e.g.
38 |
39 | mkdir ~/teleport/ && cd ~/teleport/
40 |
41 | Download the latest [`teleport.phar`](http://modx.s3.amazonaws.com/releases/teleport/teleport.phar "teleport.phar") distribution of Teleport into your Teleport working directory.
42 |
43 | Create a Profile of a MODX site:
44 |
45 | php teleport.phar --action=Profile --name="MyMODXSite" --code=mymodxsite --core_path=/path/to/mysite/modx/core/ --config_key=config
46 |
47 | Extract a Snapshot from the MODX site you just profiled:
48 |
49 | php teleport.phar --action=Extract --profile=profile/mymodxsite.profile.json --tpl=phar://teleport.phar/tpl/develop.tpl.json
50 |
51 |
52 | ### Other Installation Methods
53 |
54 | Alternatively, you can install Teleport using the source and [Composer](http://getcomposer.org/). Learn more about using [git clone](doc/install/git-clone.md) or a [release archive](doc/install/releases.md).
55 |
56 | _IMPORTANT: If you want to use the Teleport HTTP Server you cannot use the Phar distribution. You MUST use one of the other installation methods._
57 |
58 | ### Teleport in your PATH
59 |
60 | With any of the installation methods you can create an executable symlink called teleport pointing to bin/teleport, or directly to the teleport.phar. You can then simply type `teleport` instead of `bin/teleport` or `php teleport.phar` to execute the teleport application.
61 |
62 |
63 | ## Basic Usage
64 |
65 | In all of the usage examples that follow, call teleport based on how you have installed the application. For example, if you installed from source, substitute `bin/teleport` for `php teleport.phar`; if you have created an executable symlink to the teleport.phar, substitute `teleport` for `php teleport.phar` in the sample commands. The following examples assume you have installed the teleport.phar distribution.
66 |
67 | _NOTE: **Before** using Teleport with a MODX site, you will need to **create a Teleport Profile** from the installed site._
68 |
69 | ### Create a MODX Site Profile
70 |
71 | You can create a Teleport Profile of an existing MODX site using the following command:
72 |
73 | php teleport.phar --action=Profile --name="MySite" --code=mysite --core_path=/path/to/mysite/modx/core/ --config_key=config
74 |
75 | The resulting file would be located at profile/mysite.profile.json and could then be used for Extract or Inject commands to be run against the site represented in the profile.
76 |
77 | Learn more about [Teleport Profiles](doc/use/profile.md).
78 |
79 | ### Extract a Snapshot of a MODX Site
80 |
81 | You can Extract a Teleport snapshot from a MODX site using the following command:
82 |
83 | php teleport.phar --action=Extract --profile=profile/mysite.profile.json --tpl=phar://teleport.phar/tpl/develop.tpl.json
84 |
85 | The snapshot will be located in the workspace/ directory if it is created successfully.
86 |
87 | You can also Extract a Teleport snapshot and push it to any valid stream target using the following command:
88 |
89 | php teleport.phar --action=Extract --profile=profile/mysite.profile.json --tpl=phar://teleport.phar/tpl/develop.tpl.json --target=s3://mybucket/snapshots/ --push
90 |
91 | In either case, the absolute path to the snapshot is returned by the process as the final output. You can use this as the path for an Inject source.
92 |
93 | _NOTE: The workspace copy is removed after it is pushed unless you pass --preserveWorkspace to the CLI command._
94 |
95 | Learn more about the [Teleport Extract](doc/use/extract.md) Action.
96 |
97 | ### Inject a Snapshot into a MODX Site
98 |
99 | You can Inject a Teleport snapshot from any valid stream source into a MODX site using the following command:
100 |
101 | php teleport.phar --action=Inject --profile=profile/mysite.profile.json --source=workspace/mysite_develop-120315.1106.30-2.2.1-dev.transport.zip
102 |
103 | _NOTE: If the source is not within the workspace/ directory a copy will be pulled to that location and then removed after the Inject completes unless --preserveWorkspace is passed._
104 |
105 | Learn more about the [Teleport Inject](doc/use/inject.md) Action.
106 |
107 | ### UserCreate
108 |
109 | You can create a user in a profiled MODX site using the following command:
110 |
111 | php teleport.phar --action=UserCreate --profile=profile/mysite.profile.json --username=superuser --password=password --sudo --active --fullname="Test User" --email=testuser@example.com
112 |
113 | _NOTE: This uses the security/user/create processor from the site in the specified profile to create a user, and the action accepts any properties the processor does._
114 |
115 | Learn more about the [Teleport UserCreate](doc/use/user-create.md) Action.
116 |
117 |
118 | ## Get Started
119 |
120 | Learn more about Teleport in the [documentation](doc/index.md).
121 |
122 | ## License
123 |
124 | Teleport is Copyright (c) MODX, LLC
125 |
126 | For the full copyright and license information, please view the [LICENSE](./LICENSE "LICENSE") file that was distributed with this source code.
127 |
--------------------------------------------------------------------------------
/src/Teleport/Request/Request.php:
--------------------------------------------------------------------------------
1 | arguments);
44 | }
45 | return $isset;
46 | }
47 |
48 | /**
49 | * Get an argument or member variable from this request.
50 | *
51 | * @param string $name The name of the argument or member variable.
52 | *
53 | * @return mixed The value of the argument/variable, or null if not found.
54 | */
55 | public function __get($name)
56 | {
57 | $value = null;
58 | if (array_key_exists($name, $this->arguments)) {
59 | $value = $this->arguments[$name];
60 | }
61 | return $value;
62 | }
63 |
64 | /**
65 | * Set the value of an argument or member variable on this request.
66 | *
67 | * @param string $name The name of the argument or member variable.
68 | * @param mixed $value The value to set for the argument or member variable.
69 | */
70 | public function __set($name, $value)
71 | {
72 | if (!empty($name)) {
73 | $this->arguments[$name] = $value;
74 | }
75 | }
76 |
77 | /**
78 | * Add a message to the request results.
79 | *
80 | * @param string $msg The message to add to the results.
81 | */
82 | public function addResult($msg)
83 | {
84 | $this->results[] = $msg;
85 | }
86 |
87 | /**
88 | * Get one or more arguments from the request.
89 | *
90 | * An empty array will return all arguments.
91 | *
92 | * @param array|string $key The argument key or an array of argument keys.
93 | *
94 | * @return array|mixed|null The value or an array of values for the requested
95 | * argument(s).
96 | */
97 | public function args($key = array())
98 | {
99 | if (is_array($key)) {
100 | $args = array();
101 | if (!empty($key)) {
102 | foreach ($key as $k) $args[$k] = $this->args($k);
103 | } else {
104 | $args = $this->arguments;
105 | }
106 | return $args;
107 | } elseif (is_string($key) && !empty($key)) {
108 | return $this->$key;
109 | } else {
110 | return null;
111 | }
112 | }
113 |
114 | /**
115 | * Get a reference to the results of this Request.
116 | *
117 | * @return array An array of results from this request.
118 | */
119 | public function &getResults()
120 | {
121 | return $this->results;
122 | }
123 |
124 | /**
125 | * Handle the requested action.
126 | *
127 | * @param array $arguments An array of arguments for the request.
128 | * @throws RequestException If an error occurs handling the request.
129 | */
130 | public function handle(array $arguments)
131 | {
132 | $this->parseArguments($arguments);
133 |
134 | $actionClass = $this->getActionClass();
135 | if (class_exists($actionClass, true)) {
136 | try {
137 | /** @var \Teleport\Action\Action $handler */
138 | $handler = new $actionClass($this);
139 | $this->beforeHandle($handler);
140 | $handler->process();
141 | $this->afterHandle($handler);
142 | } catch (\Exception $e) {
143 | throw new RequestException($this, "Error handling {$this->action} Teleport request: " . $e->getMessage(), $e);
144 | }
145 | } else {
146 | throw new RequestException($this, "Unknown action {$this->action} specified in Teleport request.");
147 | }
148 | }
149 |
150 | /**
151 | * Request-specific logic executed before an action is handled.
152 | *
153 | * @param Action &$action The action to be processed.
154 | *
155 | * @throws RequestException If an error occurs which should stop processing of
156 | * the action.
157 | */
158 | public function beforeHandle(Action &$action) { }
159 |
160 | /**
161 | * Request-specific logic executed after an action is handled.
162 | *
163 | * @param Action &$action The action that was just processed.
164 | *
165 | * @throws RequestException If an error occurs.
166 | */
167 | public function afterHandle(Action &$action) { }
168 |
169 | /**
170 | * Handle an action through an APIRequest.
171 | *
172 | * @param array $arguments Arguments for an action request.
173 | *
174 | * @throws RequestException If an error occurs during processing of the action,
175 | * or an unknown action is requested.
176 | */
177 | public function request(array $arguments)
178 | {
179 | $request = new APIRequest();
180 | $request->handle($arguments);
181 | }
182 |
183 | /**
184 | * Log a result message and echo it if verbose is true.
185 | *
186 | * @param string $msg The result message.
187 | * @param bool $timestamp Indicates if the log entry should include a timestamp.
188 | *
189 | * @return void
190 | */
191 | public function log($msg, $timestamp = true)
192 | {
193 | if ($timestamp) {
194 | $timestamp = strftime("%Y-%m-%d %H:%M:%S");
195 | $msg = "[{$timestamp}] {$msg}";
196 | }
197 | $this->addResult($msg);
198 | if ($this->verbose) echo $msg . PHP_EOL;
199 | }
200 |
201 | /**
202 | * Get the action requested.
203 | *
204 | * @return string The action requested.
205 | */
206 | public function getAction()
207 | {
208 | return $this->action;
209 | }
210 |
211 | /**
212 | * Parse the request arguments into a normalized format.
213 | *
214 | * @param array $args An array of arguments to parse.
215 | *
216 | * @return array The normalized array of parsed arguments.
217 | * @throws RequestException If no valid action argument is specified.
218 | */
219 | public function parseArguments(array $args)
220 | {
221 | if (!isset($args['action']) || empty($args['action'])) {
222 | if (isset($args['version']) || isset($args['V'])) {
223 | $this->action = 'Version';
224 | $this->arguments = $args;
225 | return $this->arguments;
226 | }
227 | if (isset($args['help']) || isset($args['h'])) {
228 | $this->action = 'Help';
229 | $this->arguments = $args;
230 | return $this->arguments;
231 | }
232 | throw new RequestException($this, "No valid action argument specified.");
233 | }
234 | $this->action = $args['action'];
235 | unset($args['action']);
236 | $this->arguments = $args;
237 | return $this->arguments;
238 | }
239 |
240 | /**
241 | * Get the Action class to handle.
242 | *
243 | * Uses Teleport\Action as the default namespace to look for the action
244 | * class in unless a namespace is specified in the arguments.
245 | *
246 | * @return string The fully-qualified class name of the Action.
247 | */
248 | private function getActionClass()
249 | {
250 | if (empty($this->namespace)) {
251 | $this->namespace = 'Teleport\\Action';
252 | }
253 | return $this->namespace . '\\' . str_replace('/', '\\', $this->action);
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/Teleport/Transport/Transport.php:
--------------------------------------------------------------------------------
1 | log(\xPDO::LOG_LEVEL_ERROR, "Could not instantiate a valid Transport object from the package {$source} to {$target}. SIG: {$signature} MANIFEST: " . print_r($manifest, 1));
39 | }
40 | $manifestVersion = self::manifestVersion($manifest);
41 | switch ($manifestVersion) {
42 | case '0.1':
43 | $instance->vehicles = self::_convertManifestVer1_1(self::_convertManifestVer1_0($manifest));
44 | case '0.2':
45 | $instance->vehicles = self::_convertManifestVer1_1(self::_convertManifestVer1_0($manifest[self::MANIFEST_VEHICLES]));
46 | $instance->attributes = $manifest[self::MANIFEST_ATTRIBUTES];
47 | break;
48 | case '1.0':
49 | $instance->vehicles = self::_convertManifestVer1_1($manifest[self::MANIFEST_VEHICLES]);
50 | $instance->attributes = $manifest[self::MANIFEST_ATTRIBUTES];
51 | break;
52 | default:
53 | $instance->vehicles = $manifest[self::MANIFEST_VEHICLES];
54 | $instance->attributes = $manifest[self::MANIFEST_ATTRIBUTES];
55 | break;
56 | }
57 | } else {
58 | $xpdo->log(\xPDO::LOG_LEVEL_ERROR, "Could not unpack package {$source} to {$target}. SIG: {$signature}");
59 | }
60 | } else {
61 | $xpdo->log(\xPDO::LOG_LEVEL_ERROR, "Could not unpack package: {$target} is not writable. SIG: {$signature}");
62 | }
63 | } else {
64 | $xpdo->log(\xPDO::LOG_LEVEL_ERROR, "Package {$source} not found. SIG: {$signature}");
65 | }
66 | return $instance;
67 | }
68 |
69 | public function get($objFile, $options = array())
70 | {
71 | $vehicle = null;
72 | $objFile = $this->path . $this->signature . '/' . $objFile;
73 | $vehiclePackage = isset($options['vehicle_package']) ? $options['vehicle_package'] : 'transport';
74 | $vehiclePackagePath = isset($options['vehicle_package_path']) ? $options['vehicle_package_path'] : '';
75 | $vehicleClass = isset($options['vehicle_class']) ? $options['vehicle_class'] : '';
76 | $vehicleParentClass = isset($options['vehicle_parent_class']) ? $options['vehicle_parent_class'] : '';
77 | if (!empty($vehicleParentClass)) {
78 | $this->xpdo->loadClass('transport.' . $vehicleParentClass, '', true, true);
79 | }
80 | if (empty($vehicleClass)) $vehicleClass = $options['vehicle_class'] = 'xPDOObjectVehicle';
81 | if (!empty($vehiclePackage)) {
82 | $vehicleClass = "{$vehiclePackage}.{$vehicleClass}";
83 | $className = $this->xpdo->loadClass($vehicleClass, $vehiclePackagePath, true, true);
84 | } else {
85 | $className = $vehicleClass;
86 | }
87 | if ($className) {
88 | $vehicle = new $className();
89 | if (file_exists($objFile)) {
90 | $payload = include($objFile);
91 | if ($payload) {
92 | $vehicle->payload = $payload;
93 | }
94 | }
95 | } else {
96 | $this->xpdo->log(\xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehicleClass}) could not be loaded.");
97 | }
98 | return $vehicle;
99 | }
100 |
101 | public function put($artifact, $attributes = array())
102 | {
103 | $added = false;
104 | if (!empty($artifact)) {
105 | $vehiclePackage = isset($attributes['vehicle_package']) ? $attributes['vehicle_package'] : 'transport';
106 | $vehiclePackagePath = isset($attributes['vehicle_package_path']) ? $attributes['vehicle_package_path'] : '';
107 | $vehicleClass = isset($attributes['vehicle_class']) ? $attributes['vehicle_class'] : '';
108 | $vehicleParentClass = isset($attributes['vehicle_parent_class']) ? $attributes['vehicle_parent_class'] : '';
109 | if (!empty($vehicleParentClass)) {
110 | $this->xpdo->loadClass('transport.' . $vehicleParentClass, '', true, true);
111 | }
112 | if (empty($vehicleClass)) $vehicleClass = $options['vehicle_class'] = 'xPDOObjectVehicle';
113 | if (!empty($vehiclePackage)) {
114 | $vehicleClass = "{$vehiclePackage}.{$vehicleClass}";
115 | $className = $this->xpdo->loadClass($vehicleClass, $vehiclePackagePath, true, true);
116 | } else {
117 | $className = $vehicleClass;
118 | }
119 | if ($className) {
120 | /** @var \xPDOVehicle $vehicle */
121 | $vehicle = new $className();
122 | $vehicle->put($this, $artifact, $attributes);
123 | if ($added = $vehicle->store($this)) {
124 | $this->registerVehicle($vehicle);
125 | }
126 | } else {
127 | $this->xpdo->log(\xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehiclePackage}.{$vehicleClass}) could not be loaded.");
128 | }
129 | }
130 | return $added;
131 | }
132 |
133 | public function preInstall()
134 | {
135 | /* convert Siphon vehicle classes */
136 | $toTeleportMap = array(
137 | 'Teleport\\Transport\\MySQLVehicle' => '\\Siphon\\Transport\\SiphonMySQLVehicle',
138 | 'Teleport\\Transport\\xPDOCollectionVehicle' => '\\Siphon\\Transport\\SiphonXPDOCollectionVehicle',
139 | );
140 | array_walk($this->vehicles, function (&$vehicle) use ($toTeleportMap) {
141 | if ($teleportClass = array_search($vehicle['vehicle_class'], $toTeleportMap)) {
142 | $vehicle['vehicle_class'] = $teleportClass;
143 | if (isset($vehicle['object']) && isset($vehicle['object']['vehicle_class'])) {
144 | $vehicle['object']['vehicle_class'] = $teleportClass;
145 | }
146 | }
147 | });
148 |
149 | /* filter problem vehicles */
150 | $this->vehicles = array_filter($this->vehicles, function ($vehicle) {
151 | $keep = true;
152 | switch ($vehicle['vehicle_class']) {
153 | case 'xPDOObjectVehicle':
154 | switch ($vehicle['class']) {
155 | case 'modSystemSetting':
156 | $excludes = array(
157 | 'session_cookie_domain',
158 | 'session_cookie_path',
159 | 'new_file_permissions',
160 | 'new_folder_permissions'
161 | );
162 | if (in_array($vehicle['native_key'], $excludes)) {
163 | $keep = false;
164 | }
165 | break;
166 | default:
167 | break;
168 | }
169 | break;
170 | default:
171 | break;
172 | }
173 | return $keep;
174 | });
175 | }
176 |
177 | public function postInstall()
178 | {
179 | /* fix settings_version */
180 | /** @var \modSystemSetting $object */
181 | $object = $this->xpdo->getObject('modSystemSetting', array('key' => 'settings_version'));
182 | if (!$object) {
183 | $object = $this->xpdo->newObject('modSystemSetting');
184 | $object->fromArray(array(
185 | 'key' => 'settings_version',
186 | 'area' => 'system',
187 | 'namespace' => 'core',
188 | 'xtype' => 'textfield',
189 | ), '', true);
190 | }
191 | $object->set('value', $this->xpdo->version['full_version']);
192 | $object->save(false);
193 | }
194 | }
195 |
--------------------------------------------------------------------------------