├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── _build ├── config.json ├── gpm_resolvers │ └── gpm.resolve.tables.php ├── resolvers │ ├── resolve.synctables.php │ └── resolve.treehillstudio.php └── setup.options.php ├── _packages ├── customrequest-1.0.3-pl.transport.zip ├── customrequest-1.1.0-pl.transport.zip ├── customrequest-1.1.1-pl.transport.zip ├── customrequest-1.1.2-pl.transport.zip ├── customrequest-1.1.3-pl.transport.zip ├── customrequest-1.1.4-pl.transport.zip ├── customrequest-1.2.0-pl.transport.zip ├── customrequest-1.2.1-pl.transport.zip ├── customrequest-1.2.2-pl.transport.zip ├── customrequest-1.2.3-pl.transport.zip ├── customrequest-1.2.4-pl.transport.zip ├── customrequest-1.2.5-pl.transport.zip ├── customrequest-1.2.6-pl.transport.zip ├── customrequest-1.2.7-pl.transport.zip ├── customrequest-1.3.0-pl.transport.zip ├── customrequest-1.3.1-pl.transport.zip ├── customrequest-1.3.10-pl.transport.zip ├── customrequest-1.3.2-pl.transport.zip ├── customrequest-1.3.3-pl2.transport.zip ├── customrequest-1.3.4-pl.transport.zip ├── customrequest-1.3.5-pl.transport.zip ├── customrequest-1.3.6-pl.transport.zip ├── customrequest-1.3.7-pl.transport.zip ├── customrequest-1.3.8-pl.transport.zip └── customrequest-1.3.9-pl.transport.zip ├── assets └── components │ └── customrequest │ ├── connector.php │ ├── css │ └── mgr │ │ └── customrequest.min.css │ ├── img │ └── mgr │ │ ├── treehill-studio-small.png │ │ ├── treehill-studio-small@2x.png │ │ ├── treehill-studio.png │ │ └── treehill-studio@2x.png │ └── js │ └── mgr │ └── customrequest.min.js ├── core └── components │ └── customrequest │ ├── composer.json │ ├── composer.lock │ ├── controllers │ └── home.class.php │ ├── docs │ ├── changelog.md │ ├── license.md │ └── readme.md │ ├── elements │ └── plugins │ │ └── customrequest.plugin.php │ ├── lexicon │ ├── de │ │ ├── default.inc.php │ │ └── setting.inc.php │ ├── en │ │ ├── default.inc.php │ │ └── setting.inc.php │ └── ru │ │ ├── default.inc.php │ │ └── setting.inc.php │ ├── model │ ├── customrequest │ │ ├── customrequest.class.php │ │ ├── customrequestconfigs.class.php │ │ ├── metadata.mysql.php │ │ └── mysql │ │ │ ├── customrequestconfigs.class.php │ │ │ └── customrequestconfigs.map.inc.php │ └── schema │ │ └── customrequest.mysql.schema.xml │ ├── processors │ └── mgr │ │ ├── configs │ │ ├── create.class.php │ │ ├── getlist.class.php │ │ ├── remove.class.php │ │ ├── sortindex.class.php │ │ └── update.class.php │ │ ├── resources │ │ └── getlist.class.php │ │ └── settings │ │ ├── getlist.class.php │ │ ├── update.class.php │ │ └── updatefromgrid.class.php │ ├── src │ ├── CustomRequest.php │ ├── Plugins │ │ ├── Events │ │ │ ├── OnDocFormDelete.php │ │ │ ├── OnDocFormSave.php │ │ │ ├── OnDocPublished.php │ │ │ ├── OnDocUnPublished.php │ │ │ ├── OnPageNotFound.php │ │ │ └── OnSiteRefresh.php │ │ └── Plugin.php │ └── Processors │ │ ├── ObjectCreateProcessor.php │ │ ├── ObjectGetListProcessor.php │ │ ├── ObjectRemoveProcessor.php │ │ ├── ObjectSortindexProcessor.php │ │ ├── ObjectUpdateProcessor.php │ │ └── Processor.php │ └── templates │ └── home.tpl ├── docs ├── contributors.md ├── examples.md ├── index.md ├── installation.md └── usage.md ├── gulpfile.js ├── icon ├── customrequest.ai └── customrequest.png ├── mkdocs.yml ├── package-lock.json ├── package.json └── source ├── css └── mgr │ └── customrequest.css ├── img └── mgr │ ├── treehill-studio-small.png │ ├── treehill-studio-small@2x.png │ ├── treehill-studio.png │ └── treehill-studio@2x.png ├── js └── mgr │ ├── customrequest.js │ ├── helper │ ├── combo.js │ └── util.js │ ├── sections │ └── home.js │ └── widgets │ ├── configs.grid.js │ ├── home.panel.js │ └── settings.panel.js └── sass └── mgr ├── _modx3.scss └── customrequest.scss /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Jako 4 | ko_fi: treehillstudio 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/build.config.php 2 | config.core.php 3 | 4 | _build/site 5 | 6 | icon/*.svg 7 | 8 | _packages/*.* 9 | !_packages/.gitignore 10 | !_packages/*.zip 11 | 12 | node_modules/ 13 | core/components/*/vendor/ 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | -------------------------- 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | License is intended to guarantee your freedom to share and change free 17 | software--to make sure the software is free for all its users. This 18 | General Public License applies to most of the Free Software 19 | Foundation's software and to any other program whose authors commit to 20 | using it. (Some other Free Software Foundation software is covered by 21 | the GNU Library General Public License instead.) You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if you 34 | distribute copies of the software, or if you modify it. 35 | 36 | For example, if you distribute copies of such a program, whether 37 | gratis or for a fee, you must give the recipients all the rights that 38 | you have. You must make sure that they, too, receive or can get the 39 | source code. And you must show them these terms so they know their 40 | rights. 41 | 42 | We protect your rights with two steps: (1) copyright the software, and 43 | (2) offer you this license which gives you legal permission to copy, 44 | distribute and/or modify the software. 45 | 46 | Also, for each author's protection and ours, we want to make certain 47 | that everyone understands that there is no warranty for this free 48 | software. If the software is modified by someone else and passed on, we 49 | want its recipients to know that what they have is not the original, so 50 | that any problems introduced by others will not reflect on the original 51 | authors' reputations. 52 | 53 | Finally, any free program is threatened constantly by software 54 | patents. We wish to avoid the danger that redistributors of a free 55 | program will individually obtain patent licenses, in effect making the 56 | program proprietary. To prevent this, we have made it clear that any 57 | patent must be licensed for everyone's free use or not licensed at all. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | 63 | GNU GENERAL PUBLIC LICENSE 64 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 65 | --------------------------------------------------------------- 66 | 67 | 0. This License applies to any program or other work which contains 68 | a notice placed by the copyright holder saying it may be distributed 69 | under the terms of this General Public License. The "Program", below, 70 | refers to any such program or work, and a "work based on the Program" 71 | means either the Program or any derivative work under copyright law: 72 | that is to say, a work containing the Program or a portion of it, 73 | either verbatim or with modifications and/or translated into another 74 | language. (Hereinafter, translation is included without limitation in 75 | the term "modification".) Each licensee is addressed as "you". 76 | 77 | Activities other than copying, distribution and modification are not 78 | covered by this License; they are outside its scope. The act of 79 | running the Program is not restricted, and the output from the Program 80 | is covered only if its contents constitute a work based on the 81 | Program (independent of having been made by running the Program). 82 | Whether that is true depends on what the Program does. 83 | 84 | 1. You may copy and distribute verbatim copies of the Program's 85 | source code as you receive it, in any medium, provided that you 86 | conspicuously and appropriately publish on each copy an appropriate 87 | copyright notice and disclaimer of warranty; keep intact all the 88 | notices that refer to this License and to the absence of any warranty; 89 | and give any other recipients of the Program a copy of this License 90 | along with the Program. 91 | 92 | You may charge a fee for the physical act of transferring a copy, and 93 | you may at your option offer warranty protection in exchange for a fee. 94 | 95 | 2. You may modify your copy or copies of the Program or any portion 96 | of it, thus forming a work based on the Program, and copy and 97 | distribute such modifications or work under the terms of Section 1 98 | above, provided that you also meet all of these conditions: 99 | 100 | a) You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | b) You must cause any work that you distribute or publish, that in 104 | whole or in part contains or is derived from the Program or any 105 | part thereof, to be licensed as a whole at no charge to all third 106 | parties under the terms of this License. 107 | 108 | c) If the modified program normally reads commands interactively 109 | when run, you must cause it, when started running for such 110 | interactive use in the most ordinary way, to print or display an 111 | announcement including an appropriate copyright notice and a 112 | notice that there is no warranty (or else, saying that you provide 113 | a warranty) and that users may redistribute the program under 114 | these conditions, and telling the user how to view a copy of this 115 | License. (Exception: if the Program itself is interactive but 116 | does not normally print such an announcement, your work based on 117 | the Program is not required to print an announcement.) 118 | 119 | These requirements apply to the modified work as a whole. If 120 | identifiable sections of that work are not derived from the Program, 121 | and can be reasonably considered independent and separate works in 122 | themselves, then this License, and its terms, do not apply to those 123 | sections when you distribute them as separate works. But when you 124 | distribute the same sections as part of a whole which is a work based 125 | on the Program, the distribution of the whole must be on the terms of 126 | this License, whose permissions for other licensees extend to the 127 | entire whole, and thus to each and every part regardless of who wrote it. 128 | 129 | Thus, it is not the intent of this section to claim rights or contest 130 | your rights to work written entirely by you; rather, the intent is to 131 | exercise the right to control the distribution of derivative or 132 | collective works based on the Program. 133 | 134 | In addition, mere aggregation of another work not based on the Program 135 | with the Program (or with a work based on the Program) on a volume of 136 | a storage or distribution medium does not bring the other work under 137 | the scope of this License. 138 | 139 | 3. You may copy and distribute the Program (or a work based on it, 140 | under Section 2) in object code or executable form under the terms of 141 | Sections 1 and 2 above provided that you also do one of the following: 142 | 143 | a) Accompany it with the complete corresponding machine-readable 144 | source code, which must be distributed under the terms of Sections 145 | 1 and 2 above on a medium customarily used for software interchange; or, 146 | 147 | b) Accompany it with a written offer, valid for at least three 148 | years, to give any third party, for a charge no more than your 149 | cost of physically performing source distribution, a complete 150 | machine-readable copy of the corresponding source code, to be 151 | distributed under the terms of Sections 1 and 2 above on a medium 152 | customarily used for software interchange; or, 153 | 154 | c) Accompany it with the information you received as to the offer 155 | to distribute corresponding source code. (This alternative is 156 | allowed only for noncommercial distribution and only if you 157 | received the program in object code or executable form with such 158 | an offer, in accord with Subsection b above.) 159 | 160 | The source code for a work means the preferred form of the work for 161 | making modifications to it. For an executable work, complete source 162 | code means all the source code for all modules it contains, plus any 163 | associated interface definition files, plus the scripts used to 164 | control compilation and installation of the executable. However, as a 165 | special exception, the source code distributed need not include 166 | anything that is normally distributed (in either source or binary 167 | form) with the major components (compiler, kernel, and so on) of the 168 | operating system on which the executable runs, unless that component 169 | itself accompanies the executable. 170 | 171 | If distribution of executable or object code is made by offering 172 | access to copy from a designated place, then offering equivalent 173 | access to copy the source code from the same place counts as 174 | distribution of the source code, even though third parties are not 175 | compelled to copy the source along with the object code. 176 | 177 | 4. You may not copy, modify, sublicense, or distribute the Program 178 | except as expressly provided under this License. Any attempt 179 | otherwise to copy, modify, sublicense or distribute the Program is 180 | void, and will automatically terminate your rights under this License. 181 | However, parties who have received copies, or rights, from you under 182 | this License will not have their licenses terminated so long as such 183 | parties remain in full compliance. 184 | 185 | 5. You are not required to accept this License, since you have not 186 | signed it. However, nothing else grants you permission to modify or 187 | distribute the Program or its derivative works. These actions are 188 | prohibited by law if you do not accept this License. Therefore, by 189 | modifying or distributing the Program (or any work based on the 190 | Program), you indicate your acceptance of this License to do so, and 191 | all its terms and conditions for copying, distributing or modifying 192 | the Program or works based on it. 193 | 194 | 6. Each time you redistribute the Program (or any work based on the 195 | Program), the recipient automatically receives a license from the 196 | original licensor to copy, distribute or modify the Program subject to 197 | these terms and conditions. You may not impose any further 198 | restrictions on the recipients' exercise of the rights granted herein. 199 | You are not responsible for enforcing compliance by third parties to 200 | this License. 201 | 202 | 7. If, as a consequence of a court judgment or allegation of patent 203 | infringement or for any other reason (not limited to patent issues), 204 | conditions are imposed on you (whether by court order, agreement or 205 | otherwise) that contradict the conditions of this License, they do not 206 | excuse you from the conditions of this License. If you cannot 207 | distribute so as to satisfy simultaneously your obligations under this 208 | License and any other pertinent obligations, then as a consequence you 209 | may not distribute the Program at all. For example, if a patent 210 | license would not permit royalty-free redistribution of the Program by 211 | all those who receive copies directly or indirectly through you, then 212 | the only way you could satisfy both it and this License would be to 213 | refrain entirely from distribution of the Program. 214 | 215 | If any portion of this section is held invalid or unenforceable under 216 | any particular circumstance, the balance of the section is intended to 217 | apply and the section as a whole is intended to apply in other 218 | circumstances. 219 | 220 | It is not the purpose of this section to induce you to infringe any 221 | patents or other property right claims or to contest validity of any 222 | such claims; this section has the sole purpose of protecting the 223 | integrity of the free software distribution system, which is 224 | implemented by public license practices. Many people have made 225 | generous contributions to the wide range of software distributed 226 | through that system in reliance on consistent application of that 227 | system; it is up to the author/donor to decide if he or she is willing 228 | to distribute software through any other system and a licensee cannot 229 | impose that choice. 230 | 231 | This section is intended to make thoroughly clear what is believed to 232 | be a consequence of the rest of this License. 233 | 234 | 8. If the distribution and/or use of the Program is restricted in 235 | certain countries either by patents or by copyrighted interfaces, the 236 | original copyright holder who places the Program under this License 237 | may add an explicit geographical distribution limitation excluding 238 | those countries, so that distribution is permitted only in or among 239 | countries not thus excluded. In such case, this License incorporates 240 | the limitation as if written in the body of this License. 241 | 242 | 9. The Free Software Foundation may publish revised and/or new versions 243 | of the General Public License from time to time. Such new versions will 244 | be similar in spirit to the present version, but may differ in detail to 245 | address new problems or concerns. 246 | 247 | Each version is given a distinguishing version number. If the Program 248 | specifies a version number of this License which applies to it and "any 249 | later version", you have the option of following the terms and conditions 250 | either of that version or of any later version published by the Free 251 | Software Foundation. If the Program does not specify a version number of 252 | this License, you may choose any version ever published by the Free Software 253 | Foundation. 254 | 255 | 10. If you wish to incorporate parts of the Program into other free 256 | programs whose distribution conditions are different, write to the author 257 | to ask for permission. For software which is copyrighted by the Free 258 | Software Foundation, write to the Free Software Foundation; we sometimes 259 | make exceptions for this. Our decision will be guided by the two goals 260 | of preserving the free status of all derivatives of our free software and 261 | of promoting the sharing and reuse of software generally. 262 | 263 | NO WARRANTY 264 | ----------- 265 | 266 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 267 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 268 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 269 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 270 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 271 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 272 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 273 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 274 | REPAIR OR CORRECTION. 275 | 276 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 277 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 278 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 279 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 280 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 281 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 282 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 283 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 284 | POSSIBILITY OF SUCH DAMAGES. 285 | 286 | --------------------------- 287 | END OF TERMS AND CONDITIONS 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Weblate](https://hosted.weblate.org/widget/modx-extras/customrequest/standard/svg-badge.svg)](https://hosted.weblate.org/projects/modx-extras/customrequest/standard/) 2 | 3 | # CustomRequest 4 | 5 | Use pretty URLs everywhere in the MODX Revolution frontend 6 | 7 | ## Features 8 | 9 | CustomRequest is an effective routing plugin for MODX Revolution to map pretty 10 | but not found URLs to a MODX resource and set the request parameters by 11 | separating the URI path at the URI separators or by a regular expression. 12 | 13 | ## Installation 14 | 15 | MODX Package Management 16 | 17 | ## Usage 18 | 19 | Install via package manager and create your routings with the custom manager 20 | page that could be found in the extras menu. 21 | 22 | ## Documentation 23 | 24 | For more information please read the [documentation](https://jako.github.io/CustomRequest/). 25 | 26 | ## Translations 27 | 28 | Translations of the package can be made for the [Default Lexicon](https://hosted.weblate.org/projects/modx-extras/customrequest/standard/) and the [System Setting Lexicon](https://hosted.weblate.org/projects/modx-extras/customrequest/system-settings/) 29 | -------------------------------------------------------------------------------- /_build/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomRequest", 3 | "lowCaseName": "customrequest", 4 | "description": "Use friendly URLs everywhere", 5 | "author": "Thomas Jakobi", 6 | "version": "1.3.10", 7 | "package": { 8 | "menus": [ 9 | { 10 | "text": "customrequest.menu_home", 11 | "description": "customrequest.menu_home_desc", 12 | "action": "home", 13 | "parent": "components" 14 | } 15 | ], 16 | "elements": { 17 | "plugins": [ 18 | { 19 | "name": "CustomRequest", 20 | "description": "Use friendly URLs everywhere", 21 | "file": "customrequest.plugin.php", 22 | "events": [ 23 | "OnDocFormDelete", 24 | "OnDocFormSave", 25 | "OnDocPublished", 26 | "OnDocUnPublished", 27 | "OnPageNotFound", 28 | "OnSiteRefresh" 29 | ] 30 | } 31 | ] 32 | }, 33 | "systemSettings": [ 34 | { 35 | "key": "debug", 36 | "type": "combo-boolean", 37 | "area": "system", 38 | "value": "0" 39 | } 40 | ] 41 | }, 42 | "database": { 43 | "tables": [ 44 | "CustomrequestConfigs" 45 | ] 46 | }, 47 | "dependencies": [ 48 | { 49 | "name": "php", 50 | "version": ">=7.2" 51 | }, 52 | { 53 | "name": "modx", 54 | "version": ">=2.6" 55 | } 56 | ], 57 | "build": { 58 | "readme": "docs/readme.md", 59 | "license": "docs/license.md", 60 | "changelog": "docs/changelog.md", 61 | "setupOptions": { 62 | "source": "setup.options.php" 63 | }, 64 | "resolver": { 65 | "after": [ 66 | "resolve.synctables.php", 67 | "resolve.treehillstudio.php" 68 | ] 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /_build/gpm_resolvers/gpm.resolve.tables.php: -------------------------------------------------------------------------------- 1 | xpdo) { 16 | $modx =& $object->xpdo; 17 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 18 | case xPDOTransport::ACTION_INSTALL: 19 | case xPDOTransport::ACTION_UPGRADE: 20 | $modelPath = $modx->getOption('customrequest.core_path', null, $modx->getOption('core_path') . 'components/customrequest/') . 'model/'; 21 | 22 | $modx->addPackage('customrequest', $modelPath, null); 23 | 24 | 25 | $manager = $modx->getManager(); 26 | 27 | $manager->createObjectContainer('CustomrequestConfigs'); 28 | 29 | break; 30 | } 31 | } 32 | 33 | return true; -------------------------------------------------------------------------------- /_build/resolvers/resolve.synctables.php: -------------------------------------------------------------------------------- 1 | getTableName($table); 20 | $tableName = str_replace('`', '', $tableName); 21 | $dbname = $modx->getOption('dbname'); 22 | 23 | $c = $modx->prepare('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = :dbName AND table_name = :tableName'); 24 | $c->bindParam(':dbName', $dbname); 25 | $c->bindParam(':tableName', $tableName); 26 | $c->execute(); 27 | 28 | $unusedColumns = $c->fetchAll(PDO::FETCH_COLUMN, 0); 29 | $unusedColumns = array_flip($unusedColumns); 30 | 31 | $meta = $modx->getFieldMeta($table); 32 | $columns = array_keys($meta); 33 | 34 | $m = $modx->getManager(); 35 | 36 | foreach ($columns as $column) { 37 | if (isset($unusedColumns[$column])) { 38 | $m->alterField($table, $column); 39 | $modx->log(xPDO::LOG_LEVEL_INFO, ' -- altered column: ' . $column); 40 | unset($unusedColumns[$column]); 41 | } else { 42 | $m->addField($table, $column); 43 | $modx->log(xPDO::LOG_LEVEL_INFO, ' -- added column: ' . $column); 44 | } 45 | } 46 | 47 | foreach ($unusedColumns as $column => $v) { 48 | $m->removeField($table, $column); 49 | $modx->log(xPDO::LOG_LEVEL_INFO, ' -- removed column: ' . $column); 50 | } 51 | } 52 | } 53 | 54 | if (!function_exists('updateTableIndexes')) { 55 | /** 56 | * @param modX $modx 57 | * @param string $table 58 | */ 59 | function updateTableIndexes($modx, $table) 60 | { 61 | $tableName = $modx->getTableName($table); 62 | $tableName = str_replace('`', '', $tableName); 63 | $dbname = $modx->getOption('dbname'); 64 | 65 | $c = $modx->prepare("SELECT DISTINCT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = :dbName AND table_name = :tableName AND INDEX_NAME != 'PRIMARY'"); 66 | $c->bindParam(':dbName', $dbname); 67 | $c->bindParam(':tableName', $tableName); 68 | $c->execute(); 69 | 70 | $oldIndexes = $c->fetchAll(PDO::FETCH_COLUMN, 0); 71 | 72 | $m = $modx->getManager(); 73 | 74 | foreach ($oldIndexes as $oldIndex) { 75 | $m->removeIndex($table, $oldIndex); 76 | $modx->log(xPDO::LOG_LEVEL_INFO, ' -- removed index: ' . $oldIndex); 77 | } 78 | 79 | $meta = $modx->getIndexMeta($table); 80 | $indexes = array_keys($meta); 81 | 82 | foreach ($indexes as $index) { 83 | if ($index == 'PRIMARY') continue; 84 | $m->addIndex($table, $index); 85 | $modx->log(xPDO::LOG_LEVEL_INFO, ' -- added index: ' . $index); 86 | } 87 | } 88 | } 89 | 90 | if (!function_exists('alterTable')) { 91 | /** 92 | * @param modX $modx 93 | * @param string $table 94 | */ 95 | function alterTable($modx, $table) 96 | { 97 | $modx->log(xPDO::LOG_LEVEL_INFO, ' - Updating columns'); 98 | updateTableColumns($modx, $table); 99 | 100 | $modx->log(xPDO::LOG_LEVEL_INFO, ' - Updating indexes'); 101 | updateTableIndexes($modx, $table); 102 | } 103 | } 104 | 105 | if ($object->xpdo) { 106 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 107 | case xPDOTransport::ACTION_INSTALL: 108 | case xPDOTransport::ACTION_UPGRADE: 109 | /** @var modX $modx */ 110 | $modx =& $object->xpdo; 111 | 112 | $tables = [ 113 | 'CustomrequestConfigs' 114 | ]; 115 | 116 | $modelPath = $modx->getOption('customrequest.core_path', null, $modx->getOption('core_path') . 'components/customrequest/') . 'model/'; 117 | $modx->addPackage('customrequest', $modelPath); 118 | 119 | foreach ($tables as $table) { 120 | $modx->log(xPDO::LOG_LEVEL_INFO, 'Altering table: ' . $table); 121 | alterTable($modx, $table); 122 | } 123 | 124 | break; 125 | } 126 | } 127 | return true; 128 | -------------------------------------------------------------------------------- /_build/resolvers/resolve.treehillstudio.php: -------------------------------------------------------------------------------- 1 | xpdo; 18 | $c = $modx->newQuery('transport.modTransportPackage'); 19 | $c->where( 20 | [ 21 | 'workspace' => 1, 22 | "(SELECT 23 | `signature` 24 | FROM {$modx->getTableName('transport.modTransportPackage')} AS `latestPackage` 25 | WHERE `latestPackage`.`package_name` = `modTransportPackage`.`package_name` 26 | ORDER BY 27 | `latestPackage`.`version_major` DESC, 28 | `latestPackage`.`version_minor` DESC, 29 | `latestPackage`.`version_patch` DESC, 30 | IF(`release` = '' OR `release` = 'ga' OR `release` = 'pl','z',`release`) DESC, 31 | `latestPackage`.`release_index` DESC 32 | LIMIT 1,1) = `modTransportPackage`.`signature`", 33 | ] 34 | ); 35 | $c->where( 36 | [ 37 | 'modTransportPackage.signature:LIKE' => $options['namespace'] . '-%', 38 | 'modTransportPackage.installed:IS NOT' => null 39 | ] 40 | ); 41 | $c->limit(1); 42 | 43 | /** @var modTransportPackage $oldPackage */ 44 | $oldPackage = $modx->getObject('transport.modTransportPackage', $c); 45 | 46 | $oldVersion = ''; 47 | if ($oldPackage) { 48 | $oldVersion = $oldPackage->get('version_major') . 49 | '.' . $oldPackage->get('version_minor') . 50 | '.' . $oldPackage->get('version_patch') . 51 | '-' . $oldPackage->get('release'); 52 | } 53 | 54 | $version = ''; 55 | if ($transport->version) { 56 | $version = $transport->version; 57 | } 58 | 59 | $modxVersionObj = $modx->getObject('modSystemSetting', ['key' => 'settings_version']); 60 | $modxVersion = ($modxVersionObj) ? $modxVersionObj->get('value') : ''; 61 | $managerLang = $modx->getOption('manager_language'); 62 | 63 | $action = ''; 64 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 65 | case xPDOTransport::ACTION_INSTALL: 66 | $action = 'install'; 67 | break; 68 | case xPDOTransport::ACTION_UPGRADE: 69 | $action = 'upgrade'; 70 | break; 71 | case xPDOTransport::ACTION_UNINSTALL: 72 | $action = 'uninstall'; 73 | break; 74 | } 75 | 76 | $params = [ 77 | 'name' => $options['namespace'], 78 | 'uuid' => $modx->uuid ?? 'unknown', 79 | 'host' => $modx->getOption('http_host', null, 'unknown'), 80 | 'php_version' => phpversion(), 81 | 'modx_version' => $modxVersion, 82 | 'manager_lang' => $managerLang, 83 | 'installation_type' => $action, 84 | 'package_version_from' => $oldVersion, 85 | 'package_version' => $version, 86 | 'date' => time() 87 | ]; 88 | 89 | /** 90 | * Curl POST. 91 | */ 92 | $curl = curl_init(); 93 | if ($curl) { 94 | curl_setopt($curl, CURLOPT_URL, $url); 95 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 96 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5); 97 | curl_setopt($curl, CURLOPT_POST, true); 98 | curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($params)); 99 | curl_setopt($curl, CURLOPT_TIMEOUT, 5); 100 | 101 | $response = curl_exec($curl); 102 | $responseInfo = curl_getinfo($curl); 103 | $curlError = curl_error($curl); 104 | curl_close($curl); 105 | } 106 | 107 | return true; 108 | -------------------------------------------------------------------------------- /_build/setup.options.php: -------------------------------------------------------------------------------- 1 | 12 | #modx-setupoptions-panel { display: none; } 13 | #modx-setupoptions-form p { margin-bottom: 10px; } 14 | #modx-setupoptions-form h2 { margin-bottom: 15px; } 15 | '; 16 | 17 | $values = []; 18 | switch ($options[xPDOTransport::PACKAGE_ACTION]) { 19 | case xPDOTransport::ACTION_INSTALL: 20 | $output .= '

Install CustomRequest

21 | 22 |

Thanks for installing CustomRequest. This open source extra was 23 | developed by Treehill Studio - MODX development in Münsterland.

24 | 25 |

During the installation, we will collect some statistical data (the 26 | hostname, the MODX UUID, the PHP version and the MODX version of your 27 | MODX installation). Your data will be kept confidential and under no 28 | circumstances be used for promotional purposes or disclosed to third 29 | parties. We only like to know the usage count of this package.

30 | 31 |

If you install this package, you are giving us your permission to 32 | collect, process and use that data for statistical purposes.

'; 33 | 34 | break; 35 | case xPDOTransport::ACTION_UPGRADE: 36 | $output .= '

Upgrade CustomRequest

37 | 38 |

CustomRequest will be upgraded. This open source extra was developed by 39 | Treehill Studio - MODX development in Münsterland.

40 | 41 |

During the installation, we will collect some statistical data (the 42 | hostname, the MODX UUID, the PHP version and the MODX version of your 43 | MODX installation). Your data will be kept confidential and under no 44 | circumstances be used for promotional purposes or disclosed to third 45 | parties. We only like to know the usage count of this package.

46 | 47 |

If you upgrade this package, you are giving us your permission to 48 | collect, process and use that data for statistical purposes.

'; 49 | 50 | break; 51 | case xPDOTransport::ACTION_UNINSTALL: 52 | break; 53 | } 54 | 55 | return $output; 56 | -------------------------------------------------------------------------------- /_packages/customrequest-1.0.3-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.0.3-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.1.0-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.1.0-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.1.1-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.1.1-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.1.2-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.1.2-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.1.3-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.1.3-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.1.4-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.1.4-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.0-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.0-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.1-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.1-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.2-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.2-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.3-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.3-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.4-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.4-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.5-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.5-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.6-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.6-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.2.7-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.2.7-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.0-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.0-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.1-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.1-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.10-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.10-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.2-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.2-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.3-pl2.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.3-pl2.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.4-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.4-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.5-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.5-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.6-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.6-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.7-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.7-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.8-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.8-pl.transport.zip -------------------------------------------------------------------------------- /_packages/customrequest-1.3.9-pl.transport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/_packages/customrequest-1.3.9-pl.transport.zip -------------------------------------------------------------------------------- /assets/components/customrequest/connector.php: -------------------------------------------------------------------------------- 1 | getOption('customrequest.core_path', null, $modx->getOption('core_path') . 'components/customrequest/'); 16 | /** @var CustomRequest $customrequest */ 17 | $customrequest = $modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/', [ 18 | 'core_path' => $corePath 19 | ]); 20 | 21 | // Handle request 22 | $modx->request->handleRequest([ 23 | 'processors_path' => $customrequest->getOption('processorsPath'), 24 | 'location' => '' 25 | ]); 26 | -------------------------------------------------------------------------------- /assets/components/customrequest/css/mgr/customrequest.min.css: -------------------------------------------------------------------------------- 1 | .action-buttons{text-align:center}.action-buttons li{display:inline-block;font-size:1.1em;line-height:.7;margin-right:5px}.action-buttons li:last-child{margin-right:0}.modx-grid .x-grid-cell-icons .x-grid3-cell-inner{padding-right:5px}.treehillstudio_about{cursor:pointer;height:40px;margin-top:20px;opacity:.25;transition:opacity .5s}.treehillstudio_about:hover{opacity:1}.treehillstudio_about .x-panel-body{text-align:right}.treehillstudio_window a{color:#b2bf28;text-decoration:none}.treehillstudio_window a:hover{color:#77801a}.home-panel.debug{background:linear-gradient(to top right,transparent,transparent,transparent,#fff)}.ribbon{height:180px;overflow:hidden;pointer-events:none;position:absolute;width:180px;z-index:15}.ribbon:after,.ribbon:before{border:5px solid #a20000;content:"";display:block;position:absolute;z-index:-1}.ribbon.top-right{right:-10px;top:-10px}.ribbon.top-right:after,.ribbon.top-right:before{border-right-color:transparent;border-top-color:transparent}.ribbon.top-right:before{left:0;top:0}.ribbon.top-right:after{bottom:0;right:0}.ribbon.top-right span{left:-46px;top:46px;transform:rotate(45deg)}.ribbon span{background-color:#be0000;box-shadow:0 5px 10px rgba(0,0,0,.1);color:#fff;display:block;font:700 18px/1 Lato,sans-serif;padding:15px 0;position:absolute;text-align:center;text-shadow:0 1px 1px rgba(0,0,0,.2);text-transform:uppercase;width:300px}@media screen and (min-width:961px){#modx-content>.x-panel-bwrap>.x-panel-body>.x-panel.modx3.debug{margin-right:15px}#modx-content>.x-panel-bwrap>.x-panel-body>.x-panel.modx3.debug .modx-page-header{padding-right:0}#modx-content>.x-panel-bwrap>.x-panel-body>.x-panel.modx3.debug .modx-page-header~.x-panel{margin-right:0}#modx-content>.x-panel-bwrap>.x-panel-body>.x-panel.modx3 .treehillstudio_about{margin:0 15px}} 2 | /*! 3 | * CustomRequest - Use friendly URLs everywhere 4 | * Version: 1.3.10 5 | * Build date: 2024-06-11 6 | */ -------------------------------------------------------------------------------- /assets/components/customrequest/img/mgr/treehill-studio-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/assets/components/customrequest/img/mgr/treehill-studio-small.png -------------------------------------------------------------------------------- /assets/components/customrequest/img/mgr/treehill-studio-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/assets/components/customrequest/img/mgr/treehill-studio-small@2x.png -------------------------------------------------------------------------------- /assets/components/customrequest/img/mgr/treehill-studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/assets/components/customrequest/img/mgr/treehill-studio.png -------------------------------------------------------------------------------- /assets/components/customrequest/img/mgr/treehill-studio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/assets/components/customrequest/img/mgr/treehill-studio@2x.png -------------------------------------------------------------------------------- /assets/components/customrequest/js/mgr/customrequest.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * CustomRequest - Use friendly URLs everywhere 3 | * Version: 1.3.10 4 | * Build date: 2024-06-11 5 | */ 6 | var customRequest=function(e){customRequest.superclass.constructor.call(this,e=e||{})};Ext.extend(customRequest,Ext.Component,{initComponent:function(){this.stores={},this.ajax=new Ext.data.Connection({disableCaching:!0})},page:{},window:{},grid:{},tree:{},panel:{},combo:{},config:{},util:{},form:{}}),Ext.reg("customrequest",customRequest),CustomRequest=new customRequest,MODx.config.help_url="https://jako.github.io/CustomRequest/usage/",CustomRequest.combo.Resource=function(e){e=e||{},Ext.applyIf(e,{url:CustomRequest.config.connectorUrl,baseParams:{action:"mgr/resources/getlist",combo:!0},pageSize:10,fields:["id","pagetitle"],displayField:"pagetitle",valueField:"id",lazyRender:!0,editable:!0,typeAhead:!0,minChars:1,forceSelection:!0,triggerAction:"all"}),CustomRequest.combo.Resource.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.combo.Resource,MODx.combo.ComboBox),Ext.reg("customrequest-combo-resource",CustomRequest.combo.Resource),CustomRequest.util.htmlRenderer=function(e){return MODx.util.safeHtml(e,"")},CustomRequest.page.Home=function(e){e=e||{},Ext.applyIf(e,{buttons:[{text:_("help_ex"),handler:MODx.loadHelpPane}],formpanel:"customrequest-panel-home",components:[{xtype:"customrequest-panel-home"}]}),CustomRequest.page.Home.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.page.Home,MODx.Component),Ext.reg("customrequest-page-home",CustomRequest.page.Home),CustomRequest.grid.Configs=function(e){e=e||{},this.ident="customrequest-configs-"+Ext.id(),this.buttonColumnTpl=new Ext.XTemplate('
',{compiled:!0}),Ext.applyIf(e,{id:this.ident,url:CustomRequest.config.connectorUrl,baseParams:{action:"mgr/configs/getlist"},fields:["id","name","menuindex","alias","alias_gen","pagetitle","resourceid","context","urlparams","regex"],autoHeight:!0,paging:!0,remoteSort:!1,enableDragDrop:!0,ddGroup:"customrequest-grid-dd",autoExpandColumn:"alias",showActionsColumn:!1,columns:[{header:_("customrequest.configs_name"),dataIndex:"name",width:100},{header:_("customrequest.configs_alias"),dataIndex:"alias_gen",renderer:CustomRequest.util.htmlRenderer,width:150},{header:_("customrequest.configs_resourceid"),dataIndex:"pagetitle",width:80},{header:_("customrequest.configs_context"),dataIndex:"context",width:80},{header:_("id"),dataIndex:"id",hidden:!0,width:20},{renderer:{fn:this.buttonColumnRenderer,scope:this},menuDisabled:!0,width:40}],tbar:[{text:_("customrequest.configs_create"),cls:"primary-button",handler:this.createConfig},"->",{xtype:"textfield",id:this.ident+"-config-filter-search",emptyText:_("search")+"…",submitValue:!1,listeners:{change:{fn:this.search,scope:this},render:{fn:function(e){new Ext.KeyMap(e.getEl(),{key:Ext.EventObject.ENTER,fn:function(){return this.fireEvent("change",this),this.blur(),!0},scope:e})},scope:this}}},{xtype:"button",id:this.ident+"-config-filter-clear",cls:"x-form-filter-clear",text:_("filter_clear"),listeners:{click:{fn:this.clearFilter,scope:this}}}],listeners:{render:{fn:this.renderListener,scope:this}}}),CustomRequest.grid.Configs.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.grid.Configs,MODx.grid.Grid,{windows:{},getMenu:function(){var e=[];e.push({text:_("customrequest.configs_update"),handler:this.updateConfig}),e.push("-"),e.push({text:_("customrequest.configs_duplicate"),handler:this.duplicateConfig}),e.push("-"),e.push({text:_("customrequest.configs_remove"),handler:this.removeConfig}),this.addContextMenuItem(e)},createConfig:function(e,t){this.createUpdateConfig(e,t,!1)},updateConfig:function(e,t){this.createUpdateConfig(e,t,!0)},createUpdateConfig:function(e,t,s){var o;if(s){if(!this.menu.record||!this.menu.record.id)return!1;o=this.menu.record}else o={};s=MODx.load({xtype:"customrequest-window-config-create-update",isUpdate:s,title:s?_("customrequest.configs_update"):_("customrequest.configs_create"),record:o,listeners:{success:{fn:this.refresh,scope:this}}});s.fp.getForm().setValues(o),s.show(t.target)},duplicateConfig:function(e,t){if(!this.menu.record)return!1;var s=Ext.apply({},this.menu.record),o=(s.name=_("customrequest.duplicate")+" "+s.name,s.original_id=s.id,s.id=null,s.repeat_on=s.repeat_on||[],MODx.load({xtype:"customrequest-window-config-create-update",isUpdate:!1,title:_("customrequest.configs_duplicate"),record:s,listeners:{success:{fn:this.refresh,scope:this}}}));o.fp.getForm().setValues(s),o.show(t.target)},removeConfig:function(){if(!this.menu.record)return!1;MODx.msg.confirm({title:_("customrequest.configs_remove"),text:_("customrequest.configs_remove_confirm"),url:this.config.url,params:{action:"mgr/configs/remove",id:this.menu.record.id},listeners:{success:{fn:this.refresh,scope:this}}})},search:function(e){this.getStore().baseParams.query=e.getValue(),this.getBottomToolbar().changePage(1),this.refresh()},clearFilter:function(){this.getStore().baseParams.query="",Ext.getCmp(this.ident+"-config-filter-search").reset(),this.getBottomToolbar().changePage(1),this.refresh()},renderListener:function(a){new Ext.dd.DropTarget(a.container,{ddGroup:"customrequest-grid-dd",notifyDrop:function(e,t,s){var o=a.store,i=a.getSelectionModel(),r=i.getSelections(),e=e.getDragData(t);if(e){t=e.rowIndex;if(void 0!==t){for(var e=o.getAt(t),n=[],u=0;u"+_("customrequest")+""+(CustomRequest.config.debug?'
'+_("customrequest.debug_mode")+"
":""),border:!1,cls:"modx-page-header"},{defaults:{autoHeight:!0},border:!0,cls:"customrequest-panel",items:[{xtype:"customrequest-panel-overview"}]},{cls:"treehillstudio_about",html:'Treehill Studio',listeners:{afterrender:function(){this.getEl().select("img").on("click",function(){var e='Treehill Studio
© 2013-2024 by treehillstudio.com
';Ext.Msg.show({title:_("customrequest")+" "+CustomRequest.config.version,msg:e,buttons:Ext.Msg.OK,cls:"treehillstudio_window",width:358})})}}}]}),CustomRequest.panel.Home.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.panel.Home,MODx.Panel),Ext.reg("customrequest-panel-home",CustomRequest.panel.Home),CustomRequest.panel.HomeTab=function(e){e=e||{},Ext.applyIf(e,{id:"customrequest-panel-"+e.tabtype,title:e.title,items:[{html:"

