├── .gitignore ├── the-architect.png ├── composer.json ├── thearchitect ├── resources │ ├── css │ │ └── thearchitect.css │ ├── icon-mask.svg │ ├── icon.svg │ └── js │ │ ├── clipboard.min.js │ │ ├── thearchitect.js │ │ └── diff.min.js ├── config.php ├── templates │ ├── files.twig │ ├── output.twig │ ├── convert.twig │ ├── index.twig │ ├── migrations.twig │ ├── _itemTable.twig │ └── blueprint.twig ├── controllers │ ├── TheArchitect_ApiController.php │ └── TheArchitectController.php ├── variables │ └── TheArchitectVariable.php └── TheArchitectPlugin.php ├── library ├── menus.json ├── _partials │ ├── README.md │ └── block-call-to-action.json ├── executive-bios.json └── meta-data.json ├── LICENSE ├── README.md └── releases.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | thearchitect/vendor 3 | -------------------------------------------------------------------------------- /the-architect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pennebaker/craftcms-thearchitect/HEAD/the-architect.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pennebaker/craftcms-thearchitect", 3 | "description": "CraftCMS plugin to generate content models from JSON data.", 4 | "type": "craft-plugin", 5 | "homepage": "https://github.com/pennebaker/craftcms-thearchitect", 6 | "license": "MIT", 7 | "require": {}, 8 | "autoload": { 9 | "psr-4": { "": "thearchitect/" } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /thearchitect/resources/css/thearchitect.css: -------------------------------------------------------------------------------- 1 | th [type="checkbox"] + label { 2 | margin-right: 6px; 3 | } 4 | 5 | #similarFields .highlight { 6 | background: #ffe38f; 7 | color: #705400; 8 | } 9 | 10 | form.quick-action { 11 | display: inline-block; 12 | } 13 | 14 | td > svg { 15 | vertical-align: middle; 16 | } 17 | 18 | svg[data-clipboard-text] { 19 | cursor: pointer; 20 | } 21 | 22 | code.location { 23 | color: #8f98a3; 24 | background-color: #EBEDEF; 25 | border-radius: 3px; 26 | padding: 2px 4px; 27 | } 28 | -------------------------------------------------------------------------------- /thearchitect/config.php: -------------------------------------------------------------------------------- 1 | 7 | * @package The Architect 8 | * @copyright Copyright (c) 2016, Pennebaker 9 | * @license http://opensource.org/licenses/mit-license.php MIT License 10 | * @link https://github.com/pennebaker/craft-thearchitect 11 | */ 12 | 13 | 14 | /** 15 | * Configuration file for The Architect 16 | * 17 | * Override this by placing a file named 'thearchitect.php' inside your config folder and override variables as needed. 18 | * Multi-environment settings work in this file the same way as in general.php or db.php 19 | */ 20 | 21 | return array( 22 | 'modelsPath' => str_replace('plugins', 'config', __dir__.'/'), 23 | ); 24 | -------------------------------------------------------------------------------- /library/menus.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups":[ 3 | "Menus" 4 | ], 5 | "fields":[ 6 | { 7 | "group": "Menus", 8 | "name": "Show in Top Menus", 9 | "handle": "showInTopMenus", 10 | "type": "Lightswitch", 11 | "instructions": "Does this page appear in the main navigation?" 12 | }, 13 | { 14 | "group": "Menus", 15 | "name": "Top Menu Label", 16 | "handle": "topMenuLabel", 17 | "type": "PlainText", 18 | "instructions": "What is the label for this page in the main navigation? Shorter is better." 19 | }, 20 | { 21 | "group": "Menus", 22 | "name": "Show in Bottom Menus", 23 | "handle": "showInBottomMenus", 24 | "type": "Lightswitch", 25 | "instructions": "Does this page appear in the footer navigation?" 26 | }, 27 | { 28 | "group": "Menus", 29 | "name": "Bottom Menu Label", 30 | "handle": "bottomMenuLabel", 31 | "type": "PlainText", 32 | "instructions": "What is the label for this page in the footer? Shorter is better." 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /thearchitect/templates/files.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% set title = "The Architect"|t %} 3 | 4 | {% import "_includes/forms" as forms %} 5 | 6 | {% set tabs = { 7 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 8 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 9 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 10 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 11 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 12 | } %} 13 | 14 | {% set selectedTab = 'tab2' %} 15 | 16 | {% set content %} 17 |
18 | 19 |
20 | 21 | {{ getCsrfInput() }} 22 | {% for file in files %} 23 | 24 | {% endfor %} 25 |
26 | 27 |
28 | {% endset %} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pennebaker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /library/_partials/README.md: -------------------------------------------------------------------------------- 1 | # Partials & Modular Content Management 2 | 3 | The partials section has to do primarily with blocks within Matrix fields. The models contained within are made to be copied and pasted into a Matrix field as block-level objects. See below for an example: 4 | 5 | ``` 6 | { 7 | "group": "Default", 8 | "name": "Matrix", 9 | "handle": "matrix", 10 | "instructions": "Use this area to build out content for this page.", 11 | "type": "Matrix", 12 | "typesettings": { 13 | "blockTypes":{ 14 | "new1": // Copy and paste the entire block-level model here, including the curly braces. 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | The `new1` object shown above doesn't have braces that defines it as an object, because each partial/block-level model includes the braces that define it. This ensures that each block-level model validates when run through a linter. 21 | 22 | Block-level models in the partials directory are not configured to drag and drop into the Generator as solo fields. They lack the proper structure for the plugin to read them properly. They are meant to be added as is to a Matrix field type. 23 | 24 | ## Examples 25 | Examples will be added to the [Plugin Wiki](https://github.com/Pennebaker/craftcms-generator/wiki/Block-Examples). 26 | -------------------------------------------------------------------------------- /library/executive-bios.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups":[ 3 | "Executive Bios" 4 | ], 5 | "fields": [ 6 | { 7 | "group": "Executive Bios", 8 | "name": "Display Name", 9 | "handle": "displayName", 10 | "type": "PlainText", 11 | "instructions": "Whose bio is this?" 12 | }, 13 | { 14 | "group": "Executive Bios", 15 | "name": "Job Title", 16 | "handle": "jobTitle", 17 | "type": "PlainText", 18 | "instructions": "What's their job title?" 19 | }, 20 | { 21 | "group": "Executive Bios", 22 | "name": "Bio Content", 23 | "handle": "bio", 24 | "type": "RichText", 25 | "instructions": "The content of the bio itself.", 26 | "typesettings": { 27 | "configFile": "Simple" 28 | } 29 | }, 30 | { 31 | "group": "Executive Bios", 32 | "name": "Bio Photo", 33 | "handle": "bioPhoto", 34 | "instructions": "Usually a headshot.", 35 | "type": "Assets", 36 | "typesettings": { 37 | "restrictFiles": true, 38 | "allowedKinds": [ 39 | "image" 40 | ], 41 | "selectionLabel": "Select an image" 42 | } 43 | } 44 | ], 45 | "entryTypes": [ 46 | { 47 | "sectionName": "Executive Bios", 48 | "name":"Executive Bios", 49 | "handle":"bios", 50 | "titleLabel": "CMS Title", 51 | "fieldLayout": { 52 | "Bio": [ 53 | "Display Name", 54 | "Job Title", 55 | "Bio Content", 56 | "Bio Photo" 57 | ] 58 | } 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /thearchitect/controllers/TheArchitect_ApiController.php: -------------------------------------------------------------------------------- 1 | theArchitect->getAPIKey(); 15 | $key = craft()->request->getParam('key'); 16 | 17 | if (!$apiKey OR $key != $apiKey) { 18 | die('{"type": "export","success": false}'); 19 | } 20 | 21 | // Run Migration Export 22 | craft()->theArchitect->exportMigrationConstruct(); 23 | 24 | // Set last import to match this export time. 25 | craft()->plugins->savePluginSettings(craft()->plugins->getPlugin('theArchitect'), array('lastImport' => (new DateTime())->getTimestamp())); 26 | 27 | die('{"type": "export","success": true}'); 28 | } 29 | 30 | public function actionImport() 31 | { 32 | $apiKey = craft()->theArchitect->getAPIKey(); 33 | $key = craft()->request->getParam('key'); 34 | $force = craft()->request->getParam('force'); 35 | 36 | if (!$apiKey OR $key != $apiKey) { 37 | die('{"type": "export","success": false}'); 38 | } 39 | 40 | // Run Migration Import 41 | $result = craft()->theArchitect->importMigrationConstruct($force); 42 | 43 | if ($result) { 44 | die('{"type": "import","success": true}'); 45 | } else { 46 | die('{"type": "import","success": false}'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /library/meta-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups":[ 3 | "Meta Data" 4 | ], 5 | "fields": [ 6 | { 7 | "group": "Meta Data", 8 | "name": "SEO Title", 9 | "instructions": "The page title that appears on SERPs and bookmarks.", 10 | "type": "PlainText" 11 | }, 12 | { 13 | "group": "Meta Data", 14 | "name": "SEO Description", 15 | "instructions": "The page description that appears on SERPs.", 16 | "type": "PlainText" 17 | }, 18 | { 19 | "group": "Meta Data", 20 | "name": "OpenGraph Title", 21 | "instructions": "The OpenGraph title that appears on Facebook, LinkedIn, and other social sites. Try to limit to 90 characters.", 22 | "type": "PlainText" 23 | }, 24 | { 25 | "group": "Meta Data", 26 | "name": "OpenGraph Description", 27 | "instructions": "The OpenGraph description that appears on Facebook, LinkedIn, and other social sites. Try to limit to 300 characters.", 28 | "type": "PlainText" 29 | }, 30 | { 31 | "group": "Meta Data", 32 | "name": "OpenGraph Image", 33 | "instructions": "The OpenGraph image that appears on Facebook, LinkedIn, and other social sites.", 34 | "type": "Assets", 35 | "typesettings": { 36 | "restrictFiles": true, 37 | "allowedKinds": [ 38 | "image" 39 | ], 40 | "limit": 1, 41 | "selectionLabel": "Add an OpenGraph image." 42 | } 43 | }, 44 | { 45 | "group": "Meta Data", 46 | "name": "OpenGraph URL", 47 | "instructions": "The canonical url for this entry.", 48 | "type": "PlainText" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /library/_partials/block-call-to-action.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Call to Action Block", 3 | "handle": "ctaBlock", 4 | "instructions": "This block is used to incite action in your user.", 5 | "fields": { 6 | "new1": { 7 | "name": "headline", 8 | "handle": "headline", 9 | "instructions": "The headline for this block.", 10 | "type": "PlainText" 11 | }, 12 | "new2": { 13 | "name": "CTA Copy", 14 | "handle": "blockCopy", 15 | "instructions": "The copy that precedes the button.", 16 | "type": "RichText", 17 | "typesettings": { 18 | "configFile": "Simple" 19 | } 20 | }, 21 | "new3": { 22 | "name": "Background Image", 23 | "handle": "blockBgImg", 24 | "instructions": "The background image for this block.", 25 | "type": "Assets", 26 | "typesettings": { 27 | "restrictFiles": true, 28 | "allowedKinds": [ 29 | "image" 30 | ], 31 | "limit": 1, 32 | "selectionLabel": "Select an Image" 33 | } 34 | }, 35 | "new4": { 36 | "name": "Block Background Color", 37 | "handle": "blockBgColor", 38 | "instructions": "Is the background white or gray?", 39 | "type": "Dropdown", 40 | "typesettings": { 41 | "options": [ 42 | { 43 | "label": "White", 44 | "value": "white", 45 | "default": true 46 | }, 47 | { 48 | "label": "Gray", 49 | "value": "gray", 50 | "default": false 51 | } 52 | ] 53 | } 54 | }, 55 | "new5": { 56 | "name": "Button Text", 57 | "handle": "buttonText", 58 | "instructions": "The text for the button.", 59 | "type": "PlainText" 60 | }, 61 | "new6": { 62 | "name": "Destination", 63 | "handle": "buttonDestination", 64 | "instructions": "Where does the button go when you click it?", 65 | "type": "Entries", 66 | "typesettings": { 67 | "limit": 1, 68 | "selectionLabel": "Select a Destination" 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /thearchitect/templates/output.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% set title = "The Architect"|t %} 3 | 4 | {% import "_includes/forms" as forms %} 5 | 6 | {% set tabs = { 7 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 8 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 9 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 10 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 11 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 12 | } %} 13 | 14 | {% set selectedTab = tab %} 15 | 16 | {% set content %} 17 |
18 | 19 | {% if selectedTab == 'tab5' %} 20 |