"+e.description+"

",border:!1,cls:"panel-desc"},{layout:"form",cls:"x-form-label-left main-wrapper",defaults:{autoHeight:!0},border:!0,items:[{id:"customrequest-panel-"+e.tabtype+"-grid",xtype:"customrequest-grid-"+e.tabtype,preventRender:!0}]}]}),CustomRequest.panel.HomeTab.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.panel.HomeTab,MODx.Panel),Ext.reg("customrequest-panel-hometab",CustomRequest.panel.HomeTab),CustomRequest.panel.Overview=function(e){e=e||{},this.ident="customrequest-panel-overview-"+Ext.id(),this.panelOverviewTabs=[{xtype:"customrequest-panel-hometab",title:_("customrequest.configs"),description:_("customrequest.configs_desc")+'
'+_("customrequest.configs_desc_extended")+"
",tabtype:"configs"}],CustomRequest.config.is_admin&&this.panelOverviewTabs.push({xtype:"customrequest-panel-settings"}),Ext.applyIf(e,{id:this.ident,items:[{xtype:"modx-tabs",border:!0,stateful:!0,stateId:"customrequest-panel-overview",stateEvents:["tabchange"],getState:function(){return{activeTab:this.items.indexOf(this.getActiveTab())}},autoScroll:!0,deferredRender:!0,forceLayout:!1,defaults:{layout:"form",autoHeight:!0,hideMode:"offsets"},items:this.panelOverviewTabs,listeners:{tabchange:function(e,t){"customrequest-panel-settings"===t.xtype?Ext.getCmp("customrequest-grid-system-settings").getStore().reload():"customrequest-panel-hometab"===t.xtype&&Ext.getCmp("customrequest-panel-"+t.tabtype+"-grid")&&Ext.getCmp("customrequest-panel-"+t.tabtype+"-grid").getStore().reload()}}}]}),CustomRequest.panel.Overview.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.panel.Overview,MODx.Panel),Ext.reg("customrequest-panel-overview",CustomRequest.panel.Overview),MODx.grid.SettingsGrid&&(CustomRequest.panel.Settings=function(e){e=e||{},MODx.request.ns="customrequest",Ext.applyIf(e,{id:"customrequest-panel-settings",title:_("customrequest.settings"),items:[{html:"

"+_("customrequest.settings_desc")+"

",border:!1,cls:"panel-desc"},{xtype:"customrequest-grid-system-settings",id:"customrequest-grid-system-settings",cls:"main-wrapper",preventSaveRefresh:!0}]}),CustomRequest.panel.Settings.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.panel.Settings,MODx.Panel),Ext.reg("customrequest-panel-settings",CustomRequest.panel.Settings),CustomRequest.grid.SystemSettings=function(e){e=e||{},Ext.applyIf(e,{id:"customrequest-grid-systemsettings",url:CustomRequest.config.connectorUrl,baseParams:{action:"mgr/settings/getlist",area:MODx.request.area||""},save_action:"mgr/settings/updatefromgrid",tbar:[],queryParam:3<=CustomRequest.config.modxversion?"query":"key"}),CustomRequest.grid.SystemSettings.superclass.constructor.call(this,e)},Ext.extend(CustomRequest.grid.SystemSettings,MODx.grid.SettingsGrid,{_showMenu:function(e,t,s){s.stopEvent(),s.preventDefault(),this.menu.record=this.getStore().getAt(t).data,this.getSelectionModel().isSelected(t)||this.getSelectionModel().selectRow(t),this.menu.removeAll();t=[];this.menu.record.menu?t=this.menu.record.menu:t.push({text:_("setting_update")||_("edit"),handler:this.updateSetting}),0=7.2.5", 19 | "ext-json": "*" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "TreehillStudio\\CustomRequest\\": "src/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/components/customrequest/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "8091a07e94a54872ee605f99c6fb3e8a", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=7.2.5" 17 | }, 18 | "platform-dev": [], 19 | "platform-overrides": { 20 | "php": "7.2.5" 21 | }, 22 | "plugin-api-version": "2.2.0" 23 | } 24 | -------------------------------------------------------------------------------- /core/components/customrequest/controllers/home.class.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 23 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $path . 'model/customrequest/', [ 24 | 'core_path' => $path 25 | ]); 26 | 27 | parent::initialize(); 28 | } 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | public function loadCustomCssJs() 34 | { 35 | $assetsUrl = $this->customrequest->getOption('assetsUrl'); 36 | $jsUrl = $this->customrequest->getOption('jsUrl') . 'mgr/'; 37 | $jsSourceUrl = $assetsUrl . '../../../source/js/mgr/'; 38 | $cssUrl = $this->customrequest->getOption('cssUrl') . 'mgr/'; 39 | $cssSourceUrl = $assetsUrl . '../../../source/css/mgr/'; 40 | 41 | if ($this->customrequest->getOption('debug') && ($assetsUrl != MODX_ASSETS_URL . 'components/customrequest/')) { 42 | $this->addCss($cssSourceUrl . 'customrequest.css?v=v' . $this->customrequest->version); 43 | $this->addJavascript($jsSourceUrl . 'customrequest.js?v=v' . $this->customrequest->version); 44 | $this->addJavascript($jsSourceUrl . 'helper/combo.js?v=v' . $this->customrequest->version); 45 | $this->addJavascript($jsSourceUrl . 'helper/util.js?v=v' . $this->customrequest->version); 46 | $this->addJavascript($jsSourceUrl . 'widgets/home.panel.js?v=v' . $this->customrequest->version); 47 | $this->addJavascript($jsSourceUrl . 'widgets/configs.grid.js?v=v' . $this->customrequest->version); 48 | $this->addJavascript(MODX_MANAGER_URL . 'assets/modext/widgets/core/modx.grid.settings.js'); 49 | $this->addJavascript($jsSourceUrl . 'widgets/settings.panel.js?v=v' . $this->customrequest->version); 50 | $this->addLastJavascript($jsSourceUrl . 'sections/home.js?v=v' . $this->customrequest->version); 51 | } else { 52 | $this->addCss($cssUrl . 'customrequest.min.css?v=v' . $this->customrequest->version); 53 | $this->addJavascript(MODX_MANAGER_URL . 'assets/modext/widgets/core/modx.grid.settings.js'); 54 | $this->addLastJavascript($jsUrl . 'customrequest.min.js?v=v' . $this->customrequest->version); 55 | } 56 | $this->addHtml(''); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | * @return string[] 67 | */ 68 | public function getLanguageTopics() 69 | { 70 | return ['core:setting', 'customrequest:default']; 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | * @param array $scriptProperties 76 | */ 77 | public function process(array $scriptProperties = []) 78 | { 79 | } 80 | 81 | /** 82 | * {@inheritDoc} 83 | * @return string|null 84 | */ 85 | public function getPageTitle() 86 | { 87 | return $this->modx->lexicon('customrequest'); 88 | } 89 | 90 | /** 91 | * {@inheritDoc} 92 | * @return string 93 | */ 94 | public function getTemplateFile() 95 | { 96 | return $this->customrequest->getOption('templatesPath') . 'home.tpl'; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /core/components/customrequest/docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.3.10] - 2024-06-11 9 | 10 | ### Fixed 11 | 12 | - Fix warning: Undefined array key "q" 13 | 14 | ## [1.3.9] - 2023-10-13 15 | 16 | ### Fixed 17 | 18 | - Use the right setlocale property to get the current locale 19 | 20 | ## [1.3.8] - 2022-12-07 21 | 22 | ### Added 23 | 24 | - Use the alias from the resource uri field and fallback to makeUrl 25 | 26 | ### Fixed 27 | 28 | - Remove superfluous beforeSubmit code 29 | 30 | ## [1.3.7] - 2022-12-06 31 | 32 | ### Added 33 | 34 | - Duplicate configuration by button or context menu 35 | 36 | ## [1.3.6] - 2022-11-30 37 | 38 | ### Fixed 39 | 40 | - Fix search in the resource combo of a configuration 41 | 42 | ## [1.3.5] - 2022-01-11 43 | 44 | ### Added 45 | 46 | - Help button 47 | 48 | ## [1.3.4] - 2022-01-10 49 | 50 | ### Changed 51 | 52 | - Code refactoring 53 | - Full MODX 3 compatibility 54 | 55 | ## [1.3.3] - 2021-01-19 56 | 57 | ### Changed 58 | 59 | - Fix a not used configuration cache 60 | 61 | ## [1.3.2] - 2019-03-20 62 | 63 | ### Added 64 | 65 | - Edit CustomRequest system settings in the custom manager page 66 | 67 | ### Changed 68 | 69 | - Use $_REQUEST variable instead of $_SERVER variable to avoid subfolder installation issues 70 | - Retrieve $requestUri only during OnPageNotFound 71 | 72 | ## [1.3.1] - 2018-11-13 73 | 74 | ### Added 75 | 76 | - Log found configuration and the set request parameters 77 | 78 | ### Changed 79 | 80 | - Cache valid configurations, even if one configuration is invalid 81 | - Change the log level of debug messages 82 | 83 | ## [1.3.0] - 2018-07-06 84 | 85 | ### Added 86 | 87 | - Equal aliases in different contexts are now possible 88 | - Switch the context to the requested resource context 89 | - Change the default engine to InnoDB 90 | 91 | ### Changed 92 | 93 | - Calculate the alias on base of the resource context 94 | 95 | ## [1.2.7] - 2017-09-28 96 | 97 | ### Changed 98 | 99 | - Regard the order of the configs 100 | 101 | ## [1.2.6] - 2016-06-13 102 | 103 | ### Added 104 | 105 | - Automatic urldecode of the remaining URL parameter part 106 | 107 | ## [1.2.5] - 2016-05-19 108 | 109 | ### Changed 110 | 111 | - Bugfix: Save configurations issue 112 | 113 | ## [1.2.4] - 2016-02-04 114 | 115 | ### Changed 116 | 117 | - Fixing a cache issue with URLs in different contexts than the current 118 | 119 | ## [1.2.3] - 2016-01-27 120 | 121 | ### Changed 122 | 123 | - Fixing a configuration caching issue 124 | 125 | ## [1.2.2] - 2016-01-19 126 | 127 | ### Added 128 | 129 | - Fixing a GPM table prefix issue 130 | 131 | ## [1.2.1] - 2016-01-13 132 | 133 | ### Added 134 | 135 | - Enhanced validation rules for a configuration during create/update 136 | 137 | ## [1.2.0] - 2016-01-11 138 | 139 | ### Added 140 | 141 | - Configurations are cached 142 | - The alias of a configuration could contain a regular expression 143 | 144 | ## [1.1.4] - 2015-09-10 145 | 146 | ### Changed 147 | 148 | - Fixing the RegEx evaluation 149 | 150 | ## [1.1.3] - 2015-04-18 151 | 152 | ### Changed 153 | 154 | - Fixing the cultureKey replacement 155 | 156 | ## [1.1.2] - 2015-03-17 157 | 158 | ### Changed 159 | 160 | - Fixing the cultureKey replacement 161 | 162 | ## [1.1.1] - 2015-03-03 163 | 164 | ### Changed 165 | 166 | - Bugfixes 167 | 168 | ## [1.1.0] - 2015-03-03 169 | 170 | ### Added 171 | 172 | - Custom manager page that replaces the config files 173 | - Automatic Import for old config files 174 | 175 | ## [1.0.3] - 2015-01-29 176 | 177 | ### Added 178 | 179 | - Build by Git-Package-Management 180 | 181 | ### Changed 182 | 183 | - Bugfix: Existing url parameters issue 184 | 185 | ## [1.0.2] - 2014-05-27 186 | 187 | ### Added 188 | 189 | - Detect/Log missing/not valid config files 190 | 191 | ### Changed 192 | 193 | - Debug logging 194 | 195 | ## [1.0.1] - 2013-1-27 196 | 197 | ### Changed 198 | 199 | - Updated documentation and examples ('resourceId' instead of 'id') 200 | 201 | ## [1.0.0] - 2013-06-09 202 | 203 | ### Added 204 | 205 | - Initial release for MODX Revolution 206 | -------------------------------------------------------------------------------- /core/components/customrequest/docs/license.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | -------------------------- 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | License is intended to guarantee your freedom to share and change free 17 | software--to make sure the software is free for all its users. This 18 | General Public License applies to most of the Free Software 19 | Foundation's software and to any other program whose authors commit to 20 | using it. (Some other Free Software Foundation software is covered by 21 | the GNU Library General Public License instead.) You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if you 34 | distribute copies of the software, or if you modify it. 35 | 36 | For example, if you distribute copies of such a program, whether 37 | gratis or for a fee, you must give the recipients all the rights that 38 | you have. You must make sure that they, too, receive or can get the 39 | source code. And you must show them these terms so they know their 40 | rights. 41 | 42 | We protect your rights with two steps: (1) copyright the software, and 43 | (2) offer you this license which gives you legal permission to copy, 44 | distribute and/or modify the software. 45 | 46 | Also, for each author's protection and ours, we want to make certain 47 | that everyone understands that there is no warranty for this free 48 | software. If the software is modified by someone else and passed on, we 49 | want its recipients to know that what they have is not the original, so 50 | that any problems introduced by others will not reflect on the original 51 | authors' reputations. 52 | 53 | Finally, any free program is threatened constantly by software 54 | patents. We wish to avoid the danger that redistributors of a free 55 | program will individually obtain patent licenses, in effect making the 56 | program proprietary. To prevent this, we have made it clear that any 57 | patent must be licensed for everyone's free use or not licensed at all. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | 63 | GNU GENERAL PUBLIC LICENSE 64 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 65 | --------------------------------------------------------------- 66 | 67 | 0. This License applies to any program or other work which contains 68 | a notice placed by the copyright holder saying it may be distributed 69 | under the terms of this General Public License. The "Program", below, 70 | refers to any such program or work, and a "work based on the Program" 71 | means either the Program or any derivative work under copyright law: 72 | that is to say, a work containing the Program or a portion of it, 73 | either verbatim or with modifications and/or translated into another 74 | language. (Hereinafter, translation is included without limitation in 75 | the term "modification".) Each licensee is addressed as "you". 76 | 77 | Activities other than copying, distribution and modification are not 78 | covered by this License; they are outside its scope. The act of 79 | running the Program is not restricted, and the output from the Program 80 | is covered only if its contents constitute a work based on the 81 | Program (independent of having been made by running the Program). 82 | Whether that is true depends on what the Program does. 83 | 84 | 1. You may copy and distribute verbatim copies of the Program's 85 | source code as you receive it, in any medium, provided that you 86 | conspicuously and appropriately publish on each copy an appropriate 87 | copyright notice and disclaimer of warranty; keep intact all the 88 | notices that refer to this License and to the absence of any warranty; 89 | and give any other recipients of the Program a copy of this License 90 | along with the Program. 91 | 92 | You may charge a fee for the physical act of transferring a copy, and 93 | you may at your option offer warranty protection in exchange for a fee. 94 | 95 | 2. You may modify your copy or copies of the Program or any portion 96 | of it, thus forming a work based on the Program, and copy and 97 | distribute such modifications or work under the terms of Section 1 98 | above, provided that you also meet all of these conditions: 99 | 100 | a) You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | b) You must cause any work that you distribute or publish, that in 104 | whole or in part contains or is derived from the Program or any 105 | part thereof, to be licensed as a whole at no charge to all third 106 | parties under the terms of this License. 107 | 108 | c) If the modified program normally reads commands interactively 109 | when run, you must cause it, when started running for such 110 | interactive use in the most ordinary way, to print or display an 111 | announcement including an appropriate copyright notice and a 112 | notice that there is no warranty (or else, saying that you provide 113 | a warranty) and that users may redistribute the program under 114 | these conditions, and telling the user how to view a copy of this 115 | License. (Exception: if the Program itself is interactive but 116 | does not normally print such an announcement, your work based on 117 | the Program is not required to print an announcement.) 118 | 119 | These requirements apply to the modified work as a whole. If 120 | identifiable sections of that work are not derived from the Program, 121 | and can be reasonably considered independent and separate works in 122 | themselves, then this License, and its terms, do not apply to those 123 | sections when you distribute them as separate works. But when you 124 | distribute the same sections as part of a whole which is a work based 125 | on the Program, the distribution of the whole must be on the terms of 126 | this License, whose permissions for other licensees extend to the 127 | entire whole, and thus to each and every part regardless of who wrote it. 128 | 129 | Thus, it is not the intent of this section to claim rights or contest 130 | your rights to work written entirely by you; rather, the intent is to 131 | exercise the right to control the distribution of derivative or 132 | collective works based on the Program. 133 | 134 | In addition, mere aggregation of another work not based on the Program 135 | with the Program (or with a work based on the Program) on a volume of 136 | a storage or distribution medium does not bring the other work under 137 | the scope of this License. 138 | 139 | 3. You may copy and distribute the Program (or a work based on it, 140 | under Section 2) in object code or executable form under the terms of 141 | Sections 1 and 2 above provided that you also do one of the following: 142 | 143 | a) Accompany it with the complete corresponding machine-readable 144 | source code, which must be distributed under the terms of Sections 145 | 1 and 2 above on a medium customarily used for software interchange; or, 146 | 147 | b) Accompany it with a written offer, valid for at least three 148 | years, to give any third party, for a charge no more than your 149 | cost of physically performing source distribution, a complete 150 | machine-readable copy of the corresponding source code, to be 151 | distributed under the terms of Sections 1 and 2 above on a medium 152 | customarily used for software interchange; or, 153 | 154 | c) Accompany it with the information you received as to the offer 155 | to distribute corresponding source code. (This alternative is 156 | allowed only for noncommercial distribution and only if you 157 | received the program in object code or executable form with such 158 | an offer, in accord with Subsection b above.) 159 | 160 | The source code for a work means the preferred form of the work for 161 | making modifications to it. For an executable work, complete source 162 | code means all the source code for all modules it contains, plus any 163 | associated interface definition files, plus the scripts used to 164 | control compilation and installation of the executable. However, as a 165 | special exception, the source code distributed need not include 166 | anything that is normally distributed (in either source or binary 167 | form) with the major components (compiler, kernel, and so on) of the 168 | operating system on which the executable runs, unless that component 169 | itself accompanies the executable. 170 | 171 | If distribution of executable or object code is made by offering 172 | access to copy from a designated place, then offering equivalent 173 | access to copy the source code from the same place counts as 174 | distribution of the source code, even though third parties are not 175 | compelled to copy the source along with the object code. 176 | 177 | 4. You may not copy, modify, sublicense, or distribute the Program 178 | except as expressly provided under this License. Any attempt 179 | otherwise to copy, modify, sublicense or distribute the Program is 180 | void, and will automatically terminate your rights under this License. 181 | However, parties who have received copies, or rights, from you under 182 | this License will not have their licenses terminated so long as such 183 | parties remain in full compliance. 184 | 185 | 5. You are not required to accept this License, since you have not 186 | signed it. However, nothing else grants you permission to modify or 187 | distribute the Program or its derivative works. These actions are 188 | prohibited by law if you do not accept this License. Therefore, by 189 | modifying or distributing the Program (or any work based on the 190 | Program), you indicate your acceptance of this License to do so, and 191 | all its terms and conditions for copying, distributing or modifying 192 | the Program or works based on it. 193 | 194 | 6. Each time you redistribute the Program (or any work based on the 195 | Program), the recipient automatically receives a license from the 196 | original licensor to copy, distribute or modify the Program subject to 197 | these terms and conditions. You may not impose any further 198 | restrictions on the recipients' exercise of the rights granted herein. 199 | You are not responsible for enforcing compliance by third parties to 200 | this License. 201 | 202 | 7. If, as a consequence of a court judgment or allegation of patent 203 | infringement or for any other reason (not limited to patent issues), 204 | conditions are imposed on you (whether by court order, agreement or 205 | otherwise) that contradict the conditions of this License, they do not 206 | excuse you from the conditions of this License. If you cannot 207 | distribute so as to satisfy simultaneously your obligations under this 208 | License and any other pertinent obligations, then as a consequence you 209 | may not distribute the Program at all. For example, if a patent 210 | license would not permit royalty-free redistribution of the Program by 211 | all those who receive copies directly or indirectly through you, then 212 | the only way you could satisfy both it and this License would be to 213 | refrain entirely from distribution of the Program. 214 | 215 | If any portion of this section is held invalid or unenforceable under 216 | any particular circumstance, the balance of the section is intended to 217 | apply and the section as a whole is intended to apply in other 218 | circumstances. 219 | 220 | It is not the purpose of this section to induce you to infringe any 221 | patents or other property right claims or to contest validity of any 222 | such claims; this section has the sole purpose of protecting the 223 | integrity of the free software distribution system, which is 224 | implemented by public license practices. Many people have made 225 | generous contributions to the wide range of software distributed 226 | through that system in reliance on consistent application of that 227 | system; it is up to the author/donor to decide if he or she is willing 228 | to distribute software through any other system and a licensee cannot 229 | impose that choice. 230 | 231 | This section is intended to make thoroughly clear what is believed to 232 | be a consequence of the rest of this License. 233 | 234 | 8. If the distribution and/or use of the Program is restricted in 235 | certain countries either by patents or by copyrighted interfaces, the 236 | original copyright holder who places the Program under this License 237 | may add an explicit geographical distribution limitation excluding 238 | those countries, so that distribution is permitted only in or among 239 | countries not thus excluded. In such case, this License incorporates 240 | the limitation as if written in the body of this License. 241 | 242 | 9. The Free Software Foundation may publish revised and/or new versions 243 | of the General Public License from time to time. Such new versions will 244 | be similar in spirit to the present version, but may differ in detail to 245 | address new problems or concerns. 246 | 247 | Each version is given a distinguishing version number. If the Program 248 | specifies a version number of this License which applies to it and "any 249 | later version", you have the option of following the terms and conditions 250 | either of that version or of any later version published by the Free 251 | Software Foundation. If the Program does not specify a version number of 252 | this License, you may choose any version ever published by the Free Software 253 | Foundation. 254 | 255 | 10. If you wish to incorporate parts of the Program into other free 256 | programs whose distribution conditions are different, write to the author 257 | to ask for permission. For software which is copyrighted by the Free 258 | Software Foundation, write to the Free Software Foundation; we sometimes 259 | make exceptions for this. Our decision will be guided by the two goals 260 | of preserving the free status of all derivatives of our free software and 261 | of promoting the sharing and reuse of software generally. 262 | 263 | NO WARRANTY 264 | ----------- 265 | 266 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 267 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 268 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 269 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 270 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 271 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 272 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 273 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 274 | REPAIR OR CORRECTION. 275 | 276 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 277 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 278 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 279 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 280 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 281 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 282 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 283 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 284 | POSSIBILITY OF SUCH DAMAGES. 285 | 286 | --------------------------- 287 | END OF TERMS AND CONDITIONS 288 | -------------------------------------------------------------------------------- /core/components/customrequest/docs/readme.md: -------------------------------------------------------------------------------- 1 | # CustomRequest 2 | 3 | Use pretty URLs everywhere 4 | 5 | ## Features 6 | 7 | CustomRequest is an effective routing plugin for MODX Revolution to map pretty 8 | but not found URLs to a MODX resource and set the request parameters by 9 | separating the URI path at the URI separators or by a regular expression. 10 | 11 | As standard option the first characters of the not found URI will be compared 12 | with the alias value of each config entry. If found, this config entry is used. 13 | The alias path is stripped from the not found URI and the remaining string is 14 | used to set the request parameters. 15 | 16 | As a second option, the alias value of a config entry could contain a valid 17 | regular expression and the not found URI will be matched with that expression. 18 | If matched, this config entry is used. The first subpattern part of the 19 | expression is stripped from the found pattern and the remaining string is used 20 | to identify the resource where the user is forwarded to later. At least the 21 | found pattern is removed from the not found URI and the remaining string is 22 | used to set the request parameters. 23 | 24 | ## Installation 25 | 26 | MODX Package Management 27 | 28 | ## Documentation 29 | 30 | For more information please read the documentation on https://jako.github.io/CustomRequest/ 31 | 32 | ## GitHub Repository 33 | 34 | https://github.com/Jako/CustomRequest 35 | -------------------------------------------------------------------------------- /core/components/customrequest/elements/plugins/customrequest.plugin.php: -------------------------------------------------------------------------------- 1 | event->name; 13 | 14 | $corePath = $modx->getOption('customrequest.core_path', null, $modx->getOption('core_path') . 'components/customrequest/'); 15 | /** @var CustomRequest $customrequest */ 16 | $customrequest = $modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/', [ 17 | 'core_path' => $corePath 18 | ]); 19 | 20 | if ($customrequest) { 21 | if (class_exists($className)) { 22 | $handler = new $className($modx, $scriptProperties); 23 | if (get_class($handler) == $className) { 24 | $handler->run(); 25 | } else { 26 | $modx->log(xPDO::LOG_LEVEL_ERROR, $className. ' could not be initialized!', '', 'CustomRequest Plugin'); 27 | } 28 | } else { 29 | $modx->log(xPDO::LOG_LEVEL_ERROR, $className. ' was not found!', '', 'CustomRequest Plugin'); 30 | } 31 | } 32 | 33 | return; -------------------------------------------------------------------------------- /core/components/customrequest/lexicon/de/default.inc.php: -------------------------------------------------------------------------------- 1 | '; 35 | $_lang['customrequest.settings_desc'] = 'Bearbeiten Sie die Einstellungen von CustomRequest. Sie können den Wert einer Systemeinstellung mit einem Doppelklick auf die ‚Wert‘-Tabellenzelle oder die Systemeinstellung mit einem Rechtsklick in der Tabellenzelle bearbeiten.'; 36 | $_lang['customrequest.systemsetting_key_err_nv'] = 'Sie dürfen nur Einstellungen mit dem Prefix customrequest bearbeiten.'; 37 | $_lang['customrequest.systemsetting_usergroup_err_nv'] = 'Nur Benutzer mit einer settings Berechtigung oder einer settings_customrequest Berechtigung können die Einstellungen ändern.'; 38 | $_lang['customrequest.configs_duplicate'] = 'Konfiguration Duplizieren'; 39 | $_lang['customrequest.duplicate'] = 'Duplikat von'; 40 | $_lang['customrequest.refresh_cache'] = 'Leeren des [[+packagename]]-Caches'; 41 | -------------------------------------------------------------------------------- /core/components/customrequest/lexicon/de/setting.inc.php: -------------------------------------------------------------------------------- 1 | '; 38 | $_lang['customrequest.settings_desc'] = 'Edit the settings of CustomRequest. You can edit the value of a system setting by double-clicking on the ‘Value’ table cell or by right-clicking in the table cell.'; 39 | $_lang['customrequest.systemsetting_key_err_nv'] = 'You could only edit settings with the prefix customrequest.'; 40 | $_lang['customrequest.systemsetting_usergroup_err_nv'] = 'Only users with a settings permission or a settings_customrequest permission are allowed to change settings.'; 41 | -------------------------------------------------------------------------------- /core/components/customrequest/lexicon/en/setting.inc.php: -------------------------------------------------------------------------------- 1 | '; 35 | $_lang['customrequest.settings_desc'] = 'Редактировать настройки CustomRequest. Вы можете редактировать значение системной настройки, дважды щелкнув по ячейке таблицы «значений» или щелкнув правой кнопкой мыши по ячейке таблицы.'; 36 | $_lang['customrequest.systemsetting_key_err_nv'] = 'Вы можете редактировать настройки только с помощью обычного запроса префикса.'; 37 | $_lang['customrequest.systemsetting_usergroup_err_nv'] = 'Разрешается изменять настройки только пользователям с разрешением настроек или разрешением settings_customrequest.'; 38 | $_lang['customrequest.configs_duplicate'] = 'Дублирование конфигурации'; 39 | $_lang['customrequest.duplicate'] = 'Дубликат'; 40 | $_lang['customrequest.refresh_cache'] = 'Очистка кэша [[+packagename]]'; 41 | -------------------------------------------------------------------------------- /core/components/customrequest/lexicon/ru/setting.inc.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @package customrequest 8 | * @subpackage classfile 9 | */ 10 | 11 | require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; 12 | 13 | /** 14 | * class CustomRequest 15 | */ 16 | class CustomRequest extends \TreehillStudio\CustomRequest\CustomRequest 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /core/components/customrequest/model/customrequest/customrequestconfigs.class.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/components/customrequest/model/customrequest/metadata.mysql.php: -------------------------------------------------------------------------------- 1 | 5 | array ( 6 | 0 => 'CustomrequestConfigs', 7 | ), 8 | ); -------------------------------------------------------------------------------- /core/components/customrequest/model/customrequest/mysql/customrequestconfigs.class.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/components/customrequest/model/customrequest/mysql/customrequestconfigs.map.inc.php: -------------------------------------------------------------------------------- 1 | 'customrequest', 7 | 'version' => '1.1', 8 | 'table' => 'customrequest_configs', 9 | 'extends' => 'xPDOSimpleObject', 10 | 'tableMeta' => 11 | array ( 12 | 'engine' => 'InnoDB', 13 | ), 14 | 'fields' => 15 | array ( 16 | 'name' => NULL, 17 | 'menuindex' => 0, 18 | 'alias' => NULL, 19 | 'resourceid' => 0, 20 | 'urlparams' => NULL, 21 | 'regex' => NULL, 22 | ), 23 | 'fieldMeta' => 24 | array ( 25 | 'name' => 26 | array ( 27 | 'dbtype' => 'varchar', 28 | 'precision' => '100', 29 | 'phptype' => 'string', 30 | 'null' => true, 31 | ), 32 | 'menuindex' => 33 | array ( 34 | 'dbtype' => 'int', 35 | 'precision' => '10', 36 | 'phptype' => 'integer', 37 | 'null' => false, 38 | 'default' => 0, 39 | ), 40 | 'alias' => 41 | array ( 42 | 'dbtype' => 'varchar', 43 | 'precision' => '100', 44 | 'phptype' => 'string', 45 | 'null' => true, 46 | ), 47 | 'resourceid' => 48 | array ( 49 | 'dbtype' => 'int', 50 | 'precision' => '10', 51 | 'phptype' => 'integer', 52 | 'null' => false, 53 | 'default' => 0, 54 | ), 55 | 'urlparams' => 56 | array ( 57 | 'dbtype' => 'text', 58 | 'phptype' => 'string', 59 | 'null' => true, 60 | ), 61 | 'regex' => 62 | array ( 63 | 'dbtype' => 'text', 64 | 'phptype' => 'string', 65 | 'null' => true, 66 | ), 67 | ), 68 | ); 69 | -------------------------------------------------------------------------------- /core/components/customrequest/model/schema/customrequest.mysql.schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/configs/create.class.php: -------------------------------------------------------------------------------- 1 | getProperty('alias'); 25 | $regex = $this->getProperty('regex'); 26 | $resourceid = $this->getProperty('resourceid'); 27 | 28 | if (empty($alias) && empty($resourceid)) { 29 | $this->addFieldError('alias', $this->modx->lexicon('customrequest.configs_err_ns_alias_resourceid')); 30 | $this->addFieldError('resourceid', $this->modx->lexicon('customrequest.configs_err_ns_alias_resourceid')); 31 | } 32 | 33 | if (empty($resourceid) && !$this->customrequest->isRegularExpression($alias)) { 34 | $this->addFieldError('alias', $this->modx->lexicon('customrequest.configs_err_nv_alias_regex')); 35 | } 36 | 37 | if (!empty($regex) && !$this->customrequest->isRegularExpression($regex)) { 38 | $this->addFieldError('regex', $this->modx->lexicon('customrequest.configs_err_nv_regex')); 39 | } 40 | 41 | $count = $this->modx->getCount($this->classKey); 42 | $this->object->set('menuindex', $count); 43 | 44 | return parent::beforeSave(); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | * @return bool 50 | */ 51 | public function afterSave() 52 | { 53 | $this->customrequest->reset(); 54 | 55 | return parent::afterSave(); 56 | } 57 | } 58 | 59 | return 'CustomrequestConfigsCreateProcessor'; 60 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/configs/getlist.class.php: -------------------------------------------------------------------------------- 1 | toArray('', false, true); 28 | /** @var modResource $resource */ 29 | $resource = $this->modx->getObject('modResource', $ta['resourceid']); 30 | if ($resource) { 31 | $context = $this->modx->getContext($resource->get('context_key')); 32 | // If the alias could be retrieved by a resource id or if the alias is a valid regular rexpression 33 | $ta['pagetitle'] = $resource->get('pagetitle') . ' (' . $ta['resourceid'] . ')'; 34 | $ta['alias_gen'] = ($ta['alias']) ?: '' . $this->makeUrl($ta['resourceid'], $resource->get('context_key')) . ''; 35 | $ta['context'] = ($context->get('name')) ? $context->get('name') . ' (' . $resource->get('context_key') . ')' : $resource->get('context_key'); 36 | } else { 37 | $ta['resourceid'] = ''; 38 | if (!$this->customrequest->isRegularExpression($ta['alias'])) { 39 | $ta['alias_gen'] = '' . $ta['alias'] . ''; 40 | } 41 | } 42 | return $ta; 43 | } 44 | 45 | /** 46 | * @param integer $id 47 | * @param string $context 48 | * @return string 49 | */ 50 | private function makeUrl($id, $context) 51 | { 52 | $resource = $this->modx->getObject('modResource', $id); 53 | $tmpKey = $this->modx->context->key; 54 | $contextKey = $resource->get('context_key'); 55 | $this->modx->switchContext($contextKey); 56 | $url = $this->modx->makeUrl($id, $context); 57 | $this->modx->switchContext($tmpKey); 58 | return str_replace($this->modx->getOption('site_url'), '', $url); 59 | } 60 | } 61 | 62 | return 'CustomrequestConfigsGetListProcessor'; 63 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/configs/remove.class.php: -------------------------------------------------------------------------------- 1 | customrequest->reset(); 23 | 24 | return parent::afterRemove(); 25 | } 26 | } 27 | 28 | return 'CustomRequestConfigsRemoveProcessor'; 29 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/configs/sortindex.class.php: -------------------------------------------------------------------------------- 1 | customrequest->reset(); 23 | } 24 | } 25 | 26 | return 'CustomrequestConfigsSortindexProcessor'; 27 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/configs/update.class.php: -------------------------------------------------------------------------------- 1 | getProperty('alias'); 25 | $regex = $this->getProperty('regex'); 26 | $resourceid = $this->getProperty('resourceid'); 27 | 28 | if (empty($alias) && empty($resourceid)) { 29 | $this->addFieldError('alias', $this->modx->lexicon('customrequest.configs_err_ns_alias_resourceid')); 30 | $this->addFieldError('resourceid', $this->modx->lexicon('customrequest.configs_err_ns_alias_resourceid')); 31 | } 32 | 33 | if (empty($resourceid) && !$this->customrequest->isRegularExpression($alias)) { 34 | $this->addFieldError('alias', $this->modx->lexicon('customrequest.configs_err_nv_alias_regex')); 35 | } 36 | 37 | if (!empty($regex) && !$this->customrequest->isRegularExpression($regex)) { 38 | $this->addFieldError('regex', $this->modx->lexicon('customrequest.configs_err_nv_regex')); 39 | } 40 | 41 | return parent::beforeSave(); 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | * @return bool 47 | */ 48 | public function afterSave() 49 | { 50 | $this->customrequest->reset(); 51 | 52 | return parent::afterSave(); 53 | } 54 | } 55 | 56 | return 'CustomrequestConfigsUpdateProcessor'; 57 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/resources/getlist.class.php: -------------------------------------------------------------------------------- 1 | where([ 29 | 'deleted' => false, 30 | 'published' => true 31 | ]); 32 | return $c; 33 | } 34 | 35 | /** 36 | * (@inheritDoc} 37 | * @param xPDOObject $object 38 | * @return array 39 | */ 40 | public function prepareRow(xPDOObject $object) 41 | { 42 | $ta = $object->toArray('', false, true); 43 | $ta['pagetitle'] = $ta['pagetitle'] . ' (' . $ta['id'] . ')'; 44 | return $ta; 45 | } 46 | } 47 | 48 | return 'CustomrequestResourcesGetListProcessor'; 49 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/settings/getlist.class.php: -------------------------------------------------------------------------------- 1 | permission) ? $this->modx->hasPermission($this->permission) || $this->modx->hasPermission('customrequest_' . $this->permission) : true; 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | * @return array 37 | */ 38 | public function prepareCriteria() 39 | { 40 | return ['namespace' => 'customrequest']; 41 | } 42 | } 43 | 44 | return 'CustomRequestSystemSettingsGetlistProcessor'; 45 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/settings/update.class.php: -------------------------------------------------------------------------------- 1 | permission) ? $this->modx->hasPermission($this->permission) || $this->modx->hasPermission('customrequest_' . $this->permission) : true; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | * @return bool 36 | */ 37 | public function beforeSave() 38 | { 39 | $this->setProperty('namespace', 'customrequest'); 40 | $this->checkForBooleanValue(); 41 | $this->checkCanSave(); 42 | return parent::beforeSave(); 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | * @return bool 48 | */ 49 | public function afterSave() 50 | { 51 | $this->updateTranslations($this->getProperties()); 52 | $this->clearCache(); 53 | return parent::afterSave(); 54 | } 55 | 56 | /** 57 | * Verify the namespace passed is a valid namespace 58 | */ 59 | protected function checkCanSave() 60 | { 61 | $key = $this->getProperty('key'); 62 | if (strpos($key, 'customrequest.') !== 0) { 63 | $this->addFieldError('key', $this->modx->lexicon('customrequest.systemsetting_key_err_nv')); 64 | } 65 | if (!$this->modx->hasPermission($this->permission) && !$this->modx->hasPermission('customrequest_' . $this->permission)) { 66 | $this->addFieldError('usergroup', $this->modx->lexicon('customrequest.systemsetting_usergroup_err_nv')); 67 | } 68 | } 69 | } 70 | 71 | return 'CustomRequestSystemSettingsUpdateProcessor'; 72 | -------------------------------------------------------------------------------- /core/components/customrequest/processors/mgr/settings/updatefromgrid.class.php: -------------------------------------------------------------------------------- 1 | getProperty('data'); 19 | if (empty($data)) return $this->modx->lexicon('invalid_data'); 20 | $properties = json_decode($data, true); 21 | $this->setProperties($properties); 22 | $this->unsetProperty('data'); 23 | 24 | return parent::initialize(); 25 | } 26 | } 27 | 28 | return 'CustomRequestSystemSettingsUpdateFromGridProcessor'; 29 | -------------------------------------------------------------------------------- /core/components/customrequest/src/CustomRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @package customrequest 8 | * @subpackage classfile 9 | */ 10 | 11 | namespace TreehillStudio\CustomRequest; 12 | 13 | use CustomrequestConfigs; 14 | use modResource; 15 | use modX; 16 | use xPDO; 17 | 18 | /** 19 | * class CustomRequest 20 | */ 21 | class CustomRequest 22 | { 23 | /** 24 | * A reference to the modX instance 25 | * @var modX $modx 26 | */ 27 | public $modx; 28 | 29 | /** 30 | * The namespace 31 | * @var string $namespace 32 | */ 33 | public $namespace = 'customrequest'; 34 | 35 | /** 36 | * The package name 37 | * @var string $packageName 38 | */ 39 | public $packageName = 'CustomRequest'; 40 | 41 | /** 42 | * The version 43 | * @var string $version 44 | */ 45 | public $version = '1.3.10'; 46 | 47 | /** 48 | * The class options 49 | * @var array $options 50 | */ 51 | public $options = []; 52 | 53 | /** 54 | * The requests array 55 | * @var array $requests 56 | */ 57 | public $requests = []; 58 | 59 | /** 60 | * The found request 61 | * @var array $found 62 | */ 63 | public $found = []; 64 | 65 | /** 66 | * CustomRequest constructor 67 | * 68 | * @param modX $modx A reference to the modX instance. 69 | * @param array $options An array of options. Optional. 70 | */ 71 | public function __construct(modX &$modx, $options = []) 72 | { 73 | $this->modx =& $modx; 74 | $this->namespace = $this->getOption('namespace', $options, $this->namespace); 75 | 76 | $corePath = $this->getOption('core_path', $options, $this->modx->getOption('core_path', null, MODX_CORE_PATH) . 'components/' . $this->namespace . '/'); 77 | $assetsPath = $this->getOption('assets_path', $options, $this->modx->getOption('assets_path', null, MODX_ASSETS_PATH) . 'components/' . $this->namespace . '/'); 78 | $assetsUrl = $this->getOption('assets_url', $options, $this->modx->getOption('assets_url', null, MODX_ASSETS_URL) . 'components/' . $this->namespace . '/'); 79 | $modxversion = $this->modx->getVersionData(); 80 | 81 | // Load some default paths for easier management 82 | $this->options = array_merge([ 83 | 'namespace' => $this->namespace, 84 | 'version' => $this->version, 85 | 'corePath' => $corePath, 86 | 'modelPath' => $corePath . 'model/', 87 | 'vendorPath' => $corePath . 'vendor/', 88 | 'chunksPath' => $corePath . 'elements/chunks/', 89 | 'pagesPath' => $corePath . 'elements/pages/', 90 | 'snippetsPath' => $corePath . 'elements/snippets/', 91 | 'pluginsPath' => $corePath . 'elements/plugins/', 92 | 'controllersPath' => $corePath . 'controllers/', 93 | 'processorsPath' => $corePath . 'processors/', 94 | 'templatesPath' => $corePath . 'templates/', 95 | 'assetsPath' => $assetsPath, 96 | 'assetsUrl' => $assetsUrl, 97 | 'jsUrl' => $assetsUrl . 'js/', 98 | 'cssUrl' => $assetsUrl . 'css/', 99 | 'imagesUrl' => $assetsUrl . 'images/', 100 | 'connectorUrl' => $assetsUrl . 'connector.php', 101 | ], $options); 102 | 103 | $lexicon = $this->modx->getService('lexicon', 'modLexicon'); 104 | $lexicon->load($this->namespace . ':default'); 105 | 106 | $this->packageName = $this->modx->lexicon('customrequest'); 107 | 108 | $this->modx->addPackage($this->namespace, $this->getOption('modelPath')); 109 | 110 | // Add default options 111 | $this->options = array_merge($this->options, [ 112 | 'debug' => (bool)$this->getOption('debug', $options, false), 113 | 'modxversion' => $modxversion['version'], 114 | 'is_admin' => $this->modx->user && $this->modx->context && ($modx->hasPermission('settings') || $modx->hasPermission($this->namespace . '_settings')), 115 | 'configsPath' => $this->getOption('configsPath', null, $corePath . 'configs/'), 116 | 'cachePath' => $this->modx->getOption('core_path') . 'cache/', 117 | 'cacheKey' => 'requests', 118 | 'cacheOptions' => [ 119 | xPDO::OPT_CACHE_KEY => 'customrequest', 120 | xPDO::OPT_CACHE_HANDLER => $modx->getOption('cache_resource_handler', null, $this->modx->getOption(xPDO::OPT_CACHE_HANDLER, null, 'xPDOFileCache')), 121 | ], 122 | ]); 123 | 124 | if ($this->getOption('aliases')) { 125 | $this->requests = json_decode($this->getOption('aliases'), true); 126 | } 127 | if (!$this->requests) { 128 | $this->requests = []; 129 | } 130 | } 131 | 132 | /** 133 | * Get a local configuration option or a namespaced system setting by key. 134 | * 135 | * @param string $key The option key to search for. 136 | * @param array $options An array of options that override local options. 137 | * @param mixed $default The default value returned if the option is not found locally or as a 138 | * namespaced system setting; by default this value is null. 139 | * @return mixed The option value or the default value specified. 140 | */ 141 | public function getOption($key, $options = [], $default = null) 142 | { 143 | $option = $default; 144 | if (!empty($key) && is_string($key)) { 145 | if ($options != null && array_key_exists($key, $options)) { 146 | $option = $options[$key]; 147 | } elseif (array_key_exists($key, $this->options)) { 148 | $option = $this->options[$key]; 149 | } elseif (array_key_exists("$this->namespace.$key", $this->modx->config)) { 150 | $option = $this->modx->getOption("$this->namespace.$key"); 151 | } 152 | } 153 | return $option; 154 | } 155 | 156 | /** 157 | * Load all config files and prepare the values. 158 | * 159 | * @access public 160 | * @return void 161 | */ 162 | public function initialize() 163 | { 164 | $this->requests = $this->modx->cacheManager->get($this->getOption('cacheKey'), $this->getOption('cacheOptions')); 165 | 166 | if (empty($this->requests) || $this->getOption('debug')) { 167 | // Clear the requests in debug mode 168 | $this->requests = []; 169 | 170 | // Import config records 171 | $c = $this->modx->newQuery('CustomrequestConfigs'); 172 | $c->sortby('menuindex'); 173 | /** @var CustomrequestConfigs[] $configs */ 174 | $configs = $this->modx->getCollection('CustomrequestConfigs', $c); 175 | foreach ($configs as $config) { 176 | // Fill additional urlParams if defined 177 | $urlParams = json_decode($config->get('urlparams')) ? json_decode($config->get('urlparams')) : []; 178 | $resourceId = $config->get('resourceid'); 179 | $alias = $config->get('alias'); 180 | $aliasRegEx = false; 181 | $regEx = $config->get('regex'); 182 | $contextKey = ''; 183 | if ($alias) { 184 | // If alias is defined, calculate the other values 185 | if (!$resourceId) { 186 | $resourceId = 0; 187 | if (!$this->isRegularExpression($alias)) { 188 | $resourceId = $this->modx->findResource($alias); 189 | if (!$resourceId) { 190 | // If resourceId could not be calculated and alias is not a valid regular expression, don't use that setting 191 | if ($this->getOption('debug')) { 192 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'Could not calculate the resourceId for the given alias "' . $alias . '".', '', 'CustomRequest Plugin'); 193 | } 194 | break; 195 | } 196 | } else { 197 | $aliasRegEx = true; 198 | } 199 | } 200 | if ($resourceId) { 201 | $resource = $this->modx->getObject('modResource', $resourceId); 202 | if ($resource) { 203 | $contextKey = $resource->get('context_key'); 204 | } 205 | } 206 | } else { 207 | $resourceId = $config->get('resourceid'); 208 | if ($resourceId) { 209 | // If alias is not defined and resourceId is defined, calculate the other values 210 | if ($config->get('alias')) { 211 | $alias = $config->get('alias'); 212 | } else { 213 | /** @var modResource $resource */ 214 | $resource = $this->modx->getObject('modResource', $resourceId); 215 | if ($resource) { 216 | $contextKey = $resource->get('context_key'); 217 | $alias = $resource->get('uri'); 218 | // If the resource uri field is empty try to get it with make url 219 | if (!$alias) { 220 | $tmpKey = $this->modx->context->key; 221 | $this->modx->switchContext($contextKey); 222 | $alias = $this->modx->makeUrl($resourceId); 223 | $this->modx->switchContext($tmpKey); 224 | } 225 | if ($alias) { 226 | // Cutoff trailing .html or / 227 | $alias = trim(str_replace('.html', '', $alias), '/'); 228 | } else { 229 | // If alias could not be calculated, don't use that setting 230 | if ($this->getOption('debug')) { 231 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'Could not calculate the alias for the given resourceId "' . $resourceId . '".', '', 'CustomRequest Plugin'); 232 | } 233 | continue; 234 | } 235 | } else { 236 | // If alias could not be calculated, don't use that setting 237 | if ($this->getOption('debug')) { 238 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'No resource with ID "' . $resourceId . '" found.', '', 'CustomRequest Plugin'); 239 | } 240 | continue; 241 | } 242 | } 243 | } 244 | } 245 | $this->requests[$contextKey . ':' . $alias] = [ 246 | 'resourceId' => $resourceId, 247 | 'alias' => $alias, 248 | 'aliasRegEx' => $aliasRegEx, 249 | 'urlParams' => $urlParams, 250 | 'regEx' => $regEx, 251 | 'contextKey' => $contextKey 252 | ]; 253 | } 254 | $this->modx->cacheManager->set($this->getOption('cacheKey'), $this->requests, 0, $this->getOption('cacheOptions')); 255 | } 256 | } 257 | 258 | /** 259 | * Reset the customrequest cache partition 260 | */ 261 | public function reset() 262 | { 263 | $this->modx->cacheManager->delete($this->getOption('cacheKey'), $this->getOption('cacheOptions')); 264 | } 265 | 266 | /** 267 | * Check if the search string starts with one of the allowed aliases and 268 | * prepare the url param string if successful. 269 | * 270 | * @access public 271 | * @param string $search A string to search the allowed aliases in 272 | * @return boolean 273 | */ 274 | public function searchAliases($search) 275 | { 276 | // Strip cultureKey i.e. in Babel installations. 277 | if (0 === strpos($search, $this->modx->cultureKey . '/')) { 278 | $search = substr($search, strlen($this->modx->cultureKey) + 1); 279 | } 280 | 281 | $valid = false; 282 | // Loop through the allowed aliases 283 | if (is_array($this->requests) && count($this->requests)) { 284 | foreach ($this->requests as $request) { 285 | if (!$request['aliasRegEx']) { 286 | // Check if searched string starts with the alias and for the contextKey 287 | if ($request['alias'] && 0 === strpos($search, $request['alias']) && $request['contextKey'] == $this->modx->context->key) { 288 | $this->found = [ 289 | // Strip alias from seached string and urldecode it 290 | 'urlParams' => urldecode(substr($search, strlen($request['alias']))), 291 | // Set the found resource id 292 | 'resourceId' => $request['resourceId'], 293 | // Set the found alias 294 | 'alias' => $request['alias'], 295 | // Set the found regEx 296 | 'regEx' => $request['regEx'], 297 | // Set the found regEx 298 | 'contextKey' => $request['contextKey'] 299 | ]; 300 | $valid = true; 301 | break; 302 | } 303 | } else { 304 | if (preg_match($request['alias'], $search, $matches)) { 305 | $alias = trim(str_replace($matches[1], '', $matches[0]), '/'); 306 | $resourceId = $this->modx->findResource($alias . '/', $request['contextKey']); 307 | if ($resourceId) { 308 | $this->found = [ 309 | // Strip alias from seached string and urldecode it 310 | 'urlParams' => urldecode(substr($search, strlen($matches[0]))), 311 | // Set the found resource id 312 | 'resourceId' => $resourceId, 313 | // Set the found alias 314 | 'alias' => $request['alias'], 315 | // Set the found regEx 316 | 'regEx' => $request['regEx'], 317 | // Set the found regEx 318 | 'contextKey' => $request['contextKey'] 319 | ]; 320 | $valid = true; 321 | break; 322 | } 323 | } 324 | } 325 | } 326 | } else { 327 | if ($this->getOption('debug')) { 328 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'No valid configs found.', '', 'CustomRequest Plugin'); 329 | } 330 | } 331 | 332 | return $valid; 333 | } 334 | 335 | /** 336 | * Prepare the request parameters. 337 | * 338 | * @access public 339 | * @return void 340 | */ 341 | public function setRequest() 342 | { 343 | $params = str_replace('.html', '', $this->found['urlParams']); 344 | if ($this->found['regEx']) { 345 | if (!preg_match($this->found['regEx'], $params, $matches)) { 346 | // Return without redirecting 347 | return; 348 | } 349 | // $matches[0] contains the full match, we don't want that 350 | array_shift($matches); 351 | $params = $matches; 352 | } else { 353 | $params = explode('/', trim($params, '/')); 354 | } 355 | if (count($params) >= 1) { 356 | $setting = $this->requests[$this->found['contextKey'] . ':' . $this->found['alias']]; 357 | 358 | $foundParams = []; 359 | // Set the request parameters 360 | foreach ($params as $key => $value) { 361 | if (isset($setting['urlParams'][$key])) { 362 | $_REQUEST[$setting['urlParams'][$key]] = $value; 363 | $_GET[$setting['urlParams'][$key]] = $value; 364 | $foundParams[$setting['urlParams'][$key]] = $value; 365 | } else { 366 | $_REQUEST['p' . ($key + 1)] = $value; 367 | $_GET['p' . ($key + 1)] = $value; 368 | $foundParams['p' . ($key + 1)] = $value; 369 | } 370 | } 371 | if ($this->getOption('debug')) { 372 | $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'Used configuration:' . "\n" . print_r($setting, true) . "\n" . 'Set params:' . "\n" . print_r($foundParams, true), '', 'CustomRequest Plugin'); 373 | } 374 | } 375 | if ($resource = $this->modx->getObject('modResource', $this->found['resourceId'])) { 376 | if ($this->modx->context->key != $resource->get('context_key')) { 377 | $this->modx->switchContext($resource->get('context_key')); 378 | 379 | // Set locale after context switch since $this->modx->_initCulture is called before OnPageNotFound 380 | if ($this->modx->context && $this->modx->getOption('setlocale', null, true)) { 381 | $locale = setlocale(LC_ALL, '0'); 382 | setlocale(LC_ALL, $this->modx->context->getOption('locale', null, $locale)); 383 | } 384 | } 385 | } 386 | $this->modx->sendForward($this->found['resourceId']); 387 | } 388 | 389 | /** 390 | * Check for a valid regular expression 391 | * 392 | * @param $string 393 | * @return bool 394 | */ 395 | public function isRegularExpression($string) { 396 | set_error_handler(function() {}, E_WARNING); 397 | $isRegularExpression = preg_match($string, '') !== FALSE; 398 | restore_error_handler(); 399 | return $isRegularExpression; 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Plugins/Events/OnDocFormDelete.php: -------------------------------------------------------------------------------- 1 | customrequest->initialize(); 16 | if ($this->modx->context->get('key') !== 'mgr') { 17 | $requestParamAlias = $this->modx->getOption('request_param_alias', null, 'q'); 18 | $requestUri = trim(strtok($_REQUEST[$requestParamAlias] ?? '', '?'), '/'); 19 | if ($this->customrequest->searchAliases($requestUri)) { 20 | $this->customrequest->setRequest(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Plugins/Events/OnSiteRefresh.php: -------------------------------------------------------------------------------- 1 | customrequest->reset(); 17 | $this->modx->log(xPDO::LOG_LEVEL_INFO, $this->modx->lexicon('customrequest.refresh_cache', [ 18 | 'packagename' => $this->customrequest->packageName 19 | ])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Plugins/Plugin.php: -------------------------------------------------------------------------------- 1 | scriptProperties = &$scriptProperties; 35 | $this->modx =& $modx; 36 | $corePath = $this->modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 37 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/', [ 38 | 'core_path' => $corePath 39 | ]); 40 | } 41 | 42 | /** 43 | * Run the plugin event. 44 | */ 45 | public function run() 46 | { 47 | $init = $this->init(); 48 | if ($init !== true) { 49 | return; 50 | } 51 | 52 | $this->process(); 53 | } 54 | 55 | /** 56 | * Initialize the plugin event. 57 | * 58 | * @return bool 59 | */ 60 | public function init() 61 | { 62 | return true; 63 | } 64 | 65 | /** 66 | * Process the plugin event code. 67 | * 68 | * @return mixed 69 | */ 70 | abstract public function process(); 71 | } -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/ObjectCreateProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 37 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 38 | } 39 | 40 | /** 41 | * Get a boolean property. 42 | * @param string $k 43 | * @param mixed $default 44 | * @return bool 45 | */ 46 | public function getBooleanProperty($k, $default = null) 47 | { 48 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | * @return bool 54 | */ 55 | public function beforeSave() 56 | { 57 | foreach ($this->required as $required) { 58 | $value = $this->getProperty($required); 59 | if (empty($value)) { 60 | $this->addFieldError($required, $this->modx->lexicon('field_required')); 61 | } 62 | } 63 | 64 | return parent::beforeSave(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/ObjectGetListProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 39 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 40 | } 41 | 42 | /** 43 | * Get a boolean property. 44 | * @param string $k 45 | * @param mixed $default 46 | * @return bool 47 | */ 48 | public function getBooleanProperty($k, $default = null) 49 | { 50 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | * @return string[] 56 | */ 57 | public function getLanguageTopics() 58 | { 59 | if (file_exists($this->customrequest->getOption('corePath') . 'lexicon/' . $this->modx->getOption('manager_language', [], 'en') . '/custom.inc.php')) { 60 | $this->languageTopics[] = 'customrequest:custom'; 61 | } 62 | return $this->languageTopics; 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | * @return bool 68 | */ 69 | public function beforeQuery() 70 | { 71 | if ($this->getProperty('valuesqry')) { 72 | $this->setProperty('limit', 0); 73 | } 74 | 75 | return parent::beforeQuery(); 76 | } 77 | 78 | /** 79 | * {@inheritDoc} 80 | * @param xPDOQuery $c 81 | * @return xPDOQuery 82 | */ 83 | public function prepareQueryBeforeCount(xPDOQuery $c) 84 | { 85 | $valuesQuery = $this->getProperty('valuesqry'); 86 | $query = (!$valuesQuery) ? $this->getProperty('query') : ''; 87 | if (!empty($query)) { 88 | $conditions = ['id' => intval($query)]; 89 | foreach ($this->search as $search) { 90 | $conditions['OR:' . $search . ':LIKE'] = '%' . $query . '%'; 91 | } 92 | $c->where([$conditions]); 93 | } 94 | 95 | return $c; 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | * @param xPDOQuery $c 101 | * @return xPDOQuery 102 | */ 103 | public function prepareQueryAfterCount(xPDOQuery $c) 104 | { 105 | $valuesQuery = $this->getProperty('valuesqry'); 106 | $id = (!$valuesQuery) ? $this->getProperty('id') : $this->getProperty('query'); 107 | if (!empty($id)) { 108 | $c->where([ 109 | $this->classKey . '.id:IN' => array_map('intval', explode('|', $id)) 110 | ]); 111 | } 112 | 113 | return $c; 114 | } 115 | 116 | /** 117 | * {@inheritDoc} 118 | * @param array $list 119 | * @return array 120 | */ 121 | public function beforeIteration(array $list) 122 | { 123 | if (!$this->getProperty('id') && $this->getBooleanProperty('combo', false)) { 124 | $empty = [ 125 | 'id' => 0, 126 | $this->nameField => $this->modx->lexicon('ext_emptygroup') 127 | ]; 128 | $list[] = $empty; 129 | } 130 | 131 | return $list; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/ObjectRemoveProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 35 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 36 | } 37 | 38 | /** 39 | * Get a boolean property. 40 | * @param string $k 41 | * @param mixed $default 42 | * @return bool 43 | */ 44 | public function getBooleanProperty($k, $default = null) 45 | { 46 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/ObjectSortindexProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 37 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | * @return array|string 43 | */ 44 | public function process() 45 | { 46 | if (!$this->cleanSorting()) { 47 | return $this->failure(); 48 | } 49 | 50 | $targetId = $this->getProperty('targetId'); 51 | $targetIndex = $this->modx->getObject($this->classKey, $targetId)->get($this->indexKey); 52 | 53 | // Prepare the moving ids 54 | $movingIds = explode(',', $this->getProperty('movingIds', 0)); 55 | $c = $this->modx->newQuery($this->classKey); 56 | $c->where([ 57 | 'id:IN' => $movingIds 58 | ]); 59 | $c->sortby($this->indexKey); 60 | $c->sortby('id'); 61 | /** @var xPDOObject[] $movingObjects */ 62 | $movingObjects = $this->modx->getIterator($this->classKey, $c); 63 | foreach ($movingObjects as $movingObject) { 64 | $c = $this->modx->newQuery($this->classKey); 65 | $movingIndex = $movingObject->get($this->indexKey); 66 | if ($movingIndex < $targetIndex) { 67 | $c->where([ 68 | $this->indexKey . ':>' => $movingIndex, 69 | $this->indexKey . ':<=' => $targetIndex, 70 | ]); 71 | } else { 72 | $c->where([ 73 | $this->indexKey . ':<' => $movingIndex, 74 | $this->indexKey . ':>=' => $targetIndex, 75 | ]); 76 | } 77 | $c->sortby($this->indexKey); 78 | $c->sortby('id'); 79 | /** @var xPDOObject[] $affectedObjects */ 80 | $affectedObjects = $this->modx->getIterator($this->classKey, $c); 81 | foreach ($affectedObjects as $affectedObject) { 82 | $affectedIndex = $affectedObject->get($this->indexKey); 83 | if ($movingIndex < $targetIndex) { 84 | $newIndex = $affectedIndex - 1; 85 | } else { 86 | $newIndex = $affectedIndex + 1; 87 | } 88 | $affectedObject->set($this->indexKey, $newIndex); 89 | $affectedObject->save(); 90 | } 91 | $movingObject->set($this->indexKey, $targetIndex); 92 | $movingObject->save(); 93 | } 94 | 95 | if (!$this->hasErrors()) { 96 | $this->afterSortindex(); 97 | } 98 | 99 | return $this->success(); 100 | } 101 | 102 | /** 103 | * Get a boolean property. 104 | * @param string $k 105 | * @param mixed $default 106 | * @return bool 107 | */ 108 | public function getBooleanProperty($k, $default = null) 109 | { 110 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 111 | } 112 | 113 | /** 114 | * Override in your derivative class to do functionality after sorting objects 115 | */ 116 | protected function afterSorting() 117 | { 118 | } 119 | 120 | /** 121 | * Clean the current sorting. 122 | * 123 | * @return bool 124 | */ 125 | private function cleanSorting() 126 | { 127 | $c = $this->modx->newQuery($this->classKey); 128 | $c->sortby($this->indexKey); 129 | $c->sortby('id'); 130 | 131 | /** @var xPDOObject[] $objects */ 132 | $objects = $this->modx->getIterator($this->classKey, $c); 133 | if (!$objects) { 134 | return false; 135 | } 136 | 137 | $i = 0; 138 | foreach ($objects as $object) { 139 | $object->set($this->indexKey, $i); 140 | $object->save(); 141 | $i++; 142 | } 143 | return true; 144 | } 145 | } 146 | 147 | return 'CustomRequestCategorySortindexProcessor'; 148 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/ObjectUpdateProcessor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 37 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 38 | } 39 | 40 | /** 41 | * Get a boolean property. 42 | * @param string $k 43 | * @param mixed $default 44 | * @return bool 45 | */ 46 | public function getBooleanProperty($k, $default = null) 47 | { 48 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | * @return bool 54 | */ 55 | public function beforeSave() 56 | { 57 | foreach ($this->required as $required) { 58 | $value = $this->getProperty($required); 59 | if (empty($value)) { 60 | $this->addFieldError($required, $this->modx->lexicon('field_required')); 61 | } 62 | } 63 | 64 | return parent::beforeSave(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/components/customrequest/src/Processors/Processor.php: -------------------------------------------------------------------------------- 1 | modx->getOption('customrequest.core_path', null, $this->modx->getOption('core_path') . 'components/customrequest/'); 35 | $this->customrequest = $this->modx->getService('customrequest', 'CustomRequest', $corePath . 'model/customrequest/'); 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | * @return bool 41 | */ 42 | public function checkPermissions() 43 | { 44 | return !empty($this->permission) ? $this->modx->hasPermission($this->permission) : true; 45 | } 46 | 47 | abstract public function process(); 48 | 49 | /** 50 | * Get a boolean property. 51 | * @param string $k 52 | * @param mixed $default 53 | * @return bool 54 | */ 55 | public function getBooleanProperty($k, $default = null) 56 | { 57 | return ($this->getProperty($k, $default) === 'true' || $this->getProperty($k, $default) === true || $this->getProperty($k, $default) === '1' || $this->getProperty($k, $default) === 1); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/components/customrequest/templates/home.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/core/components/customrequest/templates/home.tpl -------------------------------------------------------------------------------- /docs/contributors.md: -------------------------------------------------------------------------------- 1 | The CustomRequest project was started in 2013 by [Thomas Jakobi](https://github.com/jako). 2 | 3 | Many thanks to all who contributed, whether by creating pull requests, submitting bug reports, or donating. -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | ### Calendar 2 | 3 | With the **Date** configuration you could use an calendar snippet on the 4 | resource with the URI `calendar/date/`. The snippet on that resource would 5 | use the request parameters `year`, `month`, `day`, `title` to identify the 6 | event. An example URI triggering this configuration: 7 | `/calendar/date/2015/09/01/eventname.html` 8 | 9 | The **Calendar** configuration sends the request parameters `year`, `month`, 10 | `day` to another snippet on the resource with the URI `calendar/`. Example 11 | triggering URI: `/calendar/2015/09/01/` [^1] 12 | 13 | Name | Alias Path | Resource | URI Parameter | Regular Expression 14 | --------------|------------|----------|---------------|------------------- 15 | Date | calendar/date/ | | ["year", "month", "day", "title"] | 16 | Calendar | calendar/ | | ["year", "month", "day"] | 17 | 18 | [^1]: If you are using two or more nested aliases in your configs, the deeper alias should be defined before the narrower alias in the configs. You could drag and drop the configurations in the grid of the custom manager page. 19 | 20 | ### Gallery 21 | 22 | With the **Gallery** configuration you could use the Gallery snippet on the 23 | selected resource `Gallery Folder`. The Gallery snippet on that resource would 24 | use the request parameters `galAlbum`, `galItem` to identify the gallery and 25 | the image. An example URI triggering this configuration: `/gallery/01/02/` 26 | 27 | Name | Alias Path | Resource | URI Parameter | Regular Expression 28 | --------------|------------|----------|---------------|------------------- 29 | Gallery | | Gallery Folder | ["galAlbum", "galItem"] | 30 | 31 | ### Different URI 32 | 33 | With the **Different URI** configuration you could call the resource `Test` 34 | with a complete different URI using the request parameters `parameter1`, 35 | `parameter2`. [^2] 36 | 37 | Name | Alias Path | Resource | URI Parameter | Regular Expression 38 | --------------|------------|----------|---------------|------------------- 39 | Different URI | complete/different/uri/ | Test | ["parameter1", "parameter2"] | 40 | 41 | [^2]: The Alias Path does not have to match the alias of the Resource. 42 | 43 | ### Regular Expression 44 | 45 | You could even use regular expressions[^5] to set the request parameters [^3] 46 | 47 | Name | Alias Path | Resource | URI Parameter | Regular Expression 48 | --------------|------------|----------|---------------|------------------- 49 | Expression | | Expression | ["string", "numeric"] | #(.*?)-(\d+)# 50 | 51 | [^3]: This rule does not make much sense. If you have a real world example, please ... 52 | 53 | ### Pagination 54 | 55 | The Alias Path field could be filled with a valid regular expression[^5] (the 56 | color of the grid field is changed to blue then[^4]) and the Resource field 57 | could stay empty. Thay way you could use one pagination configuration for all 58 | pagination calls on the page. The first subpattern part (`page/`) of the 59 | expression is stripped from the not found url and the remaining string is used 60 | to identify the resource where the user is forwarded to later. 61 | 62 | Name | Alias Path | Resource | URI Parameter | Regular Expression 63 | --------------|------------|----------|---------------|------------------- 64 | Pagination | #.*?(page/)# | | ["page"] | #(\d+)# 65 | 66 | [^4]: The regular expression[^5] has to be valid and it should contain [delimiters](https://www.php.net/manual/en/regexp.reference.delimiters.php) 67 | 68 | [^5]: To build and check regular expressions you could i.e. use the [regex101](https://regex101.com/) website. 69 | 70 | ## System Settings 71 | 72 | CustomRequest uses the following system settings in the namespace `customrequest`: 73 | 74 | Key | Description | Default 75 | ----|-------------|-------- 76 | customrequest.debug | Log debug information in the MODX error log | No 77 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # CustomRequest 2 | 3 | CustomRequest is an effective routing plugin for MODX Revolution to map pretty 4 | but not found URLs to a MODX resource and set the request parameters by 5 | separating the URI path at the URI separators or by a regular expression. 6 | 7 | ### Requirements 8 | 9 | * MODX Revolution 2.6+ 10 | * PHP 7.2+ 11 | 12 | ### Features 13 | 14 | * Use pretty URLs everywhere in the MODX Revolution frontend. 15 | 16 | ### License 17 | 18 | The project is licensed under the [GPLv2 license](https://github.com/Jako/CustomRequest/LICENSE.md). 19 | 20 | ### Translations [![Default Lexicon](https://hosted.weblate.org/widget/modx-extras/customrequest/standard/svg-badge.svg)](https://hosted.weblate.org/projects/modx-extras/customrequest/) 21 | 22 | Translations of the package can be made for the [Default Lexicon](https://hosted.weblate.org/projects/modx-extras/customrequest/standard/) and the [System Setting Lexicon](https://hosted.weblate.org/projects/modx-extras/customrequest/system-settings/) 23 | 24 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | ## Install from MODX Extras 2 | 3 | Search for CustomRequest in the Package Manager of your MODX installation and 4 | install it in there. 5 | 6 | ## Manual installation 7 | 8 | If you can't access the MODX Extras Repository in your MODX installation, you 9 | can manually install CustomRequest. 10 | 11 | * Download the transport package from [MODX Extras](https://modx.com/extras/package/customrequest) (or one of the pre built transport packages in [_packages](https://github.com/Jako/CustomRequest/tree/master/_packages)) 12 | * Upload the zip file to your MODX installation's `core/packages` folder or upload it manually in the MODX Package Manager. 13 | * In the MODX Manager, navigate to the Package Manager page, and select 'Search locally for packages' from the dropdown button. 14 | * CustomRequest should now show up in the list of available packages. Click the corresponding 'Install' button and follow the instructions to complete the installation. 15 | 16 | ## Build it from source 17 | 18 | To build and install the package from source you could use [Git Package 19 | Management](https://github.com/TheBoxer/Git-Package-Management). The GitHub 20 | repository of CustomRequest contains a 21 | [config.json](https://github.com/Jako/CustomRequest/blob/master/_build/config.json) 22 | to build that package locally. Use this option, if you want to debug 23 | CustomRequest and/or contribute bugfixes and enhancements. 24 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | ## How it works 2 | 3 | CustomRequest works as routing plugin and maps not found URLs to a MODX resource 4 | and set additional request parameters by separating the URI path at the URI 5 | separators or by a regular expression. 6 | 7 | As standard option the first characters of the not found URI will be compared 8 | with the alias value of each config entry. If found, this config entry is used. 9 | The alias path is stripped from the not found URI and the remaining string is 10 | used to set the request parameters. 11 | 12 | As a second option, the alias value of a config entry could contain a valid 13 | regular expression and the not found URI will be matched with that expression. 14 | If matched, this config entry is used. The first subpattern part of the 15 | expression is stripped from the found pattern and the remaining string is used 16 | to identify the resource where the user is forwarded to later. At least the 17 | found pattern is removed from the not found URI and the remaining string is 18 | used to set the request parameters. 19 | 20 | ## Custom Manager Page 21 | 22 | You could configure CustomRequest in a custom manager page in the extras menu. 23 | The CustomRequest configurations could be created on that page. 24 | 25 | The configurations are executed in the order of the grid on this page. If there 26 | are two configurations starting with the same alias path, the first 27 | configuration is used. You can change the order of the configurations by 28 | drag and drop. 29 | 30 | The following settings could be set in each configuration: 31 | 32 | | Key | Description | 33 | |--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 34 | | Name | A name to identify this configuration. | 35 | | Alias Path |

Normally the first characters of a not found URI are compared with this string. If both strings are matching, this configuration is used. If the alias path field is not set, the alias path of the selected resource in this form is used **(The grid value is shown with green text then)**. The alias path is stripped from the not found URI and the remaining string is used to set the request parameters.

As a second option, this field could contain a valid regular expression[^1] **(The grid value is shown with blue text then)**. The regular expression has to contain [delimiters](https://www.php.net/manual/en/regexp.reference.delimiters.php). The not found URI will be matched with that expression. If matched, this config entry is used. The first subpattern part of the expression is stripped from the found pattern and the remaining string is used to identify the resource where the user is forwarded to. At least the found pattern is removed from the not found URI and the remaining string is used to set the request parameters. | 36 | | Resource | A not found URI is forwarded to this resource, if the current configuration is used and if the alias path does not contain a regular expression. | 37 | | URI Parameter | The request/get/post parameter keys, the divided second part of the not found URI are assigned to. If the Regular Expression field not set, the second part is divided at the URI separators `/` | 38 | | Regular Expression | This optional regular expression[^1] is used to divide the second parts of the not found URI. The regular expression has to contain [delimiters](https://www.php.net/manual/en/regexp.reference.delimiters.php). The search results are assigned to the request parameters in the order of occurrence. | 39 | 40 | [^1]: To build and check regular expressions you could i.e. use the [regex101](https://regex101.com/) website. Please use the code generator, since this generator creates the regular expression with delimiters. 41 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'), 2 | autoprefixer = require('autoprefixer'), 3 | composer = require('gulp-uglify/composer'), 4 | concat = require('gulp-concat'), 5 | cssnano = require('cssnano'), 6 | footer = require('gulp-footer'), 7 | format = require('date-format'), 8 | header = require('@fomantic/gulp-header'), 9 | postcss = require('gulp-postcss'), 10 | rename = require('gulp-rename'), 11 | replace = require('gulp-replace'), 12 | sass = require('gulp-sass')(require('sass')), 13 | uglifyjs = require('uglify-js'), 14 | uglify = composer(uglifyjs, console), 15 | pkg = require('./_build/config.json'); 16 | 17 | const banner = '/*!\n' + 18 | ' * <%= pkg.name %> - <%= pkg.description %>\n' + 19 | ' * Version: <%= pkg.version %>\n' + 20 | ' * Build date: ' + format("yyyy-MM-dd", new Date()) + '\n' + 21 | ' */'; 22 | const year = new Date().getFullYear(); 23 | 24 | let phpversion; 25 | let modxversion; 26 | pkg.dependencies.forEach(function (dependency, index) { 27 | switch (pkg.dependencies[index].name) { 28 | case 'php': 29 | phpversion = pkg.dependencies[index].version.replace(/>=/, ''); 30 | break; 31 | case 'modx': 32 | modxversion = pkg.dependencies[index].version.replace(/>=/, ''); 33 | break; 34 | } 35 | }); 36 | 37 | const scriptsMgr = function () { 38 | return gulp.src([ 39 | 'source/js/mgr/customrequest.js', 40 | 'source/js/mgr/helper/combo.js', 41 | 'source/js/mgr/helper/util.js', 42 | 'source/js/mgr/widgets/home.panel.js', 43 | 'source/js/mgr/widgets/configs.grid.js', 44 | 'source/js/mgr/widgets/settings.panel.js', 45 | 'source/js/mgr/sections/home.js' 46 | ]) 47 | .pipe(concat('customrequest.min.js')) 48 | .pipe(uglify()) 49 | .pipe(header(banner + '\n', {pkg: pkg})) 50 | .pipe(gulp.dest('assets/components/customrequest/js/mgr/')) 51 | }; 52 | gulp.task('scripts', gulp.series(scriptsMgr)); 53 | 54 | const sassMgr = function () { 55 | return gulp.src([ 56 | 'source/sass/mgr/customrequest.scss' 57 | ]) 58 | .pipe(sass().on('error', sass.logError)) 59 | .pipe(postcss([ 60 | autoprefixer() 61 | ])) 62 | .pipe(gulp.dest('source/css/mgr/')) 63 | .pipe(concat('customrequest.css')) 64 | .pipe(postcss([ 65 | cssnano({ 66 | preset: ['default', { 67 | discardComments: { 68 | removeAll: true 69 | } 70 | }] 71 | }) 72 | ])) 73 | .pipe(rename({ 74 | suffix: '.min' 75 | })) 76 | .pipe(footer('\n' + banner, {pkg: pkg})) 77 | .pipe(gulp.dest('assets/components/customrequest/css/mgr/')) 78 | }; 79 | gulp.task('sass', gulp.series(sassMgr)); 80 | 81 | const imagesMgr = function () { 82 | return gulp.src('./source/img/**/*.+(png|jpg|gif|svg)', {encoding: false}) 83 | .pipe(gulp.dest('assets/components/customrequest/img/')); 84 | }; 85 | gulp.task('images', gulp.series(imagesMgr)); 86 | 87 | const bumpCopyright = function () { 88 | return gulp.src([ 89 | 'core/components/customrequest/model/customrequest/customrequest.class.php', 90 | 'core/components/customrequest/src/CustomRequest.php', 91 | ], {base: './'}) 92 | .pipe(replace(/Copyright 2013(-\d{4})? by/g, 'Copyright ' + (year > 2013 ? '2013-' : '') + year + ' by')) 93 | .pipe(gulp.dest('.')); 94 | }; 95 | const bumpVersion = function () { 96 | return gulp.src([ 97 | 'core/components/customrequest/src/CustomRequest.php', 98 | ], {base: './'}) 99 | .pipe(replace(/version = '\d+\.\d+\.\d+-?[0-9a-z]*'/ig, 'version = \'' + pkg.version + '\'')) 100 | .pipe(gulp.dest('.')); 101 | }; 102 | const bumpHomepanel = function () { 103 | return gulp.src([ 104 | 'source/js/mgr/widgets/home.panel.js' 105 | ], {base: './'}) 106 | .pipe(replace(/© 2013(-\d{4})?/g, '© ' + (year > 2013 ? '2013-' : '') + year)) 107 | .pipe(gulp.dest('.')); 108 | }; 109 | const bumpDocs = function () { 110 | return gulp.src([ 111 | 'mkdocs.yml', 112 | ], {base: './'}) 113 | .pipe(replace(/© 2013(-\d{4})?/g, '© ' + (year > 2013 ? '2013-' : '') + year)) 114 | .pipe(gulp.dest('.')); 115 | }; 116 | const bumpRequirements = function () { 117 | return gulp.src([ 118 | 'docs/index.md', 119 | ], {base: './'}) 120 | .pipe(replace(/[*-] MODX Revolution \d.\d.*/g, '* MODX Revolution ' + modxversion + '+')) 121 | .pipe(replace(/[*-] PHP (v)?\d.\d.*/g, '* PHP ' + phpversion + '+')) 122 | .pipe(gulp.dest('.')); 123 | }; 124 | gulp.task('bump', gulp.series(bumpCopyright, bumpVersion, bumpHomepanel, bumpDocs, bumpRequirements)); 125 | 126 | gulp.task('watch', function () { 127 | // Watch .js files 128 | gulp.watch(['./source/js/**/*.js'], gulp.series('scripts')); 129 | // Watch .scss files 130 | gulp.watch(['./source/sass/**/*.scss'], gulp.series('sass')); 131 | // Watch *.(png|jpg|gif|svg) files 132 | gulp.watch(['./source/img/**/*.(png|jpg|gif|svg)'], gulp.series('images')); 133 | }); 134 | 135 | // Default Task 136 | gulp.task('default', gulp.series('bump', 'scripts', 'sass', 'images')); 137 | -------------------------------------------------------------------------------- /icon/customrequest.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/icon/customrequest.ai -------------------------------------------------------------------------------- /icon/customrequest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/icon/customrequest.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: CustomRequest 2 | docs_dir: docs 3 | site_dir: _build/site 4 | edit_uri: tree/master/docs 5 | 6 | repo_name: 'Jako/CustomRequest' 7 | repo_url: https://github.com/Jako/CustomRequest 8 | 9 | copyright: '© 2013-2024 Treehill Studio' 10 | 11 | nav: 12 | - Description: 'index.md' 13 | - Installation: 'installation.md' 14 | - Usage: 'usage.md' 15 | - Examples: 'examples.md' 16 | - Contributors: 'contributors.md' 17 | 18 | extra: 19 | analytics: 20 | provider: matomo 21 | url: matomo.treehillstudio.com 22 | site: 40 23 | social: 24 | - icon: fontawesome/brands/github 25 | link: https://github.com/Jako 26 | 27 | theme: 28 | name: material 29 | custom_dir: ../../mkdocs-material-treehillstudio/material 30 | 31 | # 404 page 32 | static_templates: 33 | - 404.html 34 | 35 | # Default values, taken from mkdocs_theme.yml 36 | language: en 37 | feature: 38 | tabs: false 39 | font: false 40 | 41 | markdown_extensions: 42 | - footnotes 43 | - tables 44 | - admonition 45 | - meta 46 | - toc 47 | - pymdownx.details 48 | - pymdownx.superfences 49 | - attr_list 50 | - pymdownx.emoji: 51 | emoji_index: !!python/name:material.extensions.emoji.twemoji 52 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 53 | 54 | plugins: 55 | - glightbox 56 | - search 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customrequest", 3 | "description": "Use friendly URLs everywhere", 4 | "author": "Thomas Jakobi ", 5 | "private": true, 6 | "devDependencies": { 7 | "autoprefixer": "^10.4.20", 8 | "cssnano": "^7.0.6", 9 | "date-format": "^4.0.14", 10 | "gulp": "^5.0.0", 11 | "gulp-concat": "^2.6.1", 12 | "gulp-footer": "^2.1.0", 13 | "@fomantic/gulp-header": "^2.1.1", 14 | "gulp-postcss": "^10.0.0", 15 | "gulp-rename": "^2.0.0", 16 | "gulp-replace": "^1.1.4", 17 | "gulp-sass": "^5.1.0", 18 | "gulp-uglify": "^3.0.2", 19 | "postcss": "^8.4.45", 20 | "sass": "^1.78.0", 21 | "uglify-js": "^3.19.3" 22 | }, 23 | "browserslist": [ 24 | "last 2 version", 25 | "> 1%", 26 | "maintained node versions", 27 | "not dead" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /source/css/mgr/customrequest.css: -------------------------------------------------------------------------------- 1 | .action-buttons { 2 | text-align: center; 3 | } 4 | .action-buttons li { 5 | display: inline-block; 6 | font-size: 1.1em; 7 | line-height: 0.7; 8 | margin-right: 5px; 9 | } 10 | .action-buttons li:last-child { 11 | margin-right: 0; 12 | } 13 | 14 | .modx-grid .x-grid-cell-icons .x-grid3-cell-inner { 15 | padding-right: 5px; 16 | } 17 | 18 | .treehillstudio_about { 19 | cursor: pointer; 20 | height: 40px; 21 | opacity: 0.25; 22 | transition: opacity 0.5s; 23 | margin-top: 20px; 24 | } 25 | .treehillstudio_about:hover { 26 | opacity: 1; 27 | } 28 | .treehillstudio_about .x-panel-body { 29 | text-align: right; 30 | } 31 | 32 | .treehillstudio_window a { 33 | color: #b2bf28; 34 | text-decoration: none; 35 | } 36 | .treehillstudio_window a:hover { 37 | color: #77801a; 38 | } 39 | 40 | .home-panel.debug { 41 | background: linear-gradient(to top right, transparent, transparent, transparent, #ffffff); 42 | } 43 | 44 | .ribbon { 45 | height: 180px; 46 | overflow: hidden; 47 | pointer-events: none; 48 | position: absolute; 49 | width: 180px; 50 | z-index: 15; 51 | } 52 | .ribbon:before, .ribbon:after { 53 | border: 5px solid #a20000; 54 | content: ""; 55 | display: block; 56 | position: absolute; 57 | z-index: -1; 58 | } 59 | .ribbon.top-right { 60 | right: -10px; 61 | top: -10px; 62 | } 63 | .ribbon.top-right:before, .ribbon.top-right:after { 64 | border-right-color: transparent; 65 | border-top-color: transparent; 66 | } 67 | .ribbon.top-right:before { 68 | left: 0; 69 | top: 0; 70 | } 71 | .ribbon.top-right:after { 72 | bottom: 0; 73 | right: 0; 74 | } 75 | .ribbon.top-right span { 76 | left: -46px; 77 | top: 46px; 78 | transform: rotate(45deg); 79 | } 80 | .ribbon span { 81 | background-color: #be0000; 82 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); 83 | color: #ffffff; 84 | display: block; 85 | font: 700 18px/1 "Lato", sans-serif; 86 | padding: 15px 0; 87 | position: absolute; 88 | text-align: center; 89 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 90 | text-transform: uppercase; 91 | width: 300px; 92 | } 93 | 94 | @media screen and (min-width: 961px) { 95 | #modx-content > .x-panel-bwrap > .x-panel-body > .x-panel.modx3.debug { 96 | margin-right: 15px; 97 | } 98 | #modx-content > .x-panel-bwrap > .x-panel-body > .x-panel.modx3.debug .modx-page-header { 99 | padding-right: 0; 100 | } 101 | #modx-content > .x-panel-bwrap > .x-panel-body > .x-panel.modx3.debug .modx-page-header ~ .x-panel { 102 | margin-right: 0; 103 | } 104 | #modx-content > .x-panel-bwrap > .x-panel-body > .x-panel.modx3 .treehillstudio_about { 105 | margin: 0 15px; 106 | } 107 | } -------------------------------------------------------------------------------- /source/img/mgr/treehill-studio-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/source/img/mgr/treehill-studio-small.png -------------------------------------------------------------------------------- /source/img/mgr/treehill-studio-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/source/img/mgr/treehill-studio-small@2x.png -------------------------------------------------------------------------------- /source/img/mgr/treehill-studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/source/img/mgr/treehill-studio.png -------------------------------------------------------------------------------- /source/img/mgr/treehill-studio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jako/CustomRequest/f91885e1941b374f8ee4ab0d3bb72ca6a513b1c5/source/img/mgr/treehill-studio@2x.png -------------------------------------------------------------------------------- /source/js/mgr/customrequest.js: -------------------------------------------------------------------------------- 1 | var customRequest = function (config) { 2 | config = config || {}; 3 | customRequest.superclass.constructor.call(this, config); 4 | }; 5 | Ext.extend(customRequest, Ext.Component, { 6 | initComponent: function () { 7 | this.stores = {}; 8 | this.ajax = new Ext.data.Connection({ 9 | disableCaching: true, 10 | }); 11 | }, page: {}, window: {}, grid: {}, tree: {}, panel: {}, combo: {}, config: {}, util: {}, form: {} 12 | }); 13 | Ext.reg('customrequest', customRequest); 14 | 15 | CustomRequest = new customRequest(); 16 | 17 | MODx.config.help_url = 'https://jako.github.io/CustomRequest/usage/'; 18 | -------------------------------------------------------------------------------- /source/js/mgr/helper/combo.js: -------------------------------------------------------------------------------- 1 | CustomRequest.combo.Resource = function (config) { 2 | config = config || {}; 3 | Ext.applyIf(config, { 4 | url: CustomRequest.config.connectorUrl, 5 | baseParams: { 6 | action: 'mgr/resources/getlist', 7 | combo: true 8 | }, 9 | pageSize: 10, 10 | fields: ['id', 'pagetitle'], 11 | displayField: 'pagetitle', 12 | valueField: 'id', 13 | lazyRender: true, 14 | editable: true, 15 | typeAhead: true, 16 | minChars: 1, 17 | forceSelection: true, 18 | triggerAction: 'all' 19 | }); 20 | CustomRequest.combo.Resource.superclass.constructor.call(this, config); 21 | }; 22 | Ext.extend(CustomRequest.combo.Resource, MODx.combo.ComboBox); 23 | Ext.reg('customrequest-combo-resource', CustomRequest.combo.Resource); 24 | -------------------------------------------------------------------------------- /source/js/mgr/helper/util.js: -------------------------------------------------------------------------------- 1 | CustomRequest.util.htmlRenderer = function (value) { 2 | return MODx.util.safeHtml(value, ''); 3 | } 4 | -------------------------------------------------------------------------------- /source/js/mgr/sections/home.js: -------------------------------------------------------------------------------- 1 | CustomRequest.page.Home = function (config) { 2 | config = config || {}; 3 | Ext.applyIf(config, { 4 | buttons: [{ 5 | text: _('help_ex'), 6 | handler: MODx.loadHelpPane 7 | }], 8 | formpanel: 'customrequest-panel-home', 9 | components: [{ 10 | xtype: 'customrequest-panel-home' 11 | }] 12 | }); 13 | CustomRequest.page.Home.superclass.constructor.call(this, config); 14 | }; 15 | Ext.extend(CustomRequest.page.Home, MODx.Component); 16 | Ext.reg('customrequest-page-home', CustomRequest.page.Home); 17 | -------------------------------------------------------------------------------- /source/js/mgr/widgets/configs.grid.js: -------------------------------------------------------------------------------- 1 | CustomRequest.grid.Configs = function (config) { 2 | config = config || {}; 3 | this.ident = 'customrequest-configs-' + Ext.id(); 4 | this.buttonColumnTpl = new Ext.XTemplate('' 5 | + '' 6 | + '
    ' 7 | + '' 8 | + '
  • ' 9 | + '
    ' 10 | + '
' 11 | + '
' 12 | + '
', { 13 | compiled: true 14 | }); 15 | Ext.applyIf(config, { 16 | id: this.ident, 17 | url: CustomRequest.config.connectorUrl, 18 | baseParams: { 19 | action: 'mgr/configs/getlist' 20 | }, 21 | fields: ['id', 'name', 'menuindex', 'alias', 'alias_gen', 'pagetitle', 'resourceid', 'context', 'urlparams', 'regex'], 22 | autoHeight: true, 23 | paging: true, 24 | remoteSort: false, 25 | enableDragDrop: true, 26 | ddGroup: 'customrequest-grid-dd', 27 | autoExpandColumn: 'alias', 28 | showActionsColumn: false, 29 | columns: [{ 30 | header: _('customrequest.configs_name'), 31 | dataIndex: 'name', 32 | width: 100 33 | }, { 34 | header: _('customrequest.configs_alias'), 35 | dataIndex: 'alias_gen', 36 | renderer: CustomRequest.util.htmlRenderer, 37 | width: 150 38 | }, { 39 | header: _('customrequest.configs_resourceid'), 40 | dataIndex: 'pagetitle', 41 | width: 80 42 | }, { 43 | header: _('customrequest.configs_context'), 44 | dataIndex: 'context', 45 | width: 80 46 | }, { 47 | header: _('id'), 48 | dataIndex: 'id', 49 | hidden: true, 50 | width: 20 51 | }, { 52 | renderer: { 53 | fn: this.buttonColumnRenderer, 54 | scope: this 55 | }, 56 | menuDisabled: true, 57 | width: 40 58 | }], 59 | tbar: [{ 60 | text: _('customrequest.configs_create'), 61 | cls: 'primary-button', 62 | handler: this.createConfig 63 | }, '->', { 64 | xtype: 'textfield', 65 | id: this.ident + '-config-filter-search', 66 | emptyText: _('search') + '…', 67 | submitValue: false, 68 | listeners: { 69 | change: { 70 | fn: this.search, 71 | scope: this 72 | }, 73 | render: { 74 | fn: function (cmp) { 75 | new Ext.KeyMap(cmp.getEl(), { 76 | key: Ext.EventObject.ENTER, 77 | fn: function () { 78 | this.fireEvent('change', this); 79 | this.blur(); 80 | return true; 81 | }, 82 | scope: cmp 83 | }); 84 | }, 85 | scope: this 86 | } 87 | } 88 | }, { 89 | xtype: 'button', 90 | id: this.ident + '-config-filter-clear', 91 | cls: 'x-form-filter-clear', 92 | text: _('filter_clear'), 93 | listeners: { 94 | click: { 95 | fn: this.clearFilter, 96 | scope: this 97 | } 98 | } 99 | }], 100 | listeners: { 101 | render: { 102 | fn: this.renderListener, 103 | scope: this 104 | } 105 | } 106 | }); 107 | CustomRequest.grid.Configs.superclass.constructor.call(this, config); 108 | }; 109 | Ext.extend(CustomRequest.grid.Configs, MODx.grid.Grid, { 110 | windows: {}, 111 | getMenu: function () { 112 | var m = []; 113 | m.push({ 114 | text: _('customrequest.configs_update'), 115 | handler: this.updateConfig 116 | }); 117 | m.push('-'); 118 | m.push({ 119 | text: _('customrequest.configs_duplicate'), 120 | handler: this.duplicateConfig 121 | }); 122 | m.push('-'); 123 | m.push({ 124 | text: _('customrequest.configs_remove'), 125 | handler: this.removeConfig 126 | }); 127 | this.addContextMenuItem(m); 128 | }, 129 | createConfig: function (btn, e) { 130 | this.createUpdateConfig(btn, e, false); 131 | }, 132 | updateConfig: function (btn, e) { 133 | this.createUpdateConfig(btn, e, true); 134 | }, 135 | createUpdateConfig: function (btn, e, isUpdate) { 136 | var r; 137 | if (isUpdate) { 138 | if (!this.menu.record || !this.menu.record.id) { 139 | return false; 140 | } 141 | r = this.menu.record; 142 | } else { 143 | r = {}; 144 | } 145 | var createUpdateConfig = MODx.load({ 146 | xtype: 'customrequest-window-config-create-update', 147 | isUpdate: isUpdate, 148 | title: (isUpdate) ? _('customrequest.configs_update') : _('customrequest.configs_create'), 149 | record: r, 150 | listeners: { 151 | success: { 152 | fn: this.refresh, 153 | scope: this 154 | } 155 | } 156 | }); 157 | createUpdateConfig.fp.getForm().setValues(r); 158 | createUpdateConfig.show(e.target); 159 | }, 160 | duplicateConfig: function (btn, e) { 161 | if (!this.menu.record) { 162 | return false; 163 | } 164 | var r = Ext.apply({}, this.menu.record); 165 | r.name = _('customrequest.duplicate') + ' ' + r.name; 166 | r.original_id = r.id; 167 | r.id = null; 168 | r.repeat_on = (r.repeat_on) ? r.repeat_on : []; 169 | var duplicateConfig = MODx.load({ 170 | xtype: 'customrequest-window-config-create-update', 171 | isUpdate: false, 172 | title: _('customrequest.configs_duplicate'), 173 | record: r, 174 | listeners: { 175 | success: { 176 | fn: this.refresh, 177 | scope: this 178 | } 179 | } 180 | }); 181 | duplicateConfig.fp.getForm().setValues(r); 182 | duplicateConfig.show(e.target); 183 | }, 184 | removeConfig: function () { 185 | if (!this.menu.record) { 186 | return false; 187 | } 188 | MODx.msg.confirm({ 189 | title: _('customrequest.configs_remove'), 190 | text: _('customrequest.configs_remove_confirm'), 191 | url: this.config.url, 192 | params: { 193 | action: 'mgr/configs/remove', 194 | id: this.menu.record.id 195 | }, 196 | listeners: { 197 | success: { 198 | fn: this.refresh, 199 | scope: this 200 | } 201 | } 202 | }); 203 | }, 204 | search: function (tf) { 205 | var s = this.getStore(); 206 | s.baseParams.query = tf.getValue(); 207 | this.getBottomToolbar().changePage(1); 208 | this.refresh(); 209 | }, 210 | clearFilter: function () { 211 | var s = this.getStore(); 212 | s.baseParams.query = ''; 213 | Ext.getCmp(this.ident + '-config-filter-search').reset(); 214 | this.getBottomToolbar().changePage(1); 215 | this.refresh(); 216 | }, 217 | renderListener: function (grid) { 218 | new Ext.dd.DropTarget(grid.container, { 219 | ddGroup: 'customrequest-grid-dd', 220 | notifyDrop: function (dd, e, data) { 221 | var ds = grid.store; 222 | var sm = grid.getSelectionModel(); 223 | var rows = sm.getSelections(); 224 | var dragData = dd.getDragData(e); 225 | if (dragData) { 226 | var cindex = dragData.rowIndex; 227 | if (typeof (cindex) !== 'undefined') { 228 | var target = ds.getAt(cindex); 229 | var dragIds = []; 230 | for (var i = 0; i < rows.length; i++) { 231 | ds.remove(ds.getById(rows[i].id)); 232 | dragIds.push(rows[i].id); 233 | } 234 | ds.insert(cindex, data.selections); 235 | sm.clearSelections(); 236 | grid.sortIndex(target.id, dragIds); 237 | return true; 238 | } 239 | } 240 | grid.getView().refresh(); 241 | } 242 | }) 243 | }, 244 | sortIndex: function (targetId, movingIds) { 245 | MODx.Ajax.request({ 246 | url: CustomRequest.config.connectorUrl, 247 | params: { 248 | action: 'mgr/configs/sortindex', 249 | targetId: targetId, 250 | movingIds: movingIds.join() 251 | }, 252 | listeners: { 253 | success: { 254 | fn: this.refresh, 255 | scope: this 256 | } 257 | } 258 | }); 259 | }, 260 | buttonColumnRenderer: function (value, metaData) { 261 | metaData.css = 'x-grid-cell-icons'; 262 | var values = { 263 | action_buttons: [ 264 | { 265 | className: 'update', 266 | icon: 'pencil-square-o', 267 | text: _('customrequest.configs_update') 268 | }, { 269 | className: 'duplicate', 270 | icon: 'clone', 271 | text: _('customrequest.configs_duplicate') 272 | }, { 273 | className: 'remove', 274 | icon: 'trash-o', 275 | text: _('customrequest.configs_remove') 276 | } 277 | ] 278 | }; 279 | return this.buttonColumnTpl.apply(values); 280 | }, 281 | onClick: function (e) { 282 | var t = e.getTarget(); 283 | var elm = t.className.split(' ')[0]; 284 | if (elm === 'icon') { 285 | var act = t.className.split(' ')[1]; 286 | var record = this.getSelectionModel().getSelected(); 287 | this.menu.record = record.data; 288 | switch (act) { 289 | case 'remove': 290 | this.removeConfig(record, e); 291 | break; 292 | case 'duplicate': 293 | this.duplicateConfig(record, e); 294 | break; 295 | case 'update': 296 | this.updateConfig(record, e); 297 | break; 298 | default: 299 | break; 300 | } 301 | } 302 | } 303 | }); 304 | Ext.reg('customrequest-grid-configs', CustomRequest.grid.Configs); 305 | 306 | CustomRequest.window.CreateUpdateConfig = function (config) { 307 | config = config || {}; 308 | this.ident = 'customrequest-config-create-update-' + Ext.id(); 309 | Ext.applyIf(config, { 310 | id: this.ident, 311 | url: CustomRequest.config.connectorUrl, 312 | action: (config.isUpdate) ? 'mgr/configs/update' : 'mgr/configs/create', 313 | autoHeight: true, 314 | closeAction: 'close', 315 | cls: 'modx-window customrequest-window modx' + CustomRequest.config.modxversion, 316 | fields: [{ 317 | xtype: 'textfield', 318 | fieldLabel: _('customrequest.configs_name'), 319 | name: 'name', 320 | id: this.ident + '-name', 321 | anchor: '100%' 322 | }, { 323 | xtype: 'textfield', 324 | fieldLabel: _('customrequest.configs_alias'), 325 | description: _('customrequest.configs_alias_desc'), 326 | name: 'alias', 327 | id: this.ident + '-alias', 328 | anchor: '100%' 329 | }, { 330 | xtype: 'customrequest-combo-resource', 331 | fieldLabel: _('customrequest.configs_resourceid'), 332 | description: _('customrequest.configs_resourceid_desc'), 333 | name: 'resourceid', 334 | hiddenName: 'resourceid', 335 | id: this.ident + '-resourceid', 336 | anchor: '100%' 337 | }, { 338 | xtype: 'textarea', 339 | fieldLabel: _('customrequest.configs_urlparams'), 340 | description: _('customrequest.configs_urlparams_desc'), 341 | name: 'urlparams', 342 | id: this.ident + '-urlparams', 343 | anchor: '100%' 344 | }, { 345 | xtype: 'textarea', 346 | fieldLabel: _('customrequest.configs_regex'), 347 | description: _('customrequest.configs_regex_desc'), 348 | name: 'regex', 349 | id: this.ident + '-regex', 350 | anchor: '100%' 351 | }, { 352 | xtype: 'hidden', 353 | name: 'id', 354 | id: this.ident + '-id', 355 | }], 356 | listeners: { 357 | render: function (form) { 358 | Ext.create('Ext.tip.QuickTip', { 359 | target: form.getEl() 360 | }); 361 | } 362 | } 363 | }); 364 | CustomRequest.window.CreateUpdateConfig.superclass.constructor.call(this, config); 365 | }; 366 | Ext.extend(CustomRequest.window.CreateUpdateConfig, MODx.Window); 367 | Ext.reg('customrequest-window-config-create-update', CustomRequest.window.CreateUpdateConfig); 368 | -------------------------------------------------------------------------------- /source/js/mgr/widgets/home.panel.js: -------------------------------------------------------------------------------- 1 | CustomRequest.panel.Home = function (config) { 2 | config = config || {}; 3 | Ext.applyIf(config, { 4 | cls: 'container home-panel' + ((CustomRequest.config.debug) ? ' debug' : '') + ' modx' + CustomRequest.config.modxversion, 5 | defaults: { 6 | collapsible: false, 7 | autoHeight: true 8 | }, 9 | items: [{ 10 | html: '

' + _('customrequest') + '

' + ((CustomRequest.config.debug) ? '
' + _('customrequest.debug_mode') + '
' : ''), 11 | border: false, 12 | cls: 'modx-page-header' 13 | }, { 14 | defaults: { 15 | autoHeight: true 16 | }, 17 | border: true, 18 | cls: 'customrequest-panel', 19 | items: [{ 20 | xtype: 'customrequest-panel-overview' 21 | }] 22 | }, { 23 | cls: 'treehillstudio_about', 24 | html: 'Treehill Studio', 25 | listeners: { 26 | afterrender: function () { 27 | this.getEl().select('img').on('click', function () { 28 | var msg = 'Treehill Studio
' + 29 | '© 2013-2024 by treehillstudio.com
'; 30 | Ext.Msg.show({ 31 | title: _('customrequest') + ' ' + CustomRequest.config.version, 32 | msg: msg, 33 | buttons: Ext.Msg.OK, 34 | cls: 'treehillstudio_window', 35 | width: 358 36 | }); 37 | }); 38 | } 39 | } 40 | }] 41 | }); 42 | CustomRequest.panel.Home.superclass.constructor.call(this, config); 43 | }; 44 | Ext.extend(CustomRequest.panel.Home, MODx.Panel); 45 | Ext.reg('customrequest-panel-home', CustomRequest.panel.Home); 46 | 47 | CustomRequest.panel.HomeTab = function (config) { 48 | config = config || {}; 49 | Ext.applyIf(config, { 50 | id: 'customrequest-panel-' + config.tabtype, 51 | title: config.title, 52 | items: [{ 53 | html: '

' + config.description + '

', 54 | border: false, 55 | cls: 'panel-desc' 56 | }, { 57 | layout: 'form', 58 | cls: 'x-form-label-left main-wrapper', 59 | defaults: { 60 | autoHeight: true 61 | }, 62 | border: true, 63 | items: [{ 64 | id: 'customrequest-panel-' + config.tabtype + '-grid', 65 | xtype: 'customrequest-grid-' + config.tabtype, 66 | preventRender: true 67 | }] 68 | }] 69 | }); 70 | CustomRequest.panel.HomeTab.superclass.constructor.call(this, config); 71 | }; 72 | Ext.extend(CustomRequest.panel.HomeTab, MODx.Panel); 73 | Ext.reg('customrequest-panel-hometab', CustomRequest.panel.HomeTab); 74 | 75 | CustomRequest.panel.Overview = function (config) { 76 | config = config || {}; 77 | this.ident = 'customrequest-panel-overview-' + Ext.id(); 78 | this.panelOverviewTabs = [{ 79 | xtype: 'customrequest-panel-hometab', 80 | title: _('customrequest.configs'), 81 | description: _('customrequest.configs_desc') + '
' + _('customrequest.configs_desc_extended') + '
', 82 | tabtype: 'configs' 83 | }]; 84 | if (CustomRequest.config.is_admin) { 85 | this.panelOverviewTabs.push({ 86 | xtype: 'customrequest-panel-settings' 87 | }) 88 | } 89 | Ext.applyIf(config, { 90 | id: this.ident, 91 | items: [{ 92 | xtype: 'modx-tabs', 93 | border: true, 94 | stateful: true, 95 | stateId: 'customrequest-panel-overview', 96 | stateEvents: ['tabchange'], 97 | getState: function () { 98 | return { 99 | activeTab: this.items.indexOf(this.getActiveTab()) 100 | }; 101 | }, 102 | autoScroll: true, 103 | deferredRender: true, 104 | forceLayout: false, 105 | defaults: { 106 | layout: 'form', 107 | autoHeight: true, 108 | hideMode: 'offsets' 109 | }, 110 | items: this.panelOverviewTabs, 111 | listeners: { 112 | tabchange: function (o, t) { 113 | if (t.xtype === 'customrequest-panel-settings') { 114 | Ext.getCmp('customrequest-grid-system-settings').getStore().reload(); 115 | } else if (t.xtype === 'customrequest-panel-hometab') { 116 | if (Ext.getCmp('customrequest-panel-' + t.tabtype + '-grid')) { 117 | Ext.getCmp('customrequest-panel-' + t.tabtype + '-grid').getStore().reload(); 118 | } 119 | } 120 | } 121 | } 122 | }] 123 | }); 124 | CustomRequest.panel.Overview.superclass.constructor.call(this, config); 125 | }; 126 | Ext.extend(CustomRequest.panel.Overview, MODx.Panel); 127 | Ext.reg('customrequest-panel-overview', CustomRequest.panel.Overview); 128 | -------------------------------------------------------------------------------- /source/js/mgr/widgets/settings.panel.js: -------------------------------------------------------------------------------- 1 | if (MODx.grid.SettingsGrid) { 2 | CustomRequest.panel.Settings = function (config) { 3 | config = config || {}; 4 | MODx.request.ns = 'customrequest'; 5 | Ext.applyIf(config, { 6 | id: 'customrequest-panel-settings', 7 | title: _('customrequest.settings'), 8 | items: [{ 9 | html: '

' + _('customrequest.settings_desc') + '

', 10 | border: false, 11 | cls: 'panel-desc' 12 | }, { 13 | xtype: 'customrequest-grid-system-settings', 14 | id: 'customrequest-grid-system-settings', 15 | cls: 'main-wrapper', 16 | preventSaveRefresh: true 17 | }] 18 | }); 19 | CustomRequest.panel.Settings.superclass.constructor.call(this, config); 20 | }; 21 | Ext.extend(CustomRequest.panel.Settings, MODx.Panel); 22 | Ext.reg('customrequest-panel-settings', CustomRequest.panel.Settings); 23 | 24 | CustomRequest.grid.SystemSettings = function (config) { 25 | config = config || {}; 26 | Ext.applyIf(config, { 27 | id: 'customrequest-grid-systemsettings', 28 | url: CustomRequest.config.connectorUrl, 29 | baseParams: { 30 | action: 'mgr/settings/getlist', 31 | area: MODx.request.area || '' 32 | }, 33 | save_action: 'mgr/settings/updatefromgrid', 34 | tbar: [], 35 | queryParam: (CustomRequest.config.modxversion >= 3) ? 'query' : 'key' 36 | }); 37 | CustomRequest.grid.SystemSettings.superclass.constructor.call(this, config); 38 | }; 39 | Ext.extend(CustomRequest.grid.SystemSettings, MODx.grid.SettingsGrid, { 40 | _showMenu: function (g, ri, e) { 41 | e.stopEvent(); 42 | e.preventDefault(); 43 | this.menu.record = this.getStore().getAt(ri).data; 44 | if (!this.getSelectionModel().isSelected(ri)) { 45 | this.getSelectionModel().selectRow(ri); 46 | } 47 | this.menu.removeAll(); 48 | var m = []; 49 | if (this.menu.record.menu) { 50 | m = this.menu.record.menu; 51 | } else { 52 | m.push({ 53 | text: _('setting_update') || _('edit'), 54 | handler: this.updateSetting 55 | }); 56 | } 57 | if (m.length > 0) { 58 | this.addContextMenuItem(m); 59 | this.menu.showAt(e.xy); 60 | } 61 | }, 62 | updateSetting: function (btn, e) { 63 | var r = this.menu.record; 64 | r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; 65 | var uss = MODx.load({ 66 | xtype: 'modx-window-setting-update', 67 | url: CustomRequest.config.connectorUrl, 68 | action: 'mgr/settings/update', 69 | record: r, 70 | grid: this, 71 | listeners: { 72 | success: { 73 | fn: this.refresh, 74 | scope: this 75 | } 76 | } 77 | }); 78 | uss.reset(); 79 | uss.setValues(r); 80 | uss.show(e.target); 81 | }, 82 | clearFilter: function () { 83 | var area = MODx.request.area || ''; 84 | this.getStore().baseParams = this.initialConfig.baseParams; 85 | var filterArea = Ext.getCmp('modx-filter-area'); 86 | filterArea = filterArea || this.topToolbar.getComponent('filter-area'); 87 | if (filterArea) { 88 | filterArea.store.load(); 89 | filterArea.reset(); 90 | } 91 | var filterQuery = Ext.getCmp('modx-filter-' + this.config.queryParam) 92 | filterQuery = filterQuery || this.topToolbar.getComponent('filter-query'); 93 | if (filterQuery) { 94 | filterQuery.reset(); 95 | } 96 | this.getStore().baseParams.area = area; 97 | this.getStore().baseParams[this.config.queryParam] = ''; 98 | this.getBottomToolbar().changePage(1); 99 | }, 100 | filterByKey: function (tf, newValue) { 101 | this.getStore().baseParams[this.config.queryParam] = newValue; 102 | this.getBottomToolbar().changePage(1); 103 | return true; 104 | }, 105 | filterByNamespace: function () { 106 | this.getStore().baseParams.area = ''; 107 | this.getBottomToolbar().changePage(1); 108 | var filterArea = Ext.getCmp('modx-filter-area'); 109 | filterArea = filterArea || this.topToolbar.getComponent('filter-area'); 110 | if (filterArea) { 111 | var s = filterArea.store; 112 | s.removeAll(); 113 | s.load(); 114 | filterArea.setValue(''); 115 | } 116 | }, 117 | listeners: { 118 | afterrender: function (cmp) { 119 | var filterNamespace = Ext.getCmp('modx-filter-namespace'); 120 | filterNamespace = filterNamespace || cmp.topToolbar.getComponent('filter-ns'); 121 | if (filterNamespace) { 122 | filterNamespace.hide(); 123 | } 124 | } 125 | } 126 | }); 127 | Ext.reg('customrequest-grid-system-settings', CustomRequest.grid.SystemSettings); 128 | } 129 | -------------------------------------------------------------------------------- /source/sass/mgr/_modx3.scss: -------------------------------------------------------------------------------- 1 | $modx3-green: #6cb24a; 2 | $modx3-green-darken: #528738; 3 | $modx3-blue: #234368; 4 | 5 | @media screen and (min-width: 961px) { 6 | #modx-content > .x-panel-bwrap > .x-panel-body > .x-panel.modx3 { 7 | &.debug { 8 | margin-right: 15px; 9 | .modx-page-header { 10 | padding-right: 0; 11 | ~ .x-panel { 12 | margin-right: 0; 13 | } 14 | } 15 | } 16 | .treehillstudio_about { 17 | margin: 0 15px; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/sass/mgr/customrequest.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | 3 | $treehillstudio-color: #cbd842; 4 | $red: #be0000; 5 | 6 | .action-buttons { 7 | text-align: center; 8 | li { 9 | display: inline-block; 10 | font-size: 1.1em; 11 | line-height: .7; 12 | margin-right: 5px; 13 | &:last-child { 14 | margin-right: 0; 15 | } 16 | } 17 | } 18 | 19 | .modx-grid { 20 | .x-grid-cell-icons { 21 | .x-grid3-cell-inner { 22 | padding-right: 5px; 23 | } 24 | } 25 | } 26 | 27 | .treehillstudio_about { 28 | cursor: pointer; 29 | height: 40px; 30 | opacity: 0.25; 31 | transition: opacity 0.5s; 32 | margin-top: 20px; 33 | &:hover { 34 | opacity: 1; 35 | } 36 | .x-panel-body { 37 | text-align: right; 38 | } 39 | } 40 | 41 | .treehillstudio_window { 42 | a { 43 | color: color.adjust($treehillstudio-color, $lightness: -10%); 44 | text-decoration: none; 45 | &:hover { 46 | color: color.adjust($treehillstudio-color, $lightness: -25%); 47 | } 48 | } 49 | } 50 | 51 | .home-panel { 52 | &.debug { 53 | background: linear-gradient(to top right, transparent, transparent, transparent, #ffffff); 54 | } 55 | } 56 | 57 | .ribbon { 58 | height: 180px; 59 | overflow: hidden; 60 | pointer-events: none; 61 | position: absolute; 62 | width: 180px; 63 | z-index: 15; 64 | &:before, 65 | &:after { 66 | border: 5px solid color.adjust($red, $lightness: -15%); 67 | content: ''; 68 | display: block; 69 | position: absolute; 70 | z-index: -1; 71 | } 72 | &.top-right { 73 | right: -10px; 74 | top: -10px; 75 | &:before, 76 | &:after { 77 | border-right-color: transparent; 78 | border-top-color: transparent; 79 | } 80 | &:before { 81 | left: 0; 82 | top: 0; 83 | } 84 | &:after { 85 | bottom: 0; 86 | right: 0; 87 | } 88 | span { 89 | left: -46px; 90 | top: 46px; 91 | transform: rotate(45deg); 92 | } 93 | } 94 | span { 95 | background-color: $red; 96 | box-shadow: 0 5px 10px rgba(0, 0, 0, .1); 97 | color: #ffffff; 98 | display: block; 99 | font: 700 18px/1 'Lato', sans-serif; 100 | padding: 15px 0; 101 | position: absolute; 102 | text-align: center; 103 | text-shadow: 0 1px 1px rgba(0, 0, 0, .2); 104 | text-transform: uppercase; 105 | width: 300px; 106 | } 107 | } 108 | 109 | @import 'modx3'; 110 | --------------------------------------------------------------------------------