21 | To import this json back into Craft you will need the Neo Plugin installed. 22 |

23 |

24 | If you plan on re-importing this json directly into this instance of Craft remember to rename the handle either in this json model or on the old matrix field. 25 |

26 |

27 | {{ oldFieldCount }} fields found in matrix field(s) reduced to {{ newFieldCount }} fields used by Neo 28 |

29 | {% endif %} 30 | {# Text Area for Output Data #} 31 | {{ forms.textarea({ 32 | name: 'json', 33 | class: 'nicetext code', 34 | value: (json is defined ? json : null), 35 | first: true, 36 | autofocus: true, 37 | rows: 24 38 | }) }} 39 | {% if similarFields is defined and similarFields | length %} 40 |

Some similar fields you might want to review and combine manually.

41 | 42 | 43 | 44 | 47 | 50 | 51 | 52 | 53 | {% for fields in similarFields %} 54 | 55 | 58 | 61 | 62 | {% endfor %} 63 | 64 |
45 | Field A 46 | 48 | Field B 49 |
56 |
{{ fields.A }}
57 |
59 |
{{ fields.B }}
60 |
65 | {% endif %} 66 | 67 |
68 | {% endset %} 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Looking for? [Architect for Craft 3/4](https://github.com/Pennebaker/craft-architect) 2 | 3 | ![The Architect](/the-architect.png?raw=true) 4 | # The Architect for [Craft CMS](http://buildwithcraft.com/) 5 | 6 | CraftCMS Plugin to Construct Groups, Fields, Sections, EntryTypes, Transforms, Globals, Assets, Categories, and Users & User Groups from JSON data. 7 | 8 | ## Installation 9 | 1. Move the `thearchitect` directory into your `craft/plugins` directory. 10 | 2. Go to Settings > Plugins from your Craft control panel and enable the `thearchitect` plugin 11 | 12 | Example files can be found in the `library` directory 13 | 14 | ## Exported Constructs 15 | If you want to provide json files to be loaded throught the CP. Put the files in `craft/config/thearchitect`. If using a version prior to v1.6.0 the folder for these files is `craft/plugins/thearchitect/content`. This path is also configurable by creating a config file at `craft/config/thearchitect.php` 16 | ```php 17 | 'modelsPath' => str_replace('plugins', 'config', __dir__.'/'), 18 | ``` 19 | 20 | ## Migration File 21 | The migration file is named `_master_.json` and located inside the folder with the other json files listed above. Migration files are intended to be used within a single site. They are not intended to transfer content models between websites. 22 | 23 | ## Rollback 24 | As of version 1.6.0, if Craft crashes with an exception, the Architect will attempt to roll back any changes that were made to the database for the operation. This should help prevent any issues that might appear from a partial import. If an exception happens please report them to on the [repo's issues](https://github.com/Pennebaker/craftcms-thearchitect/issues). A backup that was created during a migration that was successfully imported will be delete. Otherwise you can find the db backup inside `craft/storage/backups`. This is in case the rollback feature doesn't restore properly. It is not recommended to rely on this auto backup/restore feature. 25 | 26 | ## JSON Schema 27 | The example / syntax schemas are located on the [Repo's Wiki](https://github.com/Pennebaker/craftcms-thearchitect/wiki) 28 | 29 | If you're using the [Atom text editor](https://atom.io/), you can download a [snippet library](https://github.com/Emkaytoo/craft-json-snippets) to help speed up your writing custom models for the plugin. 30 | 31 | ## Field Layouts using names instead of handles 32 | If you have some field layouts that use names this functionality was dropped in version 1.0.3. Alternatively you can update your old models to use handles to fix them for newer versions. 33 | 34 | *Special thanks to [Shannon Threadgill](https://dribbble.com/threadgillthunder) for his totally boss illustrations.* 35 | -------------------------------------------------------------------------------- /thearchitect/variables/TheArchitectVariable.php: -------------------------------------------------------------------------------- 1 | neo->getBlockTypesByFieldId($layoutId); 16 | } 17 | 18 | /** 19 | * Returns an array of all the users. 20 | * 21 | * @return array[UserModel] 22 | */ 23 | public function getAllUsers() 24 | { 25 | return craft()->theArchitect->getAllUsers(); 26 | } 27 | 28 | /** 29 | * Returns an array of all the users. 30 | * 31 | * @return array[UserModel] 32 | */ 33 | public function getAllTagGroups() 34 | { 35 | return craft()->tags->getAllTagGroups(); 36 | } 37 | 38 | /** 39 | * Returns a entry type by its ID. 40 | * 41 | * @param int $entryTypeId 42 | * 43 | * @return EntryTypeModel|null 44 | */ 45 | public function getEntryTypeById($entryTypeId) 46 | { 47 | return craft()->sections->getEntryTypeById($entryTypeId); 48 | } 49 | 50 | /** 51 | * Returns a asset source by its ID. 52 | * 53 | * @param int $sourceId 54 | * 55 | * @return AssetSourceModel|null 56 | */ 57 | public function getSourceById($sourceId) 58 | { 59 | return craft()->assetSources->getSourceById($sourceId); 60 | } 61 | 62 | /** 63 | * Returns a asset transform by its ID. 64 | * 65 | * @param int $transformId 66 | * 67 | * @return AssetTransformModel|null 68 | */ 69 | public function getTransformById($transformId) 70 | { 71 | foreach (craft()->assetTransforms->getAllTransforms() as $transform) { 72 | if ($transform->id == $transformId) { 73 | return $transform; 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * Returns a category group by its ID. 80 | * 81 | * @param int $categoryId 82 | * 83 | * @return CategoryGroupModel|null 84 | */ 85 | public function getCategoryGroupById($categoryId) 86 | { 87 | foreach (craft()->categories->getAllGroups() as $category) { 88 | if ($category->id == $categoryId) { 89 | return $category; 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Returns a user by its ID. 96 | * 97 | * @param int $userId 98 | * 99 | * @return UserModel|null 100 | */ 101 | public function getUserById($userId) 102 | { 103 | return craft()->users->getUserById($userId); 104 | } 105 | 106 | /** 107 | * Returns a integer of the license edition 108 | * 109 | * @return Integer 110 | */ 111 | public function getEdition() 112 | { 113 | return craft()->getEdition(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /thearchitect/templates/convert.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% set title = "The Architect"|t %} 3 | 4 | {% import "_includes/forms" as forms %} 5 | 6 | {% set tabs = { 7 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 8 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 9 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 10 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 11 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 12 | } %} 13 | 14 | {% set selectedTab = 'tab5' %} 15 | 16 | {% set content %} 17 |
18 | 19 |

20 | The converted model will need the Neo Plugin installed on that Craft instance. 21 |

22 |

23 | Fields pulled out of matrix entries will be put in the same group the matrix field was in. The conversion will combine fields if they are 100% identical and that field will be grouped into the first group it was added as. 24 |

25 | 26 |
27 | 28 | {{ getCsrfInput() }} 29 |

Matrix Fields

30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | {% for field in craft.fields.getAllFields() %} 45 | {% if field.type == 'Matrix' %} 46 | 47 | 50 | 53 | 56 | 57 | {% endif %} 58 | {% endfor %} 59 | 60 |
34 |
35 | 36 | Name 37 |
38 |
HandleGroup
48 | 49 | 51 | {{ field.handle }} 52 | 54 | {{ field.group }} 55 |
61 |
62 |
63 | 64 | 65 |
66 |
67 | 68 |
69 | {% endset %} 70 | -------------------------------------------------------------------------------- /thearchitect/resources/icon-mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 27 | 31 | 32 | -------------------------------------------------------------------------------- /thearchitect/templates/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% set title = "The Architect"|t %} 3 | 4 | {% import "_includes/forms" as forms %} 5 | 6 | {% set tabs = { 7 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 8 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 9 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 10 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 11 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 12 | } %} 13 | 14 | {% set docsUrl = 'https://github.com/Pennebaker/craftcms-thearchitect/wiki' %} 15 | 16 | {% set content %} 17 |
18 | 19 | 20 | {# Output of Submitted Data #} 21 | {% if result is defined %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for item in result %} 33 | {% if item.result %} 34 | 35 | {% elseif item.type == 'Group' %} 36 | 37 | {% else %} 38 | 39 | {% endif %} 40 | 41 | 42 | {% if item.result %} 43 | 44 | {% elseif item.type == 'Group' %} 45 | 46 | {% else %} 47 | 48 | {% endif %} 49 | 67 | 68 | {% endfor %} 69 | 70 |
TypeNameResultsErrors
{{ item.type }}{{ item.name }}{{ "Success"|t }}{{ "Warning"|t }}{{ "Error"|t }} 50 | {% if not item.result and item.type == 'Group' %} 51 |
Name
52 |
    53 |
  • {{ "Group"|t ~ " \"" ~ item.name ~ "\" " ~ "has already been taken."|t }}
  • 54 |
55 | {% endif %} 56 | {% if item.errors %} 57 | {% for errType, errs in item.errors %} 58 |
{{ errType }}
59 |
    60 | {% for err in errs %} 61 |
  • {{ err }}
  • 62 | {% endfor %} 63 |
64 | {% endfor %} 65 | {% endif %} 66 |
71 | {% endif %} 72 | 73 | {% if filename is defined %} 74 |

{{ "Loaded"|t }}: {{ filename }}

75 | {% endif %} 76 | 77 | {# The Architect Input Form #} 78 | 79 |
80 | 81 | {{ getCsrfInput() }} 82 | 83 | {# Text Area for Input Data #} 84 | {{ forms.textarea({ 85 | name: 'json', 86 | class: 'nicetext code', 87 | value: (json is defined ? json : null), 88 | first: true, 89 | autofocus: true, 90 | rows: 24 91 | }) }} 92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 | {% endset %} 100 | -------------------------------------------------------------------------------- /thearchitect/TheArchitectPlugin.php: -------------------------------------------------------------------------------- 1 | getPluginUrl().'/wiki'; 88 | } 89 | 90 | /** 91 | * hasCpSection. 92 | * 93 | * @return bool 94 | */ 95 | public function hasCpSection() 96 | { 97 | if (craft()->userSession->isAdmin()) { 98 | return true; 99 | } 100 | } 101 | 102 | public function init() 103 | { 104 | $modelsPath = craft()->config->get('modelsPath', 'theArchitect'); 105 | if (!file_exists($modelsPath)) { 106 | IOHelper::createFolder($modelsPath); 107 | } 108 | $automation = craft()->theArchitect->getAutomation(); 109 | 110 | if ($automation) { 111 | $masterJson = $modelsPath.'_master_.json'; 112 | $lastImport = craft()->theArchitect->getLastImport(); 113 | if (file_exists($masterJson)) { 114 | $exportTime = filemtime($masterJson); 115 | } else { 116 | $exportTime = null; 117 | } 118 | 119 | if ($lastImport < $exportTime) { 120 | $result = craft()->theArchitect->importMigrationConstruct(); 121 | if ($result) { 122 | craft()->userSession->setNotice(Craft::t('Migration imported successfully.')); 123 | } else { 124 | craft()->userSession->setError(Craft::t('There is some field type changes. Visit the Architect Migrations page to review.')); 125 | } 126 | } 127 | } 128 | } 129 | 130 | protected function defineSettings() 131 | { 132 | return array( 133 | 'automation' => array(AttributeType::Bool, 'default' => null), 134 | 'lastImport' => array(AttributeType::Number, 'default' => null), 135 | 'apiKey' => array(AttributeType::String, 'default' => null), 136 | ); 137 | } 138 | 139 | public function registerCpRoutes() 140 | { 141 | return array( 142 | 'thearchitect/files' => array('action' => 'theArchitect/constructList'), 143 | 'thearchitect/blueprint' => array('action' => 'theArchitect/blueprint'), 144 | 'thearchitect/migrations' => array('action' => 'theArchitect/migrations'), 145 | 'thearchitect/convert' => array('action' => 'theArchitect/convert'), 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /thearchitect/resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 40 | 43 | 45 | 46 | 48 | 50 | 52 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 79 | 81 | 82 | 85 | 86 | 88 | 89 | -------------------------------------------------------------------------------- /thearchitect/resources/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v1.5.13 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,o){function r(c,a){if(!n[c]){if(!e[c]){var l="function"==typeof require&&require;if(!a&&l)return l(c,!0);if(i)return i(c,!0);var s=new Error("Cannot find module '"+c+"'");throw s.code="MODULE_NOT_FOUND",s}var u=n[c]={exports:{}};e[c][0].call(u.exports,function(t){var n=e[c][1][t];return r(n?n:t)},u,u.exports,t,e,n,o)}return n[c].exports}for(var i="function"==typeof require&&require,c=0;c 6 | 7 | 8 | 9 | {{ getCsrfInput() }} 10 | 11 | 12 | 13 | {% endmacro %} 14 | 15 | {% macro quickActionForm(action, buttonText, additionalFields, bigButton) %} 16 |
17 | 18 | 19 | {{ getCsrfInput() }} 20 | {% for fieldName, fieldValue in additionalFields %} 21 | 22 | {% endfor %} 23 | {% if bigButton %} 24 |
25 | 26 |
27 | {% else %} 28 | 29 | {% endif %} 30 |
31 | {% endmacro %} 32 | 33 | {% macro clipboardButton(clipboardText) %} 34 | 35 | {% endmacro %} 36 | 37 | {% import "_includes/forms" as forms %} 38 | {% from _self import quickActionForm, migrationsToggleForm, clipboardButton %} 39 | 40 | {% set tabs = { 41 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 42 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 43 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 44 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 45 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 46 | } %} 47 | 48 | {% set selectedTab = 'tab4' %} 49 | 50 | {% set docsUrl = 'https://github.com/Pennebaker/craftcms-thearchitect/wiki' %} 51 | 52 | {% set content %} 53 |
54 |

Migrations have only been tested in a local dev environment. Backup your DB before and while using migrations. Please report any issues you run into.

55 |

It is recommended leaving Automatic Import disabled until you have imported many times successfully with no issues or until the plugin has had some more real world testing. 56 |

Migrations are designed to be used with a single site only. Exported files store ID numbers of all exported data. When imported it will overwrite those IDs which could be different across sites. Using migrations to replicate models across sites is not the intended use.

57 | 58 |
59 | 60 |

Status

61 |

Migration file for import / export is located at: {{ jsonPath }}_master_.json.

62 |

If automatic import is enabled this is the file that will be loaded into the DB whenever the files modified time is newer than the last import time.

63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
Automatic Import{% if automation %}Enabled{% else %}Disabled{% endif %}{% if automation %}{{ migrationsToggleForm(0, 'Disable') }}{% else %}{{ migrationsToggleForm(1, 'Enable') }}{% endif %}
Last Export{% if exportTime %}{{ exportTime|date("F d Y @ g:i a") }}{% else %}n/a{% endif %}{{ quickActionForm('migrationExport', 'Run Export') }}
Last Import{% if importTime %}{{ importTime|date("F d Y @ g:i a") }}{% else %}n/a{% endif %}{{ quickActionForm('migrationImport', 'Run Import') }}
82 | 83 |
84 | 85 |

API

86 | {% if apiKey %} 87 | {% set apiExportUrl = siteUrl(craft.config.get('actionTrigger') ~ '/theArchitect/api/export', { key: apiKey }) %} 88 | {% set apiImportUrl = siteUrl(craft.config.get('actionTrigger') ~ '/theArchitect/api/import', { key: apiKey, force: false }) %} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
{{ apiKey }}{{ quickActionForm('generateKey', 'Generate New Key') }}
{{ apiExportUrl }}{{ clipboardButton(apiExportUrl) }}
{{ apiImportUrl }}{{ clipboardButton(apiImportUrl) }}
105 | {% else %} 106 | {{ quickActionForm('generateKey', 'Generate Key') }} 107 | {% endif %} 108 | 109 |
110 | 111 | {% if dbAdditions|length > 0 %} 112 | {% include 'thearchitect/_itemTable' with {itemList: dbAdditions, status: 'success', headline: 'DB Additions', text: 'List of items added since the previous export. Performing an import will not delete these added fields.'} %} 113 | {% endif %} 114 | {% if dbUpdates|length > 0 %} 115 | {% include 'thearchitect/_itemTable' with {itemList: dbUpdates, status: 'warning', headline: 'DB Updates', text: 'List of items updated since the previous export. Performing an import will overwrite these changes prefering the exported model.'} %} 116 | {% endif %} 117 | {% if dbDeletions|length > 0 %} 118 | {% include 'thearchitect/_itemTable' with {itemList: dbDeletions, status: 'error', headline: 'DB Deletions', text: 'List if items deleted since the previous export. Performing an import will add these items back to the database.'} %} 119 | {% endif %} 120 | {% if modelAdditions|length > 0 %} 121 | {% include 'thearchitect/_itemTable' with {itemList: modelAdditions, status: 'success', headline: 'Model Additions', text: 'List if items added to the model since the previous import. Performing an import will create these items in the database.'} %} 122 | {% endif %} 123 | {% if modelUpdates|length > 0 %} 124 | {% include 'thearchitect/_itemTable' with {itemList: modelUpdates, status: 'warning', headline: 'Model Updates', text: 'List if items updated in the model since the previous import. Performing an import will modify existing items in the database.'} %} 125 | {% endif %} 126 | {% if modelDeletions|length > 0 %} 127 | {% include 'thearchitect/_itemTable' with {itemList: modelDeletions, status: 'error', headline: 'Model Deletions', text: 'List if items deleted from the model since the previous import. Performing an import will delete these items from the database.', subText: {Users: 'User deletions are not handled by The Architect please manually delete the users below to remove them.'}} %} 128 | {% endif %} 129 | {% if mismatch|length > 0 %} 130 |

Field Migration Problems

131 |

Problems listed below are preventing standard and automation imports from running. Please review changes before forcing an import.

132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | {% for fields in mismatch %} 143 | 144 | 147 | 150 | 153 | 156 | 157 | {% endfor %} 158 | 159 |
GroupNameHandleType
145 | {{ fields[0].group }}{% if fields[0].group != fields[1].group %} → {{ fields[1].group }}{% endif %} 146 | 148 | {{ fields[0].name }}{% if fields[0].name != fields[1].name %} → {{ fields[1].name }}{% endif %} 149 | 151 | {{ fields[0].handle }}{% if fields[0].handle != fields[1].handle %} → {{ fields[1].handle }}{% endif %} 152 | 154 | {{ fields[0].type }} → {{ fields[1].type }} 155 |
160 | {{ quickActionForm('migrationImport', 'Force Import', { force: null }, true) }} 161 | {% endif %} 162 | 163 |
164 | {% endset %} 165 | -------------------------------------------------------------------------------- /thearchitect/templates/_itemTable.twig: -------------------------------------------------------------------------------- 1 | {% if itemList['fieldIDs']|length > 0 or 2 | itemList['sectionIDs']|length > 0 or 3 | itemList['entryTypeIDs']|length > 0 or 4 | itemList['assetSourceIDs']|length > 0 or 5 | itemList['assetTransformIDs']|length > 0 or 6 | itemList['globalIDs']|length > 0 or 7 | itemList['categoryIDs']|length > 0 or 8 | itemList['userIDs']|length > 0 or 9 | (itemList['groupIDs'] is defined and itemList['groupIDs']|length > 0) 10 | %} 11 |

{{ headline }}

12 |

{{ text }}

13 | {% if itemList['fieldIDs']|length > 0 %} 14 |

Fields

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for fieldID in itemList['fieldIDs'] %} 26 | {% set field = craft.fields.getFieldById(fieldID) %} 27 | 28 | 31 | 34 | 37 | 40 | 41 | {% endfor %} 42 | 43 |
GroupNameHandleType
29 | {{ field.group }} 30 | 32 | {{ field.name }} 33 | 35 | {{ field.handle }} 36 | 38 | {{ field.type }} 39 |
44 | {% endif %} 45 | 46 | {% if itemList['sectionIDs']|length > 0 %} 47 |

Sections

48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {% for sectionID in itemList['sectionIDs'] %} 59 | {% set section = craft.sections.getSectionById(sectionID) %} 60 | 61 | 64 | 67 | 70 | 73 | 74 | {% endfor %} 75 | 76 |
NameHandleTypeURL Format
62 | {{ section.name }} 63 | 65 | {{ section.handle }} 66 | 68 | {{ section.type }} 69 | 71 | {{ section.urlFormat }} 72 |
77 | {% endif %} 78 | 79 | {% if itemList['entryTypeIDs']|length > 0 %} 80 |

Entry Types

81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {% for entryTypeID in itemList['entryTypeIDs'] %} 90 | {% set entryType = craft.theArchitect.getEntryTypeById(entryTypeID) %} 91 | 92 | 95 | 98 | 99 | {% endfor %} 100 | 101 |
NameHandle
93 | {{ entryType.name }} 94 | 96 | {{ entryType.handle }} 97 |
102 | {% endif %} 103 | 104 | {% if itemList['assetSourceIDs']|length > 0 %} 105 |

Asset Sources

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {% for assetSourceID in itemList['assetSourceIDs'] %} 117 | {% set assetSource = craft.theArchitect.getSourceById(assetSourceID) %} 118 | 119 | 122 | 125 | 128 | 131 | 132 | {% endfor %} 133 | 134 |
NameHandlePathUrl
120 | {{ assetSource.name }} 121 | 123 | {{ assetSource.handle }} 124 | 126 | {{ assetSource.settings.path }} 127 | 129 | {{ assetSource.settings.url }} 130 |
135 | {% endif %} 136 | 137 | {% if itemList['assetTransformIDs']|length > 0 %} 138 |

Asset Transforms

139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | {% for assetTransformID in itemList['assetTransformIDs'] %} 150 | {% set assetTransform = craft.theArchitect.getTransformById(assetTransformID) %} 151 | 152 | 155 | 158 | 161 | 164 | 165 | {% endfor %} 166 | 167 |
NameHandleModeDimensions
153 | {{ assetTransform.name }} 154 | 156 | {{ assetTransform.handle }} 157 | 159 | {{ assetTransform.mode }} 160 | 162 | {{ assetTransform.width }} x {{ assetTransform.height }} 163 |
168 | {% endif %} 169 | 170 | {% if itemList['globalIDs']|length > 0 %} 171 |

Globals

172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | {% for globalID in itemList['globalIDs'] %} 181 | {% set global = craft.globals.getSetById(globalID) %} 182 | 183 | 186 | 189 | 190 | {% endfor %} 191 | 192 |
NameHandle
184 | {{ global.name }} 185 | 187 | {{ global.handle }} 188 |
193 | {% endif %} 194 | 195 | {% if itemList['categoryIDs']|length > 0 %} 196 |

Categories

197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | {% for categoryID in itemList['categoryIDs'] %} 206 | {% set category = craft.theArchitect.getCategoryGroupById(categoryID) %} 207 | 208 | 211 | 214 | 215 | {% endfor %} 216 | 217 |
NameHandle
209 | {{ category.name }} 210 | 212 | {{ category.handle }} 213 |
218 | {% endif %} 219 | 220 | {% if itemList['userIDs']|length > 0 %} 221 |

Users

222 | {% if subText['Users'] is defined %}

{{ subText['Users'] }}{% endif %} 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | {% for userID in itemList['userIDs'] %} 233 | {% set user = craft.theArchitect.getUserById(userID) %} 234 | 235 | 238 | 241 | 244 | 245 | {% endfor %} 246 | 247 |
UsernameNameEmail
236 | {{ user.username }} 237 | 239 | {{ user.firstName }} {{ user.lastName }} 240 | 242 | {{ user.email }} 243 |
248 | {% endif %} 249 | 250 | {% if craft.theArchitect.getEdition() == 2 and itemList['groupIDs']|length > 0 %} 251 |

User Groups

252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | {% for groupID in itemList['groupIDs'] %} 261 | {% set group = craft.userGroups.getGroupById(groupID) %} 262 | 263 | 266 | 269 | 270 | {% endfor %} 271 | 272 |
NameHandle
264 | {{ group.name }} 265 | 267 | {{ group.handle }} 268 |
273 | {% endif %} 274 | 275 | {% endif %} 276 | -------------------------------------------------------------------------------- /thearchitect/controllers/TheArchitectController.php: -------------------------------------------------------------------------------- 1 | userSession->requireAdmin(); 22 | } 23 | 24 | /** 25 | * actionBlueprint [list the exportable items]. 26 | */ 27 | public function actionBlueprint() 28 | { 29 | $variables = array( 30 | 'assetSources' => craft()->assetSources->getAllSources(), 31 | 'assetTransforms' => craft()->assetTransforms->getAllTransforms(), 32 | ); 33 | 34 | craft()->templates->includeJsResource('thearchitect/js/diff.min.js'); 35 | craft()->templates->includeJsResource('thearchitect/js/thearchitect.js'); 36 | craft()->templates->includeCssResource('thearchitect/css/thearchitect.css'); 37 | 38 | $this->renderTemplate('thearchitect/blueprint', $variables); 39 | } 40 | 41 | /** 42 | * actionConstructBlueprint. 43 | */ 44 | public function actionConstructBlueprint() 45 | { 46 | // Prevent GET Requests 47 | $this->requirePostRequest(); 48 | 49 | $post = craft()->request->getPost(); 50 | 51 | $output = craft()->theArchitect->exportConstruct($post); 52 | 53 | // If the output is empty redirect back the the export page 54 | if ($output == []) { 55 | $this->redirect('thearchitect/blueprint'); 56 | } 57 | // Else display the output data as json for the user to copy 58 | else { 59 | $variables = array( 60 | 'json' => json_encode($output, /*JSON_NUMERIC_CHECK | */JSON_PRETTY_PRINT), 61 | 'tab' => 'tab3', 62 | ); 63 | 64 | $this->renderTemplate('thearchitect/output', $variables); 65 | } 66 | } 67 | 68 | /** 69 | * actionBlueprint [list the exportable items]. 70 | */ 71 | public function actionConvert() 72 | { 73 | $variables = array( 74 | 'assetSources' => craft()->assetSources->getAllSources(), 75 | 'assetTransforms' => craft()->assetTransforms->getAllTransforms(), 76 | ); 77 | 78 | craft()->templates->includeJsResource('thearchitect/js/diff.min.js'); 79 | craft()->templates->includeJsResource('thearchitect/js/thearchitect.js'); 80 | craft()->templates->includeCssResource('thearchitect/css/thearchitect.css'); 81 | 82 | $this->renderTemplate('thearchitect/convert', $variables); 83 | } 84 | 85 | /** 86 | * actionConvert [ TODO ]. 87 | */ 88 | public function actionRecode() 89 | { 90 | // Prevent GET Requests 91 | $this->requirePostRequest(); 92 | 93 | $post = craft()->request->getPost(); 94 | 95 | list($newObject, $allFields, $fields, $similarFields) = craft()->theArchitect->exportMatrixAsNeo($post); 96 | 97 | $variables = array( 98 | 'json' => json_encode($newObject, /*JSON_NUMERIC_CHECK | */JSON_PRETTY_PRINT), 99 | 'tab' => 'tab5', 100 | 'oldFieldCount' => sizeof($allFields), 101 | 'newFieldCount' => sizeof($fields), 102 | 'similarFields' => $similarFields, 103 | ); 104 | 105 | craft()->templates->includeJsResource('thearchitect/js/diff.min.js'); 106 | craft()->templates->includeJsResource('thearchitect/js/thearchitect.js'); 107 | craft()->templates->includeCssResource('thearchitect/css/thearchitect.css'); 108 | 109 | $this->renderTemplate('thearchitect/output', $variables); 110 | } 111 | 112 | /** 113 | * actionMigrations [View migration file info]. 114 | */ 115 | public function actionMigrations() 116 | { 117 | $jsonPath = craft()->config->get('modelsPath', 'theArchitect'); 118 | $masterJson = $jsonPath.'_master_.json'; 119 | 120 | if (file_exists($masterJson)) { 121 | $exportTime = filemtime($masterJson); 122 | } else { 123 | $exportTime = null; 124 | } 125 | 126 | if (file_exists($masterJson)) { 127 | list($dbAddedIDs, $dbUpdatedIDs, $dbDeleteIDs, $modelAddedIDs, $modelUpdatedIDs, $modelDeleteIDs) = craft()->theArchitect->getAddedUpdatedDeletedIds(file_get_contents($masterJson)); 128 | } 129 | 130 | $variables = array( 131 | 'automation' => craft()->theArchitect->getAutomation(), 132 | 'exportTime' => $exportTime, 133 | 'importTime' => craft()->theArchitect->getLastImport(), 134 | 'apiKey' => craft()->theArchitect->getAPIKey(), 135 | 'jsonPath' => $jsonPath, 136 | 'mismatch' => craft()->theArchitect->compareMigrationConstruct(), 137 | 'dbAdditions' => (isset($dbAddedIDs)) ? $dbAddedIDs : null, 138 | 'dbUpdates' => (isset($dbUpdatedIDs)) ? $dbUpdatedIDs : null, 139 | 'dbDeletions' => (isset($dbDeleteIDs)) ? $dbDeleteIDs : null, 140 | 'modelAdditions' => (isset($modelAddedIDs)) ? $modelAddedIDs : null, 141 | 'modelUpdates' => (isset($modelUpdatedIDs)) ? $modelUpdatedIDs : null, 142 | 'modelDeletions' => (isset($modelDeleteIDs)) ? $modelDeleteIDs : null, 143 | ); 144 | 145 | craft()->templates->includeCssResource('thearchitect/css/thearchitect.css'); 146 | craft()->templates->includeJsResource('thearchitect/js/clipboard.min.js'); 147 | craft()->templates->includeJs('var clipboard = new Clipboard("[data-clipboard-text]");clipboard.on("success",function(){Craft.cp.displayNotice("Copied to clipboard!");});clipboard.on("error",function(){Craft.cp.displayError("Error copying to clipboard.");});'); 148 | 149 | $this->renderTemplate('thearchitect/migrations', $variables); 150 | } 151 | 152 | public function actionMigrationExport() 153 | { 154 | // Run Migration Export 155 | craft()->theArchitect->exportMigrationConstruct(); 156 | 157 | // Set last import to match this export time. 158 | craft()->plugins->savePluginSettings(craft()->plugins->getPlugin('theArchitect'), array('lastImport' => (new DateTime())->getTimestamp())); 159 | 160 | $this->redirect('thearchitect/migrations'); 161 | } 162 | 163 | public function actionMigrationImport() 164 | { 165 | $force = (!is_null(craft()->request->getPost('force'))); 166 | 167 | $jsonPath = craft()->config->get('modelsPath', 'theArchitect'); 168 | $masterJson = $jsonPath.'_master_.json'; 169 | 170 | if (!file_exists($masterJson)) { 171 | craft()->userSession->setError(Craft::t('There is no migration file to import.')); 172 | } else { 173 | $result = craft()->theArchitect->importMigrationConstruct($force); 174 | 175 | if ($result) { 176 | touch($masterJson); 177 | craft()->userSession->setNotice(Craft::t('Migration imported successfully.')); 178 | } else { 179 | craft()->userSession->setError(Craft::t('There is some field type changes. To prevent content loss please review the field types before forcing.')); 180 | } 181 | } 182 | 183 | $this->redirect('thearchitect/migrations'); 184 | } 185 | 186 | public function actionGenerateKey() 187 | { 188 | // Generate a new API key for import / export url calls 189 | craft()->plugins->savePluginSettings(craft()->plugins->getPlugin('theArchitect'), array('apiKey' => craft()->theArchitect->generateUUID4())); 190 | 191 | $this->redirect('thearchitect/migrations'); 192 | } 193 | 194 | /** 195 | * actionConstructList [list the files inside the `config.jsonPath` folder]. 196 | */ 197 | public function actionConstructList() 198 | { 199 | $files = array(); 200 | $jsonPath = craft()->path->getConfigPath().'thearchitect/'; 201 | 202 | if (file_exists($jsonPath) && is_dir($jsonPath) && $handle = opendir($jsonPath)) { 203 | while (false !== ($entry = readdir($handle))) { 204 | if ($entry != '.' && $entry != '..' && $entry != '_master_.json' && strtolower(pathinfo($entry, PATHINFO_EXTENSION)) == 'json') { 205 | $files[] = $entry; 206 | } 207 | } 208 | closedir($handle); 209 | } 210 | 211 | natsort($files); 212 | 213 | $groups = craft()->fields->getAllGroups(); 214 | $fields = craft()->fields->getAllFields(); 215 | $sections = craft()->sections->getAllSections(); 216 | 217 | foreach ($fields as $field) { 218 | if ($field->type == 'Matrix') { 219 | $blockTypes = craft()->matrix->getBlockTypesByFieldId($field->id); 220 | foreach ($blockTypes as $blockType) { 221 | $blockType->getFields(); 222 | } 223 | } 224 | } 225 | 226 | $variables = array( 227 | 'files' => $files, 228 | ); 229 | 230 | $this->renderTemplate('thearchitect/files', $variables); 231 | } 232 | 233 | /** 234 | * actionConstructLoad [load the selected file for review before processing]. 235 | */ 236 | public function actionConstructLoad() 237 | { 238 | // Prevent GET Requests 239 | $this->requirePostRequest(); 240 | 241 | $fileName = craft()->request->getRequiredPost('fileName'); 242 | $jsonPath = craft()->path->getConfigPath().'thearchitect/'; 243 | 244 | $filePath = $jsonPath.$fileName; 245 | 246 | if (file_exists($filePath)) { 247 | $json = file_get_contents($filePath); 248 | 249 | $variables = array( 250 | 'json' => $json, 251 | 'filename' => $fileName, 252 | ); 253 | 254 | $this->renderTemplate('thearchitect/index', $variables); 255 | } 256 | } 257 | 258 | /** 259 | * actionConstruct. 260 | */ 261 | public function actionConstruct() 262 | { 263 | // Prevent GET Requests 264 | $this->requirePostRequest(); 265 | 266 | $json = craft()->request->getRequiredPost('json'); 267 | 268 | if ($json) { 269 | $notice = craft()->theArchitect->parseJson($json); 270 | 271 | $variables = array( 272 | 'json' => $json, 273 | 'result' => $notice, 274 | ); 275 | 276 | $this->renderTemplate('thearchitect/index', $variables); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /thearchitect/resources/js/thearchitect.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('#allFields').on('change', function(e) { 3 | if ($(this).is(':checked')) { 4 | $('.fields [id^="field"]:not(:disabled)').prop('checked', true); 5 | $('.fields [id^="field"]:not(:disabled)').change(); 6 | } else { 7 | $('.fields [id^="field"]:not(:disabled)').prop('checked', false); 8 | $('.fields [id^="field"]:not(:disabled)').change(); 9 | } 10 | }); 11 | $('.fields [id^="field"]:not(:disabled)').on('change', function(e) { 12 | if ($(this).is(':checked')) { 13 | if ($('.fields [id^="field"]:checked:not(:disabled)').length == $('.fields [id^="field"]:not(:disabled)').length) { 14 | $('#allFields').prop('checked', true); 15 | } 16 | } else { 17 | $('#allFields').prop('checked', false); 18 | } 19 | }); 20 | 21 | $('#allSections').on('change', function(e) { 22 | if ($(this).is(':checked')) { 23 | $('.sections [id^="section"]:not(:disabled)').prop('checked', true); 24 | $('.sections [id^="section"]:not(:disabled)').change(); 25 | } else { 26 | $('.sections [id^="section"]:not(:disabled)').prop('checked', false); 27 | $('.sections [id^="section"]:not(:disabled)').change(); 28 | } 29 | }); 30 | $('.sections [id^="section"]:not(:disabled)').on('change', function(e) { 31 | if ($(this).is(':checked')) { 32 | if ($('.sections [id^="section"]:checked:not(:disabled)').length == $('.sections [id^="section"]:not(:disabled)').length) { 33 | $('#allSections').prop('checked', true); 34 | } 35 | } else { 36 | $('#allSections').prop('checked', false); 37 | } 38 | }); 39 | 40 | $('#allAssetSources').on('change', function(e) { 41 | if ($(this).is(':checked')) { 42 | $('.assetSources [id^="assetSource"]:not(:disabled)').prop('checked', true); 43 | $('.assetSources [id^="assetSource"]:not(:disabled)').change(); 44 | } else { 45 | $('.assetSources [id^="assetSource"]:not(:disabled)').prop('checked', false); 46 | $('.assetSources [id^="assetSource"]:not(:disabled)').change(); 47 | } 48 | }); 49 | $('.assetSources [id^="assetSource"]:not(:disabled)').on('change', function(e) { 50 | if ($(this).is(':checked')) { 51 | if ($('.assetSources [id^="assetSource"]:checked:not(:disabled)').length == $('.assetSources [id^="assetSource"]:not(:disabled)').length) { 52 | $('#allAssetSources').prop('checked', true); 53 | } 54 | } else { 55 | $('#allAssetSources').prop('checked', false); 56 | } 57 | }); 58 | 59 | $('#allAssetTransforms').on('change', function(e) { 60 | if ($(this).is(':checked')) { 61 | $('.assetTransforms [id^="assetTransform"]:not(:disabled)').prop('checked', true); 62 | $('.assetTransforms [id^="assetTransform"]:not(:disabled)').change(); 63 | } else { 64 | $('.assetTransforms [id^="assetTransform"]:not(:disabled)').prop('checked', false); 65 | $('.assetTransforms [id^="assetTransform"]:not(:disabled)').change(); 66 | } 67 | }); 68 | $('.assetTransforms [id^="assetTransform"]:not(:disabled)').on('change', function(e) { 69 | if ($(this).is(':checked')) { 70 | if ($('.assetTransforms [id^="assetTransform"]:checked:not(:disabled)').length == $('.assetTransforms [id^="assetTransform"]:not(:disabled)').length) { 71 | $('#allAssetTransforms').prop('checked', true); 72 | } 73 | } else { 74 | $('#allAssetTransforms').prop('checked', false); 75 | } 76 | }); 77 | 78 | $('#allGlobals').on('change', function(e) { 79 | if ($(this).is(':checked')) { 80 | $('.globals [id^="global"]:not(:disabled)').prop('checked', true); 81 | $('.globals [id^="global"]:not(:disabled)').change(); 82 | } else { 83 | $('.globals [id^="global"]:not(:disabled)').prop('checked', false); 84 | $('.globals [id^="global"]:not(:disabled)').change(); 85 | } 86 | }); 87 | $('.globals [id^="global"]:not(:disabled)').on('change', function(e) { 88 | if ($(this).is(':checked')) { 89 | if ($('.globals [id^="global"]:checked:not(:disabled)').length == $('.globals [id^="global"]:not(:disabled)').length) { 90 | $('#allGlobals').prop('checked', true); 91 | } 92 | } else { 93 | $('#allGlobals').prop('checked', false); 94 | } 95 | }); 96 | 97 | $('#allCategories').on('change', function(e) { 98 | if ($(this).is(':checked')) { 99 | $('.categories [id^="category"]:not(:disabled)').prop('checked', true); 100 | $('.categories [id^="category"]:not(:disabled)').change(); 101 | } else { 102 | $('.categories [id^="category"]:not(:disabled)').prop('checked', false); 103 | $('.categories [id^="category"]:not(:disabled)').change(); 104 | } 105 | }); 106 | $('.categories [id^="category"]:not(:disabled)').on('change', function(e) { 107 | if ($(this).is(':checked')) { 108 | if ($('.categories [id^="category"]:checked:not(:disabled)').length == $('.categories [id^="category"]:not(:disabled)').length) { 109 | $('#allCategories').prop('checked', true); 110 | } 111 | } else { 112 | $('#allCategories').prop('checked', false); 113 | } 114 | }); 115 | 116 | $('#allRoutes').on('change', function(e) { 117 | if ($(this).is(':checked')) { 118 | $('.routes [id^="route"]:not(:disabled)').prop('checked', true); 119 | $('.routes [id^="route"]:not(:disabled)').change(); 120 | } else { 121 | $('.routes [id^="route"]:not(:disabled)').prop('checked', false); 122 | $('.routes [id^="route"]:not(:disabled)').change(); 123 | } 124 | }); 125 | $('.routes [id^="route"]:not(:disabled)').on('change', function(e) { 126 | if ($(this).is(':checked')) { 127 | if ($('.routes [id^="route"]:checked:not(:disabled)').length == $('.routes [id^="route"]:not(:disabled)').length) { 128 | $('#allRoutes').prop('checked', true); 129 | } 130 | } else { 131 | $('#allRoutes').prop('checked', false); 132 | } 133 | }); 134 | 135 | $('#allTags').on('change', function(e) { 136 | if ($(this).is(':checked')) { 137 | $('.tags [id^="tag"]:not(:disabled)').prop('checked', true); 138 | $('.tags [id^="tag"]:not(:disabled)').change(); 139 | } else { 140 | $('.tags [id^="tag"]:not(:disabled)').prop('checked', false); 141 | $('.tags [id^="tag"]:not(:disabled)').change(); 142 | } 143 | }); 144 | $('.tags [id^="tag"]:not(:disabled)').on('change', function(e) { 145 | if ($(this).is(':checked')) { 146 | if ($('.tags [id^="tag"]:checked:not(:disabled)').length == $('.tags [id^="tag"]:not(:disabled)').length) { 147 | $('#allTags').prop('checked', true); 148 | } 149 | } else { 150 | $('#allTags').prop('checked', false); 151 | } 152 | }); 153 | 154 | $('#allUsers').on('change', function(e) { 155 | if ($(this).is(':checked')) { 156 | $('.users [id^="user"]:not(:disabled)').prop('checked', true); 157 | $('.users [id^="user"]:not(:disabled)').change(); 158 | } else { 159 | $('.users [id^="user"]:not(:disabled)').prop('checked', false); 160 | $('.users [id^="user"]:not(:disabled)').change(); 161 | } 162 | }); 163 | $('.users [id^="user"]:not(:disabled)').on('change', function(e) { 164 | if ($(this).is(':checked')) { 165 | if ($('.users [id^="user"]:checked:not(:disabled)').length == $('.users [id^="user"]:not(:disabled)').length) { 166 | $('#allUsers').prop('checked', true); 167 | } 168 | } else { 169 | $('#allUsers').prop('checked', false); 170 | } 171 | }); 172 | 173 | $('#allGroups').on('change', function(e) { 174 | if ($(this).is(':checked')) { 175 | $('.groups [id^="group"]:not(:disabled)').prop('checked', true); 176 | $('.groups [id^="group"]:not(:disabled)').change(); 177 | } else { 178 | $('.groups [id^="group"]:not(:disabled)').prop('checked', false); 179 | $('.groups [id^="group"]:not(:disabled)').change(); 180 | } 181 | }); 182 | $('.groups [id^="group"]:not(:disabled)').on('change', function(e) { 183 | if ($(this).is(':checked')) { 184 | if ($('.groups [id^="group"]:checked:not(:disabled)').length == $('.groups [id^="group"]:not(:disabled)').length) { 185 | $('#allGroups').prop('checked', true); 186 | } 187 | } else { 188 | $('#allGroups').prop('checked', false); 189 | } 190 | }); 191 | 192 | $('#allProductTypes').on('change', function(e) { 193 | if ($(this).is(':checked')) { 194 | $('.productTypes [id^="productType"]:not(:disabled)').prop('checked', true); 195 | $('.productTypes [id^="productType"]:not(:disabled)').change(); 196 | } else { 197 | $('.productTypes [id^="productType"]:not(:disabled)').prop('checked', false); 198 | $('.productTypes [id^="productType"]:not(:disabled)').change(); 199 | } 200 | }); 201 | $('.productTypes [id^="productType"]:not(:disabled)').on('change', function(e) { 202 | if ($(this).is(':checked')) { 203 | if ($('.productTypes [id^="productType"]:checked:not(:disabled)').length == $('.productTypes [id^="productType"]:not(:disabled)').length) { 204 | $('#allProductTypes').prop('checked', true); 205 | } 206 | } else { 207 | $('#allProductTypes').prop('checked', false); 208 | } 209 | }); 210 | 211 | $('[data-fields] [type="checkbox"]').on('change', function(e) { 212 | var parentRow = $(this).closest('[data-fields]'); 213 | if ($(this).prop('checked')) { 214 | var utilizedFields = parentRow.data('fields').trim().split(' '); 215 | utilizedFields.forEach(function(id) { 216 | $('.fields [data-id="' + id + '"] [type="checkbox"]').prop('checked', true); 217 | $('.fields [data-id="' + id + '"] [type="checkbox"]').change(); 218 | }); 219 | } 220 | }); 221 | 222 | $('[data-groups] [type="checkbox"]').on('change', function(e) { 223 | var parentRow = $(this).closest('[data-groups]'); 224 | if ($(this).prop('checked')) { 225 | var utilizedFields = parentRow.data('groups').trim().split(' '); 226 | utilizedFields.forEach(function(id) { 227 | $('.groups [data-id="' + id + '"] [type="checkbox"]').prop('checked', true); 228 | $('.groups [data-id="' + id + '"] [type="checkbox"]').change(); 229 | }); 230 | } 231 | }); 232 | 233 | $('.field[data-id] [type="checkbox"]').on('change', function(e) { 234 | var parentRow = $(this).closest('[data-id]'); 235 | var id = parentRow.data('id'); 236 | if (!$(this).prop('checked')) { 237 | $('[data-fields*="' + id + '"] [type="checkbox"]').prop('checked', false); 238 | $('[data-fields*="' + id + '"] [type="checkbox"]').change(); 239 | } 240 | }); 241 | 242 | $('#similarFields tbody tr').each(function() { 243 | var leftEle = $(this).find('td:first-child > pre'); 244 | var rightEle = $(this).find('td:last-child > pre'); 245 | 246 | var leftStr = leftEle.html(); 247 | var rightStr = rightEle.html(); 248 | 249 | var diff = JsDiff.diffLines(leftStr, rightStr); 250 | 251 | diff.forEach(function(_diff) { 252 | if (_diff.removed) { 253 | leftStr = leftStr.replace(_diff.value, '' + _diff.value + ''); 254 | } 255 | if (_diff.added) { 256 | rightStr = rightStr.replace(_diff.value, '' + _diff.value + ''); 257 | } 258 | }); 259 | 260 | leftEle.html(leftStr); 261 | rightEle.html(rightStr); 262 | }); 263 | }); 264 | 265 | (function($) { 266 | // The Architect Loaded 267 | })(jQuery); 268 | -------------------------------------------------------------------------------- /releases.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "version": "1.6.0-beta.6", 3 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.6.zip", 4 | "date": "2017-09-18T09:02:00-05:00", 5 | "notes": [ 6 | "[Fixed] Tag importing without a field layout.", 7 | "[Fixed] SuperTable export error.", 8 | "[Fixed] LinkIt export error.", 9 | "[Fixed] Asset source parsing." 10 | ] 11 | }, { 12 | "version": "1.6.0-beta.5", 13 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.5.zip", 14 | "date": "2017-05-05T10:41:16-05:00", 15 | "notes": [ 16 | "[Fixed] Neo valid field layout detection fixed." 17 | ] 18 | }, { 19 | "version": "1.6.0-beta.4", 20 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.4.zip", 21 | "date": "2017-04-26T13:17:32-05:00", 22 | "notes": [ 23 | "[Fixed] Removed rollback feature when exception occurs. The database still gets backed up before any modifications happen.", 24 | "[Fixed] Fix section errors when hasUrls is false.", 25 | "[Fixed] Fix missing output list after import if something goes wrong.", 26 | "[Fixed] Fix SuperTable export if width is undefined.", 27 | "[Fixed] Add Field Layout handle testing to Neo field export.", 28 | "[Fixed] Fix content model export converting string values into numbers.", 29 | "[Fixed] Fix parseFieldSources looping over string values." 30 | ] 31 | }, { 32 | "version": "1.6.0-beta.3", 33 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.3.zip", 34 | "date": "2017-02-28T07:55:44-06:00", 35 | "notes": [ 36 | "[Fixed] Correctly export PositionSelect when inside a Matrix or SuperTable." 37 | ] 38 | }, { 39 | "version": "1.6.0-beta.2", 40 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.2.zip", 41 | "date": "2017-02-27T09:14:32-06:00", 42 | "notes": [ 43 | "[Fixed] User group export when exporting a user without group permissions with a user that has group permissions." 44 | ] 45 | }, { 46 | "version": "1.6.0-beta", 47 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.6.0-beta.zip", 48 | "date": "2017-02-13T08:13:33-06:00", 49 | "notes": [ 50 | "[Added] Import / Exporting of Routes", 51 | "[Added] Import / Exporting of Tags", 52 | "[Added] Structural migrations", 53 | "[Improved] Implemented a database rollback feature when an exception occurs." 54 | ] 55 | }, { 56 | "version": "1.5.5.3", 57 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.5.3.zip", 58 | "date": "2017-01-23T09:36:08-06:00", 59 | "notes": [ 60 | "[Fixed] Fixed maxChildBlock from causing errors if using an older version of Neo.", 61 | "[Improved] Added field type checks to test if a field is available before attempting creation.", 62 | "[Improved] Added locale check to category group import." 63 | ] 64 | }, { 65 | "version": "1.5.5.2", 66 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.5.2.zip", 67 | "date": "2016-11-03T12:45:38-05:00", 68 | "notes": [ 69 | "[Fixed] Fixed maxChildBlock from causing errors if not part of the JSON model." 70 | ] 71 | }, { 72 | "version": "1.5.5.1", 73 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.5.1.zip", 74 | "date": "2016-11-03T10:49:36-05:00", 75 | "notes": [ 76 | "[Added] Support for NeoField's maxChildBlock setting added in Neo v1.4.0." 77 | ] 78 | }, { 79 | "version": "1.5.5", 80 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.5.zip", 81 | "date": "2016-11-02T08:36:00-05:00", 82 | "notes": [ 83 | "[Fixed] Correctly Fix Section import when hasUrls is set to false.", 84 | "[Fixed] Fix RichText field export when no available asset sources are selected." 85 | ] 86 | }, { 87 | "version": "1.5.4.1", 88 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.4.1.zip", 89 | "date": "2016-10-24T17:22:52-05:00", 90 | "notes": [ 91 | "[Fixed] Fix Section export/import to not always generate a primary locale.", 92 | "[Fixed] Fix Section enableVersioning flag always being true from incorrect parsing.", 93 | "[Fixed] Fix Group permissions createSubfoldersInAssetSource belongs in assetsource_perms." 94 | ] 95 | }, { 96 | "version": "1.5.4", 97 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.4.zip", 98 | "date": "2016-10-24T13:18:48-05:00", 99 | "notes": [ 100 | "[Fixed] Fix Section import when hasUrls is set to false.", 101 | "[Fixed] Fix User Group Permissions export/import of category groups using IDs instead of handles." 102 | ] 103 | }, { 104 | "version": "1.5.3.1", 105 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.3.1.zip", 106 | "date": "2016-10-24T10:12:48-05:00", 107 | "notes": [ 108 | "[Fixed] Fix Section export when using locales outside of primary locale." 109 | ] 110 | }, { 111 | "version": "1.5.2", 112 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.2.zip", 113 | "date": "2016-09-26T07:39:34-05:00", 114 | "notes": [ 115 | "[Fixed] Fix UserGroup error on Export tab when not using Pro edition of craft.", 116 | "[Fixed] Fix User field requiring an array to be imported successfully." 117 | ] 118 | }, { 119 | "version": "1.5.1", 120 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.1.zip", 121 | "date": "2016-09-22T09:11:11-05:00", 122 | "notes": [ 123 | "[Fixed] Fix UserGroup import order to ensure User Fields can select a group as a source during import." 124 | ] 125 | }, { 126 | "version": "1.5.0", 127 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.5.0.zip", 128 | "date": "2016-09-20T12:45:02-05:00", 129 | "notes": [ 130 | "[Added] Import / Exporting of Users", 131 | "[Added] Import / Exporting of User Groups", 132 | "[Added] Import / Exporting of Category Groups", 133 | "[Added] Some more graceful errors with sections and fields." 134 | ] 135 | }, { 136 | "version": "1.4.3", 137 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.4.3.zip", 138 | "date": "2016-08-30T09:19:37-05:00", 139 | "notes": [ 140 | "[Fixed] Fix importing section entry types that used identical handles." 141 | ] 142 | }, { 143 | "version": "1.4.2", 144 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.4.2.zip", 145 | "date": "2016-08-15T10:55:25-05:00", 146 | "notes": [ 147 | "[Fixed] Fix exporting of Sections urlFormat." 148 | ] 149 | }, { 150 | "version": "1.4.1", 151 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.4.1.zip", 152 | "date": "2016-07-18T14:28:46-05:00", 153 | "notes": [ 154 | "[Fixed] Fix exporting of PositionSelect fields having incorrect structure." 155 | ] 156 | }, { 157 | "version": "1.4.0", 158 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.4.0.zip", 159 | "date": "2016-06-24T17:06:09-05:00", 160 | "notes": [ 161 | "[Added] Fruit LinkIt import / export support.", 162 | "[Added] Reasons import / export support.", 163 | "[Added] Relabel import / export support.", 164 | "[Fixed] Fix exporting of RichText fields missing some settings." 165 | ] 166 | }, { 167 | "version": "1.3.3", 168 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.3.3.zip", 169 | "date": "2016-06-22T11:45:45-05:00", 170 | "notes": [ 171 | "[Fixed] yet another SuperTable export crash" 172 | ] 173 | }, { 174 | "version": "1.3.2", 175 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.3.2.zip", 176 | "date": "2016-06-22T11:09:18-05:00", 177 | "notes": [ 178 | "[Fixed] javascript dependent field selection", 179 | "[Fixed] SuperTable field sources exporting" 180 | ] 181 | }, { 182 | "version": "1.3.1", 183 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.3.1.zip", 184 | "date": "2016-06-22T10:43:48-05:00", 185 | "notes": [ 186 | "[Fixed] crashes related to requiredFields missing or not being arrays", 187 | "[Added] SuperTable exporting" 188 | ] 189 | }, { 190 | "version": "1.3.0", 191 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.3.0.zip", 192 | "date": "2016-06-22T09:14:51-05:00", 193 | "notes": [ 194 | "[Improved] Avoid déjà vu by selecting all on the export page instead of selecting each field individually.", 195 | "[Improved] \"We are all victims of causality. I make fields that are dependent on other fields, I must export the dependent fields with the main field. Cause and effect.\" You don't remember that line? Either way now when you select a field with dependencies, the dependencies are automatically selected, too.", 196 | "[Improved] Take the red pill. The Architect permits you to export Matrix fields as Neo fields." 197 | ] 198 | }, { 199 | "version": "1.2.2", 200 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.2.2.zip", 201 | "date": "2016-06-08T10:41:04-05:00", 202 | "notes": [ 203 | "[Fixed] getReleaseFeedUrl had the incorrect url" 204 | ] 205 | }, { 206 | "version": "1.2.1", 207 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.2.1.zip", 208 | "date": "2016-06-08T08:47:58-05:00", 209 | "notes": [ 210 | "[Improved] Disable checkbox for SuperTables since they do not export correctly.", 211 | "[Improved] Plugin Icons" 212 | ] 213 | }, { 214 | "version": "1.2.0", 215 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.2.0.zip", 216 | "date": "2016-06-03T12:08:30-05:00", 217 | "notes": [ 218 | "[Improved] Import assets and set field layouts in a specific order to allow fields that reference assets to be imported while allowing assets to reference fields itself.", 219 | "[Added] Check field layouts for missing fields to prevent database errors from craft." 220 | ] 221 | }, { 222 | "version": "1.1.2", 223 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.1.2.zip", 224 | "date": "2016-06-03T11:17:44-05:00", 225 | "notes": [ 226 | "[Fixed] Assets sources bug when sources is \"*\" instead of an array.", 227 | "[Fixed] Neo field export missing groups." 228 | ] 229 | }, { 230 | "version": "1.1.1", 231 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.1.1.zip", 232 | "date": "2016-06-02T11:30:12-05:00", 233 | "notes": [ 234 | "[Improved] Import Sections before fields" 235 | ] 236 | }, { 237 | "version": "1.1.0", 238 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.1.0.zip", 239 | "date": "2016-06-02T11:19:19-05:00", 240 | "notes": [ 241 | "[Added] Exporting of sections", 242 | "[Added] Exporting of sources", 243 | "[Added] Exporting of transforms", 244 | "[Added] Exporting of globals" 245 | ] 246 | }, { 247 | "version": "1.0.2", 248 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.0.2.zip", 249 | "date": "2016-06-01T11:50:29-05:00", 250 | "notes": [ 251 | "[Improved] JSON_NUMERIC_CHECK flag for json_encode" 252 | ] 253 | }, { 254 | "version": "1.0.1", 255 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.0.1.zip", 256 | "date": "2016-06-01T10:27:56-05:00", 257 | "notes": [ 258 | "[Added] getReleaseFeedUrl defined for craft update notifications about plugin" 259 | ] 260 | }, { 261 | "version": "1.0.1", 262 | "downloadUrl": "https://github.com/Pennebaker/craftcms-thearchitect/archive/v1.0.1.zip", 263 | "date": "2016-06-01T10:24:15-05:00", 264 | "notes": [ 265 | "[Added] Import and construct fields", 266 | "[Added] Exporting of fields" 267 | ] 268 | }] 269 | -------------------------------------------------------------------------------- /thearchitect/templates/blueprint.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% set title = "The Architect"|t %} 3 | 4 | {% import "_includes/forms" as forms %} 5 | 6 | {% set tabs = { 7 | tab1: { label: "Raw Input"|t, url: url('thearchitect') }, 8 | tab2: { label: "Available Files"|t, url: url('thearchitect/files') }, 9 | tab3: { label: "Export"|t, url: url('thearchitect/blueprint') }, 10 | tab4: { label: "Migrations"|t, url: url('thearchitect/migrations') }, 11 | tab5: { label: "Matrix to Neo Export"|t, url: url('thearchitect/convert') }, 12 | } %} 13 | 14 | {% set selectedTab = 'tab3' %} 15 | 16 | {% set content %} 17 |
18 | 19 |
20 | 21 | {{ getCsrfInput() }} 22 |

Fields

23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for field in craft.fields.getAllFields() %} 39 | {% set utilizedFields = {} %} 40 | {% if field.type == 'Neo' %} 41 | {% for blockType in craft.theArchitect.getNeoBlockTypesByFieldId(field.id) %} 42 | {% for blockTab in blockType.getFieldLayout().getTabs() %} 43 | {% for tabField in blockTab.getFields() %} 44 | {% set utilizedFields = utilizedFields | merge([tabField.fieldId]) %} 45 | {% endfor %} 46 | {% endfor %} 47 | {% endfor %} 48 | {% endif %} 49 | 50 | 53 | 56 | 59 | 62 | 63 | {% endfor %} 64 | 65 |
27 |
28 | 29 | Name 30 |
31 |
HandleTypeGroup
51 | 52 | 54 | {{ field.handle }} 55 | 57 | {{ field.type }} 58 | 60 | {{ field.group }} 61 |
66 |
67 |

Sections

68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {% for section in craft.sections.getAllSections() %} 84 | {% set utilizedFields = {} %} 85 | {% for entryType in section.getEntryTypes() %} 86 | {% for fieldId in craft.fields.getLayoutById(entryType.fieldLayoutId).getFieldIds() %} 87 | {% set utilizedFields = utilizedFields | merge([fieldId]) %} 88 | {% endfor %} 89 | {% endfor %} 90 | 91 | 96 | 99 | 102 | 105 | 106 | {% endfor %} 107 | 108 |
72 |
73 | 74 | Name 75 |
76 |
HandleTypeURL Format
92 |
93 | 94 |
95 |
97 | {{ section.handle }} 98 | 100 | {{ section.type }} 101 | 103 | {{ section.urlFormat }} 104 |
109 |
110 |

Sources

111 | 112 | 113 | 114 | 120 | 121 | 122 | 123 | 124 | 125 | {% for assetSource in assetSources %} 126 | {% set utilizedFields = {} %} 127 | {% for sourceTab in assetSource.getFieldLayout().getTabs() %} 128 | {% for tabField in sourceTab.getFields() %} 129 | {% set utilizedFields = utilizedFields | merge([tabField.fieldId]) %} 130 | {% endfor %} 131 | {% endfor %} 132 | 133 | 136 | 139 | 142 | 143 | {% endfor %} 144 | 145 |
115 |
116 | 117 | Name 118 |
119 |
HandleType
134 | 135 | 137 | {{ assetSource.handle }} 138 | 140 | {{ assetSource.type }} 141 |
146 |
147 |

Transforms

148 | 149 | 150 | 151 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | {% for assetTransform in assetTransforms %} 165 | 166 | 169 | 172 | 175 | 178 | 181 | 182 | {% endfor %} 183 | 184 |
152 |
153 | 154 | Name 155 |
156 |
HandleModeDimensionsFormat
167 | 168 | 170 | {{ assetTransform.handle }} 171 | 173 | {{ assetTransform.mode }} 174 | 176 | {{ assetTransform.width }} × {{ assetTransform.height }} 177 | 179 | {{ (assetTransform.format) ? assetTransform.format : 'auto' }} 180 |
185 |
186 |

Globals

187 | 188 | 189 | 190 | 196 | 197 | 198 | 199 | 200 | {% for global in craft.globals.getAllSets() %} 201 | {% set utilizedFields = {} %} 202 | {% for sourceTab in global.getFieldLayout().getTabs() %} 203 | {% for tabField in sourceTab.getFields() %} 204 | {% set utilizedFields = utilizedFields | merge([tabField.fieldId]) %} 205 | {% endfor %} 206 | {% endfor %} 207 | 208 | 211 | 214 | 215 | {% endfor %} 216 | 217 |
191 |
192 | 193 | Name 194 |
195 |
Handle
209 | 210 | 212 | {{ global.handle }} 213 |
218 |
219 |

Categories

220 | 221 | 222 | 223 | 229 | 230 | 231 | 232 | 233 | {% for category in craft.categoryGroups.getAllGroups() %} 234 | {% set utilizedFields = {} %} 235 | {% for sourceTab in category.getFieldLayout().getTabs() %} 236 | {% for tabField in sourceTab.getFields() %} 237 | {% set utilizedFields = utilizedFields | merge([tabField.fieldId]) %} 238 | {% endfor %} 239 | {% endfor %} 240 | 241 | 244 | 247 | 248 | {% endfor %} 249 | 250 |
224 |
225 | 226 | Name 227 |
228 |
Handle
242 | 243 | 245 | {{ category.handle }} 246 |
251 |
252 |

Routes

253 | 254 | 255 | 256 | 262 | 263 | 264 | 265 | 266 | {% for route in craft.routes.getDbRoutes() %} 267 | 268 | 271 | 274 | 275 | {% endfor %} 276 | 277 |
257 |
258 | 259 | URI 260 |
261 |
Template
269 | 270 | 272 | {{ route.template }} 273 |
278 |
279 |

Tags

280 | 281 | 282 | 283 | 289 | 290 | 291 | 292 | 293 | {% for tag in craft.theArchitect.getAllTagGroups() %} 294 | 295 | 298 | 301 | 302 | {% endfor %} 303 | 304 |
284 |
285 | 286 | Name 287 |
288 |
Handle
296 | 297 | 299 | {{ tag.handle }} 300 |
305 |
306 |

Users

307 | 308 | 309 | 310 | 316 | 317 | 318 | 319 | 320 | 321 | {% for user in craft.theArchitect.getAllUsers() %} 322 | {% set utilizedGroups = {} %} 323 | {% for group in user.getGroups() %} 324 | {% set utilizedGroups = utilizedGroups | merge([group.id]) %} 325 | {% endfor %} 326 | 327 | 330 | 333 | 336 | 337 | {% endfor %} 338 | 339 |
311 |
312 | 313 | Name 314 |
315 |
UsernameEmail
328 | 329 | 331 | {{ user.username }} 332 | 334 | {{ user.email }} 335 |
340 |
341 |

User Groups

342 | 343 | 344 | 345 | 351 | 352 | 353 | 354 | 355 | {% if craft.userGroups is not null %} 356 | {% for group in craft.userGroups.getAllGroups() %} 357 | 358 | 361 | 364 | 365 | {% endfor %} 366 | {% endif %} 367 | 368 |
346 |
347 | 348 | Name 349 |
350 |
Handle
359 | 360 | 362 | {{ group.handle }} 363 |
369 |
370 | {% if craft.plugins.getPlugin('commerce') %} 371 |

Product Types

372 | 373 | 374 | 375 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | {% for productType in craft.commerce.getProductTypes() %} 388 | 389 | 392 | 395 | 398 | 401 | 402 | {% endfor %} 403 | 404 |
376 |
377 | 378 | Name 379 |
380 |
HandleTitle FormatTemplate
390 | 391 | 393 | {{ productType.handle }} 394 | 396 | {{ productType.titleFormat }} 397 | 399 | {{ productType.template }} 400 |
405 |
406 | {% endif %} 407 |
408 | 409 | 410 |
411 |
412 | 413 |
414 | {% endset %} 415 | -------------------------------------------------------------------------------- /thearchitect/resources/js/diff.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | diff v2.2.3 4 | 5 | Software License Agreement (BSD License) 6 | 7 | Copyright (c) 2009-2015, Kevin Decker 8 | 9 | All rights reserved. 10 | 11 | Redistribution and use of this software in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above 15 | copyright notice, this list of conditions and the 16 | following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above 19 | copyright notice, this list of conditions and the 20 | following disclaimer in the documentation and/or other 21 | materials provided with the distribution. 22 | 23 | * Neither the name of Kevin Decker nor the names of its 24 | contributors may be used to endorse or promote products 25 | derived from this software without specific prior 26 | written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 29 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 30 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 31 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 34 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 35 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | @license 37 | */ 38 | !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.JsDiff=b():a.JsDiff=b()}(this,function(){/******/ 39 | return function(a){/******/ 40 | // The require function 41 | /******/ 42 | function b(d){/******/ 43 | // Check if module is in cache 44 | /******/ 45 | if(c[d])/******/ 46 | return c[d].exports;/******/ 47 | // Create a new module (and put it into the cache) 48 | /******/ 49 | var e=c[d]={/******/ 50 | exports:{},/******/ 51 | id:d,/******/ 52 | loaded:!1};/******/ 53 | // Return the exports of the module 54 | /******/ 55 | /******/ 56 | // Execute the module function 57 | /******/ 58 | /******/ 59 | // Flag the module as loaded 60 | /******/ 61 | return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}// webpackBootstrap 62 | /******/ 63 | // The module cache 64 | /******/ 65 | var c={};/******/ 66 | // Load entry module and return exports 67 | /******/ 68 | /******/ 69 | // expose the modules object (__webpack_modules__) 70 | /******/ 71 | /******/ 72 | // expose the module cache 73 | /******/ 74 | /******/ 75 | // __webpack_public_path__ 76 | /******/ 77 | return b.m=a,b.c=c,b.p="",b(0)}([/* 0 */ 78 | /***/ 79 | function(a,b,c){/*istanbul ignore start*/ 80 | "use strict";/*istanbul ignore start*/ 81 | function d(a){return a&&a.__esModule?a:{"default":a}}b.__esModule=!0,b.canonicalize=b.convertChangesToXML=b.convertChangesToDMP=b.parsePatch=b.applyPatches=b.applyPatch=b.createPatch=b.createTwoFilesPatch=b.structuredPatch=b.diffJson=b.diffCss=b.diffSentences=b.diffTrimmedLines=b.diffLines=b.diffWordsWithSpace=b.diffWords=b.diffChars=b.Diff=void 0;/*istanbul ignore end*/ 82 | var/*istanbul ignore start*/e=c(1),f=d(e),/*istanbul ignore start*/g=c(2),/*istanbul ignore start*/h=c(3),/*istanbul ignore start*/i=c(5),/*istanbul ignore start*/j=c(6),/*istanbul ignore start*/k=c(7),/*istanbul ignore start*/l=c(8),/*istanbul ignore start*/m=c(9),/*istanbul ignore start*/n=c(10),/*istanbul ignore start*/o=c(12),/*istanbul ignore start*/p=c(13),/*istanbul ignore start*/q=c(14);/* See LICENSE file for terms of use */ 83 | /* 84 | * Text diff implementation. 85 | * 86 | * This library supports the following APIS: 87 | * JsDiff.diffChars: Character by character diff 88 | * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace 89 | * JsDiff.diffLines: Line based diff 90 | * 91 | * JsDiff.diffCss: Diff targeted at CSS content 92 | * 93 | * These methods are based on the implementation proposed in 94 | * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). 95 | * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 96 | */ 97 | b.Diff=f["default"],/*istanbul ignore start*/ 98 | b.diffChars=g.diffChars,/*istanbul ignore start*/ 99 | b.diffWords=h.diffWords,/*istanbul ignore start*/ 100 | b.diffWordsWithSpace=h.diffWordsWithSpace,/*istanbul ignore start*/ 101 | b.diffLines=i.diffLines,/*istanbul ignore start*/ 102 | b.diffTrimmedLines=i.diffTrimmedLines,/*istanbul ignore start*/ 103 | b.diffSentences=j.diffSentences,/*istanbul ignore start*/ 104 | b.diffCss=k.diffCss,/*istanbul ignore start*/ 105 | b.diffJson=l.diffJson,/*istanbul ignore start*/ 106 | b.structuredPatch=o.structuredPatch,/*istanbul ignore start*/ 107 | b.createTwoFilesPatch=o.createTwoFilesPatch,/*istanbul ignore start*/ 108 | b.createPatch=o.createPatch,/*istanbul ignore start*/ 109 | b.applyPatch=m.applyPatch,/*istanbul ignore start*/ 110 | b.applyPatches=m.applyPatches,/*istanbul ignore start*/ 111 | b.parsePatch=n.parsePatch,/*istanbul ignore start*/ 112 | b.convertChangesToDMP=p.convertChangesToDMP,/*istanbul ignore start*/ 113 | b.convertChangesToXML=q.convertChangesToXML,/*istanbul ignore start*/ 114 | b.canonicalize=l.canonicalize},/* 1 */ 115 | /***/ 116 | function(a,b){/*istanbul ignore start*/ 117 | "use strict";function c(){}function d(a,b,c,d,e){for(var f=0,g=b.length,h=0,i=0;g>f;f++){var j=b[f];if(j.removed){ 118 | // Reverse add and remove so removes are output first to match common convention 119 | // The diffing algorithm is tied to add then remove output and this is the simplest 120 | // route to get the desired output with minimal overhead. 121 | if(j.value=d.slice(i,i+j.count).join(""),i+=j.count,f&&b[f-1].added){var k=b[f-1];b[f-1]=b[f],b[f]=k}}else{if(!j.added&&e){var l=c.slice(h,h+j.count);l=l.map(function(a,b){var c=d[i+b];return c.length>a.length?c:a}),j.value=l.join("")}else j.value=c.slice(h,h+j.count).join("");h+=j.count, 122 | // Common case 123 | j.added||(i+=j.count)}} 124 | // Special case handle for when one terminal is ignored. For this case we merge the 125 | // terminal into the prior string and drop the change. 126 | var m=b[g-1];return g>1&&(m.added||m.removed)&&a.equals("",m.value)&&(b[g-2].value+=m.value,b.pop()),b}function e(a){return{newPos:a.newPos,components:a.components.slice(0)}}b.__esModule=!0,b["default"]=/*istanbul ignore end*/c,c.prototype={/*istanbul ignore start*/ 127 | /*istanbul ignore end*/ 128 | diff:function(a,b){function c(a){return h?(setTimeout(function(){h(void 0,a)},0),!0):a} 129 | // Main worker method. checks all permutations of a given edit length for acceptance. 130 | function f(){for(var f=-1*l;l>=f;f+=2){var g=void 0,h=n[f-1],m=n[f+1],o=(m?m.newPos:0)-f;h&&( 131 | // No one else is going to attempt to use this value, clear it 132 | n[f-1]=void 0);var p=h&&h.newPos+1=0&&k>o;if(p||q){ 133 | // If we have hit the end of both strings, then we are done 134 | if( 135 | // Select the diagonal that we want to branch from. We select the prior 136 | // path whose position in the new string is the farthest from the origin 137 | // and does not pass the bounds of the diff graph 138 | !p||q&&h.newPos=j&&o+1>=k)return c(d(i,g.components,b,a,i.useLongestToken)); 139 | // Otherwise track this path as a potential candidate and continue. 140 | n[f]=g}else 141 | // If this path is a terminal then prune 142 | n[f]=void 0}l++}/*istanbul ignore start*/ 143 | var/*istanbul ignore end*/g=arguments.length<=2||void 0===arguments[2]?{}:arguments[2],h=g.callback;"function"==typeof g&&(h=g,g={}),this.options=g;var i=this;a=this.castInput(a),b=this.castInput(b),a=this.removeEmpty(this.tokenize(a)),b=this.removeEmpty(this.tokenize(b));var j=b.length,k=a.length,l=1,m=j+k,n=[{newPos:-1,components:[]}],o=this.extractCommon(n[0],b,a,0);if(n[0].newPos+1>=j&&o+1>=k) 144 | // Identity per the equality and tokenizer 145 | return c([{value:b.join(""),count:b.length}]); 146 | // Performs the length of edit iteration. Is a bit fugly as this has to support the 147 | // sync and async mode which is never fun. Loops over execEditLength until a value 148 | // is produced. 149 | if(h)!function q(){setTimeout(function(){ 150 | // This should not happen, but we want to be safe. 151 | /* istanbul ignore next */ 152 | // This should not happen, but we want to be safe. 153 | /* istanbul ignore next */ 154 | return l>m?h():void(f()||q())},0)}();else for(;m>=l;){var p=f();if(p)return p}},/*istanbul ignore start*/ 155 | /*istanbul ignore end*/ 156 | pushComponent:function(a,b,c){var d=a[a.length-1];d&&d.added===b&&d.removed===c? 157 | // We need to clone here as the component clone operation is just 158 | // as shallow array clone 159 | a[a.length-1]={count:d.count+1,added:b,removed:c}:a.push({count:1,added:b,removed:c})},/*istanbul ignore start*/ 160 | /*istanbul ignore end*/ 161 | extractCommon:function(a,b,c,d){for(var e=b.length,f=c.length,g=a.newPos,h=g-d,i=0;e>g+1&&f>h+1&&this.equals(b[g+1],c[h+1]);)g++,h++,i++;return i&&a.components.push({count:i}),a.newPos=g,h},/*istanbul ignore start*/ 162 | /*istanbul ignore end*/ 163 | equals:function(a,b){return a===b},/*istanbul ignore start*/ 164 | /*istanbul ignore end*/ 165 | removeEmpty:function(a){for(var b=[],c=0;ck))return!1;b++}}return!0}/*istanbul ignore start*/ 223 | var/*istanbul ignore end*/d=arguments.length<=2||void 0===arguments[2]?{}:arguments[2];if("string"==typeof b&&(b=/*istanbul ignore start*/(0,g.parsePatch)(b)),Array.isArray(b)){if(b.length>1)throw new Error("applyPatch only works with a single input.");b=b[0]} 224 | // Search best fit offsets for each hunk based on the previous ones 225 | for(var e=a.split("\n"),f=b.hunks,h=d.compareLine||function(a,b,c,d){/*istanbul ignore end*/ 226 | return b===d},j=0,k=d.fuzzFactor||0,l=0,m=0,n=void 0,o=void 0,p=0;p=a+g)return g;f=!0} 270 | // Check if trying to fit before text beginning, and if not, check it fits 271 | // before offset location 272 | return e?void 0:(f||(d=!0),a-g>=b?-g++:(e=!0,h()))}}},/* 12 */ 273 | /***/ 274 | function(a,b,c){/*istanbul ignore start*/ 275 | "use strict";/*istanbul ignore start*/ 276 | function d(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b0?j(h.lines.slice(-i.context)):[],m-=o.length,n-=o.length)} 282 | // Output our changes 283 | /*istanbul ignore start*/ 284 | (g=/*istanbul ignore end*/o).push.apply(/*istanbul ignore start*/g,/*istanbul ignore start*/d(/*istanbul ignore end*/f.map(function(a){return(b.added?"+":"-")+a}))), 285 | // Track the updated file position 286 | b.added?q+=f.length:p+=f.length}else{ 287 | // Identical context lines. Track line changes 288 | if(m) 289 | // Close out any changes that have been output (or join overlapping) 290 | if(f.length<=2*i.context&&a=k.length-2&&f.length<=i.context){ 297 | // EOF is inside this hunk 298 | var v=/\n$/.test(c),w=/\n$/.test(e);0!=f.length||v?v&&w||o.push("\\ No newline at end of file"): 299 | // special case: old has no eol and no trailing context; no-nl can end up before adds 300 | o.splice(u.oldLines,0,"\\ No newline at end of file")}l.push(u),m=0,n=0,o=[]}p+=f.length,q+=f.length}},s=0;s"):e.removed&&b.push(""),b.push(d(e.value)),e.added?b.push(""):e.removed&&b.push("")}return b.join("")}function d(a){var b=a;return b=b.replace(/&/g,"&"),b=b.replace(//g,">"),b=b.replace(/"/g,""")}b.__esModule=!0,b.convertChangesToXML=c}])}); 312 | --------------------------------------------------------------------------------