├── LICENSE ├── README.md ├── css ├── images │ ├── drag.png │ ├── gray-grad.png │ └── wpspin_light.gif ├── jquery-ui │ └── smoothness │ │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_888888_256x240.png │ │ └── ui-icons_cd0a0a_256x240.png │ │ └── jquery-ui-zoninator.css └── zoninator.css ├── functions.php ├── js ├── jquery.ui.touch-punch.min.js └── zoninator.js ├── language ├── zoninator-he_IL.mo └── zoninator-he_IL.po ├── rector.php ├── src ├── class-zoninator-api-controller.php ├── class-zoninator-api-filter-search.php ├── class-zoninator-api-schema-converter.php ├── class-zoninator-api.php ├── class-zoninator-zoneposts-widget.php ├── class-zoninator.php └── zoninator_rest │ ├── class-zoninator-rest-bootstrap.php │ ├── class-zoninator-rest-classloader.php │ ├── class-zoninator-rest-controller.php │ ├── class-zoninator-rest-environment.php │ ├── class-zoninator-rest-exception.php │ ├── class-zoninator-rest-expect.php │ ├── class-zoninator-rest-model.php │ ├── class-zoninator-rest-type.php │ ├── controller │ ├── bundle │ │ └── class-zoninator-rest-controller-bundle-builder.php │ ├── class-zoninator-rest-controller-action.php │ ├── class-zoninator-rest-controller-bundle.php │ ├── class-zoninator-rest-controller-crud.php │ ├── class-zoninator-rest-controller-extension.php │ ├── class-zoninator-rest-controller-model.php │ ├── class-zoninator-rest-controller-route.php │ └── class-zoninator-rest-controller-settings.php │ ├── data │ ├── class-zoninator-rest-data-mapper.php │ ├── class-zoninator-rest-data-serializer.php │ └── store │ │ ├── class-zoninator-rest-data-store-abstract.php │ │ ├── class-zoninator-rest-data-store-builder.php │ │ ├── class-zoninator-rest-data-store-customposttype.php │ │ ├── class-zoninator-rest-data-store-nil.php │ │ └── class-zoninator-rest-data-store-option.php │ ├── field │ ├── class-zoninator-rest-field-declaration.php │ └── declaration │ │ └── class-zoninator-rest-field-declaration-builder.php │ ├── interfaces │ ├── class-zoninator-rest-interfaces-builder.php │ ├── class-zoninator-rest-interfaces-classloader.php │ ├── class-zoninator-rest-interfaces-controller.php │ ├── class-zoninator-rest-interfaces-model.php │ ├── class-zoninator-rest-interfaces-registrable.php │ ├── class-zoninator-rest-interfaces-type.php │ ├── controller │ │ └── class-zoninator-rest-interfaces-controller-bundle.php │ ├── data │ │ └── class-zoninator-rest-interfaces-data-store.php │ ├── model │ │ ├── class-zoninator-rest-interfaces-model-collection.php │ │ └── class-zoninator-rest-interfaces-model-declaration.php │ └── permissions │ │ └── class-zoninator-rest-interfaces-permissions-provider.php │ ├── model │ ├── class-zoninator-rest-model-collection.php │ ├── class-zoninator-rest-model-declaration.php │ ├── class-zoninator-rest-model-definition.php │ ├── class-zoninator-rest-model-settings.php │ ├── class-zoninator-rest-model-validationdata.php │ ├── declaration │ │ └── class-zoninator-rest-model-declaration-settings.php │ └── definition │ │ └── class-zoninator-rest-model-definition-builder.php │ ├── permissions │ └── class-zoninator-rest-permissions-any.php │ └── type │ ├── class-zoninator-rest-type-array.php │ ├── class-zoninator-rest-type-boolean.php │ ├── class-zoninator-rest-type-integer.php │ ├── class-zoninator-rest-type-nullable.php │ ├── class-zoninator-rest-type-number.php │ ├── class-zoninator-rest-type-registry.php │ ├── class-zoninator-rest-type-string.php │ └── class-zoninator-rest-type-typedarray.php └── zoninator.php /README.md: -------------------------------------------------------------------------------- 1 | # Zone Manager (Zoninator) 2 | 3 | Stable tag: 0.10.1 4 | Requires at least: 5.9 5 | Tested up to: 6.6 6 | Requires PHP: 7.4 7 | License: GPLv2 or later 8 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 | Tags: zones, post order, post list, posts, order, zonination, content curation, curation, content management 10 | Contributors: batmoo, automattic, wpcomvip, pkevan, matthumphreys, potatomaster, jblz, nickdaugherty, betzster, garyj 11 | 12 | Content curation made easy! Create "zones" then add and order your content! 13 | 14 | ## Description 15 | 16 | This plugin is designed to help you curate your content. It lets you assign and order stories within zones that you create, edit, and delete, and display those groupings of related stories on your site. 17 | 18 | This plugin was originally built by [Mohammad Jangda](http://digitalize.ca) in conjunction with [William Davis](http://wpdavis.com/) and the [Bangor Daily News](http://www.bangordailynews.com/). 19 | 20 | ### Features 21 | 22 | * Add, edit, and delete zones. 23 | * Add and remove posts (or any custom post type) to or from zones. 24 | * Order posts in any given zone. 25 | * Limit capabilities on who can add, edit, and delete zones vs add content to zones. 26 | * Locking mechanism, so only one user can edit a zone at a time (to avoid conflicts). 27 | * Idle control, so people can't keep the zone locked. 28 | 29 | ## Installation 30 | 31 | 1. Unzip contents and upload to the `/wp-content/plugins/` directory 32 | 2. Activate the plugin through the 'Plugins' menu in WordPress 33 | 3. Go to Dashboard > Zones to create and manage your zones, and easily search for and add existing posts. 34 | 4. Use the plugin's handy API functions to add zones to your theme that retrieve and display your content. Or, for those who are a bit code-averse, go to Appearance-Widgets and add Zone Posts widgets to display your zone posts in your sidebar or footer. The widget will pull the posts from the chosen zone. 35 | 36 | ### Usage examples 37 | 38 | You can work with a zone's posts either as an array or a WP_Query object. 39 | 40 | **WP_Query** 41 | 42 | ~~~php 43 | $zone_query = z_get_zone_query( 'homepage' ); 44 | if ( $zone_query->have_posts() ) : 45 | while ( $zone_query->have_posts() ) : $zone_query->the_post(); 46 | echo '
  • ' . get_the_title() . '
  • '; 47 | endwhile; 48 | endif; 49 | wp_reset_query(); 50 | ~~~ 51 | 52 | **Posts Array** 53 | 54 | ~~~php 55 | $zone_posts = z_get_posts_in_zone( 'homepage' ); 56 | foreach ( $zone_posts as $zone_post ) : 57 | echo '
  • ' . get_the_title( $zone_post->ID ) . '
  • '; 58 | endforeach; 59 | ~~~ 60 | 61 | ## Function Reference 62 | 63 | Get an array of all zones: 64 | 65 | ~~~php 66 | z_get_zones() 67 | ~~~ 68 | 69 | Get a single zone, accepts either ID or slug: 70 | 71 | ~~~php 72 | z_get_zone( $zone ) 73 | ~~~ 74 | 75 | Get an array of ordered posts in a given zone, accepts either ID or slug: 76 | 77 | ~~~php 78 | z_get_posts_in_zone( $zone ) 79 | ~~~ 80 | 81 | Get a WP_Query object for a given zone, accepts either ID or slug: 82 | 83 | ~~~php 84 | z_get_zone_query( $zone ); 85 | ~~~ 86 | 87 | More functions listed in `functions.php`. 88 | 89 | ## Frequently Asked Questions 90 | 91 | ### How do I disable the locking feature? 92 | 93 | You can use a filter: 94 | 95 | ~~~php 96 | add_filter( 'zoninator_zone_max_lock_period', 'z_disable_zoninator_locks' ); 97 | ~~~ 98 | 99 | ### How do I change the locking feature settings? 100 | 101 | Filter the following and change according to your needs: 102 | 103 | * Number of seconds a lock is valid for, default `30`: `zoninator_zone_lock_period` 104 | * Max idle time in seconds: `zoninator_zone_max_lock_period` 105 | 106 | ## Changelog 107 | 108 | Please visit the [changelog](https://github.com/automattic/zoninator/blob/trunk/CHANGELOG.md). 109 | 110 | ## Screenshots 111 | 112 | 1. Create and manage your zones and content through a fairly intuitive and familiar interface. 113 | ![Overview of a homepage slideshow zone showing the name and description, and two posts assigned to that zone](.wordpress-org/screenshot-1.png) 114 | 2. Zone editing 115 | ![Editing a food zone in Zoninator](.wordpress-org/screenshot-2.png) 116 | 3. Use the Zone Posts widget in the widgets area. 117 | ![A sidebar widget area with a couple of Zone Posts widgets](.wordpress-org/screenshot-3.png) 118 | 4. Output of the zone posts widgets. 119 | ![Sidebar front end with the links to posts showing](.wordpress-org/screenshot-4.png) 120 | -------------------------------------------------------------------------------- /css/images/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/images/drag.png -------------------------------------------------------------------------------- /css/images/gray-grad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/images/gray-grad.png -------------------------------------------------------------------------------- /css/images/wpspin_light.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/images/wpspin_light.gif -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /css/jquery-ui/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/css/jquery-ui/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /css/zoninator.css: -------------------------------------------------------------------------------- 1 | .wrap.zoninator-page { 2 | width: 95%; 3 | } 4 | 5 | #zoninator-wrap { 6 | overflow: hidden; 7 | clear: both; 8 | position: relative; 9 | margin-top: 15px; 10 | } 11 | 12 | #zoninator-wrap .delete { 13 | color: #BC0B0B; 14 | } 15 | 16 | /** Tabs **/ 17 | .zone-tabs-container { 18 | width: 30%; 19 | float: left; 20 | } 21 | 22 | .zone-tabs-wrapper .zone-tab { 23 | display: block; 24 | background: #fff; 25 | float: none; 26 | font-size: 12px; 27 | line-height: 16px; 28 | padding: 4px 14px 6px; 29 | margin: 0px; 30 | font-weight: normal; 31 | } 32 | 33 | .zone-tabs-wrapper .zone-tab:hover, 34 | .zone-tabs-wrapper .zone-tab:active, 35 | .zone-tabs-wrapper .zone-tab-active { 36 | background: #0074a2; 37 | color: #fff; 38 | margin-bottom: -1px; 39 | } 40 | 41 | .nav-tab-active { 42 | float: none; 43 | } 44 | 45 | /** Edit **/ 46 | #zone-edit-wrapper { 47 | padding: 10px; 48 | overflow: hidden; 49 | border: 1px solid #ccc; 50 | -moz-border-radius: 4px; 51 | -wekbit-border-radius: 4px; 52 | -ms-border-radius: 4px; 53 | border-radius: 4px; 54 | background: url(images/gray-grad.png) repeat-x scroll left top #dfdfdf; 55 | 56 | width: 67%; 57 | float: right; 58 | } 59 | 60 | #zone-edit-wrapper .form-wrap { 61 | margin: 0; 62 | } 63 | 64 | #zone-edit-wrapper .form-field { 65 | padding: 0; 66 | } 67 | 68 | #zone-edit-wrapper .form-field.error { 69 | padding: 0 10px 5px; 70 | } 71 | 72 | #zone-edit-wrapper .form-field input, 73 | #zone-edit-wrapper .form-field textarea { 74 | width: 98%; 75 | } 76 | 77 | #zone-edit-wrapper .submit-field { 78 | width: 95%; 79 | } 80 | 81 | #zone-edit-wrapper .submit-field .submitdelete { 82 | float: right; 83 | } 84 | 85 | #zone-info-readonly label { 86 | font-weight: bold; 87 | } 88 | 89 | #zone-info-readonly span { 90 | } 91 | 92 | /** Zone Posts **/ 93 | .zone-info-col { 94 | border-bottom: 1px dotted #333; 95 | padding-bottom: 15px; 96 | margin-bottom: 15px; 97 | } 98 | 99 | .zone-posts-col { 100 | background: #fff; 101 | border: 1px solid #ccc; 102 | -moz-border-radius: 4px; 103 | -wekbit-border-radius: 4px; 104 | -ms-border-radius: 4px; 105 | border-radius: 4px; 106 | 107 | padding: 10px; 108 | } 109 | 110 | .zone-posts-col h3 { 111 | margin: 3px 0; 112 | } 113 | 114 | .zone-posts-wrapper.loading { 115 | background: url(images/wpspin_light.gif) no-repeat right top; 116 | } 117 | 118 | .zone-posts-wrapper.readonly { 119 | } 120 | 121 | .zone-posts-wrapper.readonly .zone-search-wrapper, 122 | .zone-posts-wrapper.readonly .row-actions { 123 | display: none !important; 124 | } 125 | 126 | .zone-posts-wrapper.readonly .zone-post { 127 | cursor: default !important; 128 | } 129 | 130 | .zone-posts-wrapper.readonly .zone-post:hover .zone-post-position { 131 | background: none; 132 | text-indent: 0; 133 | } 134 | 135 | .zone-posts-list { 136 | margin: 10px 0; 137 | } 138 | 139 | .zone-posts-list .ui-state-highlight { 140 | margin-bottom: 5px; 141 | -moz-border-radius: 4px; 142 | -wekbit-border-radius: 4px; 143 | -ms-border-radius: 4px; 144 | border-radius: 4px; 145 | } 146 | 147 | .zone-posts-list.ui-sortable .zone-post { 148 | cursor: move; 149 | } 150 | 151 | .zone-post { 152 | padding: 5px 0 5px 10px; 153 | margin-bottom: 5px; 154 | font-size: 12px; 155 | line-height: 15px; 156 | font-weight: bold; 157 | color: #444; 158 | overflow: hidden; 159 | 160 | border: 1px dotted #eee; 161 | -moz-border-radius: 4px; 162 | -wekbit-border-radius: 4px; 163 | -ms-border-radius: 4px; 164 | border-radius: 4px; 165 | 166 | background: #f5f5f5; 167 | } 168 | 169 | .zone-post table { 170 | width: 100%; 171 | } 172 | 173 | .zone-post-col { 174 | } 175 | 176 | .zone-post:hover .zone-post-position { 177 | background: url(images/drag.png) no-repeat left center; 178 | text-indent: -9999px; 179 | } 180 | 181 | .zone-post-position { 182 | font-size: 24px; 183 | line-height: 32px; 184 | font-weight: normal; 185 | text-align: center; 186 | width: 32px; 187 | } 188 | 189 | /* 190 | .zone-post-handle { 191 | display: block; 192 | background: url(images/drag.png) no-repeat center center; 193 | width: 32px; 194 | height: 32px; 195 | 196 | float: right; 197 | visibility: hidden; 198 | } 199 | */ 200 | .zone-post-status { 201 | color: #999; 202 | } 203 | 204 | .zone-post .row-actions { 205 | font-size: 11px; 206 | font-weight: normal; 207 | } 208 | 209 | .zone-post:hover .row-actions, 210 | .zone-post.loading .row-actions, 211 | .zone-post:hover .zone-post-handle, 212 | .zone-post.loading .zone-post-handle { 213 | visibility: visible; 214 | } 215 | 216 | .zone-post.loading .zone-post-position { 217 | background: url(images/wpspin_light.gif) no-repeat center center; 218 | text-indent: -9999px; 219 | } 220 | 221 | .zone-search-wrapper { 222 | padding: 7px 0; 223 | border-bottom: 1px dotted #999; 224 | } 225 | 226 | .zone-posts-save-input { 227 | padding: 7px 0; 228 | overflow: hidden; 229 | } 230 | 231 | #zone-posts-save { 232 | float: left; 233 | width: 125px; 234 | } 235 | 236 | .zone-posts-save-info { 237 | float: left; 238 | margin: 0 0 0 10px; 239 | padding: 0 10px; 240 | line-height: 28px; 241 | color: #666; 242 | } 243 | 244 | .notice-error { color: #a94442; background-color: #f2dede; } 245 | 246 | .notice-success { color: #3c763d; background-color: #dff0d8; } 247 | 248 | .notice-info { color: #0f1d79; background-color: #d9edf7; } 249 | 250 | #zone-post-search { 251 | width: 99%; 252 | } 253 | 254 | #zone-post-latest { 255 | width: 98%; 256 | } 257 | 258 | /** Autocomplete **/ 259 | .ui-autocomplete-input.loading { 260 | background: url(images/wpspin_light.gif) no-repeat right center; 261 | } 262 | 263 | .ui-autocomplete { 264 | min-width: 330px; 265 | max-width: 600px; 266 | font-size: 13px; 267 | line-height: 17px; 268 | } 269 | 270 | .ui-autocomplete .ui-corner-all { 271 | padding-top: 5px; 272 | padding-bottom: 5px; 273 | border-bottom: 1px dotted #eee; 274 | cursor: pointer; 275 | overflow: hidden; 276 | } 277 | 278 | .ui-autocomplete .title { 279 | display: block; 280 | width: 80%; 281 | float: left; 282 | } 283 | 284 | .ui-autocomplete .date { 285 | text-transform: uppercase; 286 | color: #666; 287 | font-size: 11px; 288 | clear: both; 289 | float: left; 290 | } 291 | 292 | .ui-autocomplete .type { 293 | text-transform: uppercase; 294 | color: #666; 295 | font-size: 11px; 296 | width: 19%; 297 | margin-left: 1%; 298 | float: right; 299 | text-align: right; 300 | } 301 | 302 | .ui-autocomplete .status { 303 | font-size: 10px; 304 | width: 19%; 305 | margin-left: 1%; 306 | float: right; 307 | text-align: right; 308 | } 309 | 310 | .zone-advanced-search-filters-wrapper { 311 | display: none; 312 | } 313 | 314 | .zone-advanced-search-filters-heading { 315 | float: right; 316 | font-weight: bold; 317 | } 318 | 319 | .zone-toggle-advanced-search { 320 | cursor: pointer; 321 | } 322 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | get_zones(); 16 | } 17 | 18 | /** 19 | * @param $zone int|string ID or Slug of the zone 20 | * @return array Zone object 21 | */ 22 | function z_get_zone( $zone ) { 23 | return z_get_zoninator()->get_zone( $zone ); 24 | } 25 | 26 | /** 27 | * @param $zone int|string ID or Slug of the zone 28 | * @param $args array override default zoninator args 29 | * @return array List of orders post objects 30 | */ 31 | function z_get_posts_in_zone( $zone, $args = array() ) { 32 | return z_get_zoninator()->get_zone_posts( $zone, $args ); 33 | } 34 | 35 | /** 36 | * @param $zone int|string ID or Slug of the zone 37 | * @return WP_Query List of orders post objects 38 | */ 39 | function z_get_zone_query( $zone, $args = array() ) { 40 | return z_get_zoninator()->get_zone_query( $zone, $args ); 41 | } 42 | 43 | /** 44 | * @param $zone int|string ID or Slug of the zone 45 | * @param $post_id int ID of the post (or, null if in The Loop) 46 | * @return array|false Returns next post relative to post_id for the given zone 47 | */ 48 | function z_get_next_post_in_zone( $zone, $post_id = 0 ) { 49 | $post_id = z_get_loop_post_id_or_default( $post_id ); 50 | return z_get_zoninator()->get_next_post_in_zone( $zone, $post_id ); 51 | } 52 | 53 | /** 54 | * @param $zone int|string ID or Slug of the zone 55 | * @param $post_id int ID of the post (or, null if in The Loop) 56 | * @return array|false Returns previous post relative to post_id for the given zone 57 | */ 58 | function z_get_prev_post_in_zone( $zone, $post_id = 0 ) { 59 | $post_id = z_get_loop_post_id_or_default( $post_id ); 60 | return z_get_zoninator()->get_prev_post_in_zone( $zone, $post_id ); 61 | } 62 | 63 | /** 64 | * @param $post_id int ID of the post (or, null if in The Loop) 65 | * @return array List of of zones that the given post is in 66 | */ 67 | function z_get_post_zones( $post_id = 0 ) { 68 | $post_id = z_get_loop_post_id_or_default( $post_id ); 69 | return z_get_zoninator()->get_zones_for_post( $post_id ); 70 | } 71 | 72 | function z_get_loop_post_id_or_default( $post_id = 0 ) { 73 | if ( ! $post_id ) { 74 | global $post; 75 | if ( $post && isset( $post->ID ) ) { 76 | $post_id = $post->ID; 77 | } 78 | } 79 | 80 | return $post_id; 81 | } 82 | 83 | /** 84 | * Handy function to disable the locking mechanism 85 | */ 86 | function z_disable_zoninator_locks() { 87 | return -1; 88 | } 89 | 90 | // (Should probably publicly expose set_zone_posts as well, e.g. if we wanted to add a metabox on the Edit Post page) 91 | -------------------------------------------------------------------------------- /js/jquery.ui.touch-punch.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Touch Punch 0.2.2 3 | * 4 | * Copyright 2011, Dave Furfero 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Depends: 8 | * jquery.ui.widget.js 9 | * jquery.ui.mouse.js 10 | */ 11 | (function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return;}var c=b.ui.mouse.prototype,e=c._mouseInit,a;function d(g,h){if(g.originalEvent.touches.length>1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery); 12 | -------------------------------------------------------------------------------- /language/zoninator-he_IL.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/zoninator/7352d0572052bcae506781c3b41d89970ef044d3/language/zoninator-he_IL.mo -------------------------------------------------------------------------------- /language/zoninator-he_IL.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: Zoninator 1.0.0\n" 4 | "Report-Msgid-Bugs-To: http://pojo.me/\n" 5 | "POT-Creation-Date: 2014-06-09 17:04+0200\n" 6 | "PO-Revision-Date: 2014-06-09 17:13+0200\n" 7 | "Last-Translator: Ariel K \n" 8 | "Language-Team: Pojo.me Team \n" 9 | "Language: he_IL\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;" 14 | "_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;" 15 | "esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n" 16 | "X-Poedit-Basepath: .\n" 17 | "X-Poedit-SourceCharset: UTF-8\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 1.5.5\n" 20 | "X-Poedit-SearchPath-0: ..\n" 21 | 22 | #: ../widget.zone-posts.php:11 23 | msgid "Use this widget to display a list of posts from any zone." 24 | msgstr "יש להשתמש בוידג'ט הזה כדי להציג רשימת פוסטים מאזור כלשהו." 25 | 26 | #: ../widget.zone-posts.php:21 27 | msgid "Zone Posts" 28 | msgstr "אזור פוסטים" 29 | 30 | #: ../widget.zone-posts.php:99 31 | msgid "You need to create at least one zone before you use this widget!" 32 | msgstr "צריך ליצור אזור אחד לפחות לפני שניתן להשתמש בוידג'ט זה!" 33 | 34 | #: ../widget.zone-posts.php:108 35 | msgid "Zone:" 36 | msgstr "אזור:" 37 | 38 | #: ../widget.zone-posts.php:111 39 | msgid "-- Select a zone --" 40 | msgstr "--בחירת אזור--" 41 | 42 | #: ../widget.zone-posts.php:125 43 | msgid "Show zone description in widget" 44 | msgstr "להציג תיאור אזור בוידג'ט" 45 | 46 | #: ../zoninator.php:81 47 | msgid "The zone was successfully created." 48 | msgstr "האזור נוצר בהצלחה." 49 | 50 | #: ../zoninator.php:82 51 | msgid "The zone was successfully updated." 52 | msgstr "אזור עודכן בהצלחה." 53 | 54 | #: ../zoninator.php:83 55 | msgid "The zone was successfully deleted." 56 | msgstr "אזור נמחק בהצלחה" 57 | 58 | #: ../zoninator.php:84 59 | msgid "Sorry, something went wrong! Please try again?" 60 | msgstr "מצטערים, משהו פה שגוי! יש לנסות שוב." 61 | 62 | #: ../zoninator.php:85 63 | #, php-format 64 | msgid "" 65 | "Sorry, this zone is in use by %s and is currently locked. Please try again " 66 | "later." 67 | msgstr "" 68 | "מצטערים, אזור זה נמצא בשימוש על ידי %s ולכן הוא נעול ברגע זה. יש לנסות שוב " 69 | "מאוחר יותר." 70 | 71 | #: ../zoninator.php:86 72 | msgid "" 73 | "Sorry, you have reached the maximum idle limit and will now be redirected to " 74 | "the Dashboard." 75 | msgstr "מצטערים, המערכת לא זיהתה פעילות לאחרונה, הינך מופנה אל לוח הבקרה." 76 | 77 | #: ../zoninator.php:102 ../zoninator.php:153 ../zoninator.php:258 78 | msgid "Zones" 79 | msgstr "אזורים" 80 | 81 | #: ../zoninator.php:153 82 | msgid "Zoninator" 83 | msgstr "ניהול אזורים" 84 | 85 | #: ../zoninator.php:165 86 | msgid "another user" 87 | msgstr "משתמש אחר" 88 | 89 | #: ../zoninator.php:271 90 | msgid "Edit Zone" 91 | msgstr "עריכת אזור" 92 | 93 | #: ../zoninator.php:284 ../zoninator.php:286 94 | msgid "Add New" 95 | msgstr "הוספת חדש" 96 | 97 | #: ../zoninator.php:368 ../zoninator.php:411 98 | msgid "Name" 99 | msgstr "שם" 100 | 101 | #: ../zoninator.php:374 ../zoninator.php:417 102 | msgid "Slug" 103 | msgstr "מזהה" 104 | 105 | #: ../zoninator.php:381 ../zoninator.php:423 106 | msgid "Description" 107 | msgstr "תיאור" 108 | 109 | #: ../zoninator.php:395 110 | msgid "Save" 111 | msgstr "שמירה" 112 | 113 | #: ../zoninator.php:398 114 | msgid "Delete" 115 | msgstr "מחיקה" 116 | 117 | #: ../zoninator.php:442 118 | msgid "Zone Content" 119 | msgstr "תוכן אזור" 120 | 121 | #: ../zoninator.php:457 122 | msgid "" 123 | "To create a zone, enter a name (and any other info) to to left and click " 124 | "\"Save\". You can then choose content items to add to the zone." 125 | msgstr "" 126 | "כדי ליצור אזור, יש להזין שם (ומידע נוסף אם צריך) וללחוץ על \"שמירה\". לאחר " 127 | "מכן ניתן לבחור פריטי תוכן ולהוסיף לאזור." 128 | 129 | #: ../zoninator.php:493 130 | msgid "Click and drag to change the position of this item." 131 | msgstr "יש ללחוץ ולגרור כדי לשנות את המיקום של פריט זה." 132 | 133 | #: ../zoninator.php:500 ../zoninator.php:502 134 | msgid "Opens in new window" 135 | msgstr "פתיחה בלשונית חדשה" 136 | 137 | #: ../zoninator.php:500 138 | msgid "Edit" 139 | msgstr "עריכה" 140 | 141 | #: ../zoninator.php:501 142 | msgid "Remove this item from the zone" 143 | msgstr "למחוק פריט זה מהאזור" 144 | 145 | #: ../zoninator.php:501 146 | msgid "Remove" 147 | msgstr "הסרה" 148 | 149 | #: ../zoninator.php:502 150 | msgid "View" 151 | msgstr "תצוגה" 152 | 153 | #: ../zoninator.php:518 154 | msgid "Hide" 155 | msgstr "להסתיר" 156 | 157 | #: ../zoninator.php:518 158 | msgid "Show Filters" 159 | msgstr "להציג סינון" 160 | 161 | #: ../zoninator.php:529 162 | msgid "Filter:" 163 | msgstr "סינון:" 164 | 165 | #: ../zoninator.php:532 166 | msgid "Show all Categories" 167 | msgstr "הצגת כל הקטגוריות" 168 | 169 | #: ../zoninator.php:609 170 | msgid "No results found" 171 | msgstr "לא נמצאו תוצאות" 172 | 173 | #: ../zoninator.php:611 174 | #, php-format 175 | msgid "Choose post from %s" 176 | msgstr "לבחור פוסט מ %s" 177 | 178 | #: ../zoninator.php:613 ../zoninator.php:645 179 | msgid "Choose a post" 180 | msgstr "בחירת פוסט" 181 | 182 | #: ../zoninator.php:643 183 | msgid "Add Recent Content" 184 | msgstr "להוסיף תוכן אחרון" 185 | 186 | #: ../zoninator.php:660 187 | msgid "Search for content" 188 | msgstr "חיפוש בתוכן" 189 | 190 | #: ../zoninator.php:662 191 | msgid "" 192 | "Enter a term or phrase in the text box above to search for and add content " 193 | "to this zone." 194 | msgstr "יש להזין מונח או ביטוי בתיבת הטקסט כדי לחפש ולהוסיף תוכן לאזור זה." 195 | 196 | #: ../zoninator.php:835 197 | msgid "(no title)" 198 | msgstr "(ללא כותרת)" 199 | 200 | #: ../zoninator.php:881 201 | msgid "Slug is a required field." 202 | msgstr "מזהה פוסט הינו שדה חובה" 203 | 204 | #: ../zoninator.php:926 ../zoninator.php:949 205 | msgid "Sorry, that zone doesn't exist." 206 | msgstr "מצטערים, אזור זה לא קיים." 207 | 208 | #: ../zoninator.php:943 209 | msgid "Sorry, we couldn't delete the zone." 210 | msgstr "מצטערים, לא ניתן למחוק אזור זה." 211 | 212 | #: ../zoninator.php:1306 213 | msgid "Sorry, you're not supposed to do that..." 214 | msgstr "מצטערים, איך לך הרשאות לעשות זאת..." 215 | 216 | #: ../zoninator.php:1340 217 | msgid "Invalid zone supplied" 218 | msgstr "הוזן אזור לא תקין" 219 | 220 | #: ../zoninator.php:1346 221 | msgid "No zone posts found" 222 | msgstr "לא נמצא אזור פוסטים" 223 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths( 22 | array( 23 | __DIR__ . '/src', 24 | __DIR__ . '/tests', 25 | __DIR__ . '/functions.php', 26 | __DIR__ . '/zoninator.php', 27 | ) 28 | ) 29 | ->withSkip( 30 | array( 31 | LongArrayToShortArrayRector::class, 32 | // Need a better understanding of intent in this file before switching out the use of empty(). 33 | DisallowedEmptyRuleFixerRector::class => array( 34 | __DIR__ . '/src/zoninator_rest/type/class-zoninator-rest-type-registry.php', 35 | ), 36 | // Child classes can legitimately relax the visibility of a method, so while this 37 | // might not be desirable, changing them from public to the original parent-defined 38 | // protected would count as a breaking change. 39 | MakeInheritedMethodVisibilitySameAsParentRector::class, 40 | ) 41 | ) 42 | ->withPhpSets( php74: true ) 43 | // Changes from later PHP Sets that are backwards compatible: 44 | ->withRules( 45 | array( 46 | // 8.0 47 | ConsistentImplodeRector::class, 48 | OptionalParametersAfterRequiredRector::class, 49 | RemoveParentCallWithoutParentRector::class, 50 | // Backfilled from PHP 8.0 into WP 5.9, so these can be used. 51 | StrContainsRector::class, 52 | StrEndsWithRector::class, 53 | StrStartsWithRector::class, 54 | 55 | // 8.1 56 | NullToStrictStringFuncCallArgRector::class, 57 | 58 | // 8.2 59 | VariableInStringInterpolationFixerRector::class, 60 | 61 | // 8.3 62 | AddOverrideAttributeToOverriddenMethodsRector::class, 63 | 64 | // 8.4 65 | ExplicitNullableParamTypeRector::class, 66 | ) 67 | ) 68 | ->withPreparedSets( deadCode: true, codeQuality: true, instanceOf: true, codingStyle: true ) 69 | ->withTypeCoverageLevel( 1 ); 70 | -------------------------------------------------------------------------------- /src/class-zoninator-api-controller.php: -------------------------------------------------------------------------------- 1 | [\d]+)'; 11 | 12 | public const ZONE_ITEM_POSTS_URL_REGEX = '/zones/(?P[\d]+)/posts'; 13 | 14 | public const ZONE_ITEM_POSTS_POST_REGEX = '/zones/(?P[\d]+)/posts/(?P\d+)'; 15 | 16 | public const INVALID_ZONE_ID = 'invalid-zone-id'; 17 | 18 | public const INVALID_POST_ID = 'invalid-post-id'; 19 | 20 | public const ZONE_ID_POST_ID_REQUIRED = 'zone-id-post-id-required'; 21 | 22 | public const ZONE_ID_POST_IDS_REQUIRED = 'zone-id-post-ids-required'; 23 | 24 | public const ZONE_ID_REQUIRED = 'zone-id-required'; 25 | 26 | public const ZONE_FEED_ERROR = 'zone-feed-error'; 27 | 28 | public const TERM_REQUIRED = 'term-required'; 29 | 30 | public const PERMISSION_DENIED = 'permission-denied'; 31 | 32 | public const ZONE_NOT_FOUND = 'zone-not-found'; 33 | 34 | public const POST_NOT_FOUND = 'post-not-found'; 35 | 36 | public const INVALID_ZONE_SETTINGS = 'invalid-zone-settings'; 37 | 38 | /** 39 | * Instance 40 | * 41 | * @var Zoninator 42 | */ 43 | private $instance; 44 | 45 | /** 46 | * Key Value Translation array 47 | * 48 | * @var array 49 | */ 50 | private $translations; 51 | 52 | /** 53 | * Zoninator_Api_Controller constructor. 54 | * 55 | * @param string $base Base. 56 | * @param Zoninator $instance Instance. 57 | */ 58 | public function __construct( $instance ) { 59 | $this->instance = $instance; 60 | $this->base = '/'; 61 | } 62 | 63 | /** 64 | * Set up this controller 65 | */ 66 | public function setup() { 67 | $this->translations = array( 68 | self::ZONE_NOT_FOUND => __( 'Zone not found', 'zoninator' ), 69 | self::INVALID_POST_ID => __( 'Invalid post id', 'zoninator' ), 70 | self::INVALID_ZONE_ID => __( 'Zone not found', 'zoninator' ), 71 | self::ZONE_ID_POST_ID_REQUIRED => __( 'post id and zone id required', 'zoninator' ), 72 | ); 73 | 74 | $this->add_route( 'zones' ) 75 | ->add_action( 76 | $this->action( 'index', 'get_zones' ) 77 | ->permissions( 'get_zones_permissions_check' ) 78 | ) 79 | ->add_action( 80 | $this->action( 'create', 'create_zone' ) 81 | ->permissions( 'add_zone_permissions_check' ) 82 | ->args( '_params_for_create_zone' ) 83 | ); 84 | 85 | $this->add_route( 'zones/(?P[\d]+)' ) 86 | ->add_action( 87 | $this->action( 'update', 'update_zone' ) 88 | ->permissions( 'update_zone_permissions_check' ) 89 | ->args( '_params_for_update_zone' ) 90 | ) 91 | ->add_action( 92 | $this->action( 'delete', 'delete_zone' ) 93 | ->permissions( 'update_zone_permissions_check' ) 94 | ); 95 | 96 | $this->add_route( 'zones/(?P[\d]+)/posts' ) 97 | ->add_action( 98 | $this->action( 'index', 'get_zone_posts' ) 99 | ->permissions( 'get_zone_posts_permissions_check' ) 100 | ->args( '_get_zone_id_param' ) 101 | ) 102 | ->add_action( 103 | $this->action( 'update', 'update_zone_posts' ) 104 | ->permissions( 'update_zone_permissions_check' ) 105 | ->args( '_get_zone_post_rest_route_params' ) 106 | ); 107 | 108 | $this->add_route( 'zones/(?P[\d]+)/lock' ) 109 | ->add_action( 110 | $this->action( 'update', 'zone_update_lock' ) 111 | ->permissions( 'update_zone_permissions_check' ) 112 | ->args( '_get_zone_id_param' ) 113 | ); 114 | } 115 | 116 | /** 117 | * Get the list of all zones 118 | * 119 | * @param WP_REST_Request $request Full data about the request. 120 | * @return WP_Error|WP_REST_Response 121 | */ 122 | public function get_zones( $request ) { 123 | $results = $this->instance->get_zones(); 124 | 125 | if ( is_wp_error( $results ) ) { 126 | return $this->bad_request( 127 | array( 128 | 'message' => $results->get_error_message(), 129 | ) 130 | ); 131 | } 132 | 133 | $zones = array_map( array( $this, '_filter_zone_properties' ), $results ); 134 | 135 | return $this->ok( $zones ); 136 | } 137 | 138 | /** 139 | * Create a Zone 140 | * 141 | * @param WP_REST_Request $request Full data about the request. 142 | * @return WP_Error|WP_REST_Response 143 | */ 144 | public function create_zone( $request ) { 145 | $name = $this->get_param( $request, 'name', '' ); 146 | $slug = $this->get_param( $request, 'slug', $name ); 147 | $description = $this->get_param( $request, 'description', '' ); 148 | 149 | $result = $this->instance->insert_zone( 150 | $slug, 151 | $name, 152 | array( 153 | 'description' => $description, 154 | ) 155 | ); 156 | 157 | if ( is_wp_error( $result ) ) { 158 | return $this->bad_request( 159 | array( 160 | 'message' => $result->get_error_message(), 161 | ) 162 | ); 163 | } 164 | 165 | $zone = $this->instance->get_zone( $result['term_id'] ); 166 | 167 | return $this->created( $this->_filter_zone_properties( $zone ) ); 168 | } 169 | 170 | /** 171 | * Update zone details 172 | * 173 | * @param WP_REST_Request $request Full data about the request. 174 | * @return WP_Error|WP_REST_Response 175 | */ 176 | public function update_zone( $request ) { 177 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 178 | $name = $this->get_param( $request, 'name', '' ); 179 | $slug = $this->get_param( $request, 'slug', '' ); 180 | $description = $this->get_param( $request, 'description', '', 'strip_tags' ); 181 | 182 | $zone = $this->instance->get_zone( $zone_id ); 183 | $update_params = array(); 184 | 185 | if ( ! $zone ) { 186 | return $this->not_found( $this->translations[ self::INVALID_ZONE_ID ] ); 187 | } 188 | 189 | if ( $name ) { 190 | $update_params['name'] = $name; 191 | } 192 | 193 | if ( $slug ) { 194 | $update_params['slug'] = $slug; 195 | } 196 | 197 | if ( $description ) { 198 | $update_params['details'] = array( 'description' => $description ); 199 | } 200 | 201 | $result = $this->instance->update_zone( $zone, $update_params ); 202 | 203 | if ( is_wp_error( $result ) ) { 204 | return $this->bad_request( 205 | array( 206 | 'message' => $result->get_error_message(), 207 | ) 208 | ); 209 | } 210 | 211 | return $this->ok( array( 'success' => true ) ); 212 | } 213 | 214 | /** 215 | * Delete a zone 216 | * 217 | * @param WP_REST_Request $request Full data about the request. 218 | * @return WP_Error|WP_REST_Response 219 | */ 220 | public function delete_zone( $request ) { 221 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 222 | 223 | $zone = $this->instance->get_zone( $zone_id ); 224 | 225 | if ( ! $zone ) { 226 | return $this->not_found( $this->translations[ self::INVALID_ZONE_ID ] ); 227 | } 228 | 229 | $result = $this->instance->delete_zone( $zone ); 230 | 231 | if ( is_wp_error( $result ) ) { 232 | return $this->bad_request( 233 | array( 234 | 'message' => $result->get_error_message(), 235 | ) 236 | ); 237 | } 238 | 239 | return $this->ok( array( 'success' => true ) ); 240 | } 241 | 242 | /** 243 | * Get zone posts 244 | * 245 | * @param WP_REST_Request $request Full data about the request. 246 | * @return WP_Error|WP_REST_Response 247 | */ 248 | public function get_zone_posts( $request ) { 249 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 250 | 251 | if ( empty( $zone_id ) || false === $this->instance->get_zone( $zone_id ) ) { 252 | return $this->not_found( $this->translations[ self::INVALID_ZONE_ID ] ); 253 | } 254 | 255 | $results = $this->instance->get_zone_feed( $zone_id ); 256 | 257 | if ( is_wp_error( $results ) ) { 258 | return $this->_bad_request( self::ZONE_FEED_ERROR, $results->get_error_message() ); 259 | } 260 | 261 | return new WP_REST_Response( $results, 200 ); 262 | } 263 | 264 | /** 265 | * Sets the posts for a zone 266 | * 267 | * @param WP_REST_Request $request Full data about the request.] 268 | * @return WP_Error|WP_REST_Response 269 | */ 270 | public function update_zone_posts( $request ) { 271 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 272 | $post_ids = $this->get_param( $request, 'post_ids', array() ); 273 | 274 | if ( ! $this->instance->get_zone( $zone_id ) ) { 275 | return $this->not_found( $this->translations[ self::INVALID_ZONE_ID ] ); 276 | } 277 | 278 | $posts = array_map( 'get_post', $post_ids ); 279 | 280 | if ( count( $posts ) !== count( array_filter( $posts ) ) ) { 281 | return $this->bad_request( 282 | array( 283 | 'message' => $this->translations[ self::INVALID_POST_ID ], 284 | ) 285 | ); 286 | } 287 | 288 | $result = $this->instance->add_zone_posts( $zone_id, $posts ); 289 | 290 | if ( is_wp_error( $result ) ) { 291 | return $this->respond( $result, 500 ); 292 | } 293 | 294 | return $this->ok( array( 'success' => true ) ); 295 | } 296 | 297 | /** 298 | * Update the zone's lock 299 | * 300 | * @param WP_REST_Request $request Full data about the request. 301 | * @return WP_Error|WP_REST_Response 302 | */ 303 | public function zone_update_lock( $request ) { 304 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 305 | if ( ! $zone_id ) { 306 | return $this->_bad_request( self::ZONE_ID_REQUIRED, __( 'zone id required', 'zoninator' ) ); 307 | } 308 | 309 | $zone = $this->instance->get_zone( $zone_id ); 310 | if ( ! $zone ) { 311 | return $this->not_found( $this->translations[ self::INVALID_ZONE_ID ] ); 312 | } 313 | 314 | $zone_locked = $this->instance->is_zone_locked( $zone ); 315 | if ( $zone_locked ) { 316 | $locking_user = get_userdata( $zone_locked ); 317 | return new WP_REST_Response( 318 | array( 319 | 'zone_id' => $this->instance->get_zone_id( $zone ), 320 | 'blocked' => true, 321 | ), 322 | 400 323 | ); 324 | } 325 | 326 | $this->instance->lock_zone( $zone_id ); 327 | return new WP_REST_Response( 328 | array( 329 | 'zone_id' => $this->instance->get_zone_id( $zone ), 330 | 'timeout' => $this->instance->zone_lock_period, 331 | 'max_lock_period' => $this->instance->zone_max_lock_period, 332 | ), 333 | 200 334 | ); 335 | } 336 | 337 | /** 338 | * Check if a given request has access to the zones index. 339 | * 340 | * @param WP_REST_Request $request Full data about the request. 341 | * @return WP_Error|bool 342 | */ 343 | public function get_zones_permissions_check( $request ) { 344 | return true; 345 | } 346 | 347 | /** 348 | * Check if a given request has access to add new zones. 349 | * 350 | * @param WP_REST_Request $request Full data about the request. 351 | * @return WP_Error|bool 352 | */ 353 | public function add_zone_permissions_check( $request ) { 354 | return $this->_permissions_check( 'insert' ); 355 | } 356 | 357 | /** 358 | * Check if a given request has access to get zone posts. 359 | * 360 | * @param WP_REST_Request $request Full data about the request. 361 | * @return WP_Error|bool 362 | */ 363 | public function get_zone_posts_permissions_check( $request ) { 364 | return true; 365 | } 366 | 367 | /** 368 | * Check if a given request has access to update a zone. 369 | * 370 | * @param WP_REST_Request $request Full data about the request. 371 | * @return WP_Error|bool 372 | */ 373 | public function update_zone_permissions_check( $request ) { 374 | $zone_id = $this->get_param( $request, 'zone_id', 0, 'absint' ); 375 | return $this->_permissions_check( 'update', $zone_id ); 376 | } 377 | 378 | public function is_numeric( $item ) { 379 | // see https://github.com/WP-API/WP-API/issues/1520 on why we do not use is_numeric directly. 380 | return is_numeric( $item ); 381 | } 382 | 383 | public function is_numeric_array( $items ) { 384 | return count( $items ) === count( array_filter( $items, 'is_numeric' ) ); 385 | } 386 | 387 | public function sanitize_string( $item ) { 388 | return htmlentities( stripslashes( $item ) ); 389 | } 390 | 391 | /** 392 | * @param WP_REST_Request $rest_request 393 | * @param string $key Parameter name. 394 | * @param string $default_value 395 | * @param string $sanitize_callback 396 | * 397 | * @return array|mixed|null|string 398 | */ 399 | private function get_param( $rest_request, $key, $default_value = '', $sanitize_callback = '' ) { 400 | $value = $rest_request->get_param( $key ); 401 | $value = empty( $value ) ? $default_value : $value; 402 | 403 | if ( is_callable( $sanitize_callback ) ) { 404 | $value = ( is_array( $value ) ) ? array_map( $sanitize_callback, $value ) : call_user_func( $sanitize_callback, $value ); 405 | } 406 | 407 | return $value; 408 | } 409 | 410 | public function _params_for_create_zone() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 411 | return array( 412 | 'name' => array( 413 | 'type' => 'string', 414 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 415 | 'default' => '', 416 | 'required' => false, 417 | ), 418 | 'slug' => array( 419 | 'type' => 'string', 420 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 421 | 'default' => '', 422 | 'required' => false, 423 | ), 424 | 'description' => array( 425 | 'type' => 'string', 426 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 427 | 'default' => '', 428 | 'required' => false, 429 | ), 430 | ); 431 | } 432 | 433 | public function _params_for_update_zone() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 434 | return array( 435 | 'name' => array( 436 | 'type' => 'string', 437 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 438 | 'required' => false, 439 | ), 440 | 'slug' => array( 441 | 'type' => 'string', 442 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 443 | 'required' => false, 444 | ), 445 | 'description' => array( 446 | 'type' => 'string', 447 | 'sanitize_callback' => array( $this, 'sanitize_string' ), 448 | 'required' => false, 449 | ), 450 | ); 451 | } 452 | 453 | public function _get_zone_id_param() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 454 | return array( 455 | 'zone_id' => array( 456 | 'type' => 'integer', 457 | 'validate_callback' => array( $this, 'is_numeric' ), 458 | 'sanitize_callback' => 'absint', 459 | 'required' => true, 460 | ), 461 | ); 462 | } 463 | 464 | public function _get_zone_post_rest_route_params() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 465 | $zone_params = $this->_get_zone_id_param(); 466 | return array_merge( 467 | array( 468 | 'post_ids' => array( 469 | 'type' => 'array', 470 | 'validate_callback' => array( $this, 'is_numeric_array' ), 471 | 'required' => true, 472 | 'items' => array( 'type' => 'integer' ), 473 | ), 474 | ), 475 | $zone_params 476 | ); 477 | } 478 | 479 | public function _filter_zone_properties( $zone ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 480 | $data = $zone->to_array(); 481 | 482 | return array( 483 | 'term_id' => $data['term_id'], 484 | 'slug' => $data['slug'], 485 | 'name' => $data['name'], 486 | 'description' => $data['description'], 487 | ); 488 | } 489 | 490 | private function _bad_request( $code, $message ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 491 | return new WP_Error( $code, $message, array( 'status' => 400 ) ); 492 | } 493 | 494 | /** 495 | * @param $zone_id 496 | * @return bool|WP_Error 497 | */ 498 | private function _permissions_check( $action, $zone_id = null ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore 499 | if ( ! $this->instance->check( $action, $zone_id ) ) { 500 | return new WP_Error( self::PERMISSION_DENIED, __( "Sorry, you're not supposed to do that...", 'zoninator' ) ); 501 | } 502 | 503 | return true; 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /src/class-zoninator-api-filter-search.php: -------------------------------------------------------------------------------- 1 | get_environment(); 25 | return array( 26 | $env->field( 'term', __( 'search term', 'zoninator' ) ) 27 | ->with_type( $env->type( 'string' ) ) 28 | ->with_before_set( array( $this, 'strip_slashes' ) ) 29 | ->with_required( true ) 30 | ->with_default( '' ), 31 | $env->field( 'cat', __( 'filter by category', 'zoninator' ) ) 32 | ->with_type( $env->type( 'uint' ) ) 33 | ->with_validations( array( $this, 'is_numeric' ) ) 34 | ->with_default( 0 ), 35 | $env->field( 'date', __( 'only get posts after this date (format YYYY-mm-dd)', 'zoninator' ) ) 36 | ->with_type( $env->type( 'string' ) ) 37 | ->with_before_set( array( $this, 'date_before_set' ) ) 38 | ->with_default( '' ), 39 | $env->field( 'limit', __( 'limit results', 'zoninator' ) ) 40 | ->with_type( $env->type( 'uint' ) ) 41 | ->with_before_set( array( $this, 'strip_slashes' ) ) 42 | ->with_default( Zoninator()->posts_per_page ), 43 | $env->field( 'exclude', __( 'post_ids to exclude', 'zoninator' ) ) 44 | ->with_type( $env->type( 'array:uint' ) ), 45 | ); 46 | } 47 | 48 | /** 49 | * Is Numeric 50 | * 51 | * @param mixed $item The item. 52 | * @return bool 53 | */ 54 | public function is_numeric( $item ) { 55 | // see https://github.com/WP-API/WP-API/issues/1520 on why we do not use is_numeric directly. 56 | return is_numeric( $item ); 57 | } 58 | 59 | /** 60 | * Strip slashes 61 | * 62 | * @param mixed $item Item. 63 | * @return string 64 | */ 65 | public function strip_slashes( $item ) { 66 | // see https://github.com/WP-API/WP-API/issues/1520 on why we do not use stripslashes directly. 67 | return stripslashes( $item ); 68 | } 69 | 70 | public function strip_tags( $item ) { 71 | return wp_strip_all_tags( $item ); 72 | } 73 | 74 | public function date_before_set( $model, $item ) { 75 | return $this->strip_tags( $this->strip_slashes( $item ) ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/class-zoninator-api-schema-converter.php: -------------------------------------------------------------------------------- 1 | get_fields(); 12 | $properties = array(); 13 | $required = array(); 14 | foreach ( $fields as $field_declaration ) { 15 | /** 16 | * Our declaration 17 | * 18 | * @var Zoninator_REST_Field_Declaration $field_declaration 19 | */ 20 | $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property(); 21 | if ( $field_declaration->is_required() ) { 22 | $required[] = $field_declaration->get_data_transfer_name(); 23 | } 24 | } 25 | 26 | $schema = array( 27 | '$schema' => 'http://json-schema.org/schema#', 28 | 'title' => $model_definition->get_name(), 29 | 'type' => 'object', 30 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- applying a core filter. 31 | 'properties' => (array) apply_filters( 'rest_api_schema_properties', $properties, $model_definition ), 32 | ); 33 | 34 | if ( array() !== $required ) { 35 | $schema['required'] = $required; 36 | } 37 | 38 | return $schema; 39 | } 40 | 41 | /** 42 | * As Schema 43 | * 44 | * @param Zoninator_REST_Model $model_definition Def. 45 | * @return array 46 | */ 47 | public function as_args( $model_definition ) { 48 | $fields = $model_definition->get_fields(); 49 | $result = array(); 50 | foreach ( $fields as $field_declaration ) { 51 | $type_schema = $field_declaration->get_type()->schema(); 52 | /** 53 | * Our declaration 54 | * 55 | * @var Zoninator_REST_Field_Declaration $field_declaration 56 | */ 57 | $arg = array( 58 | 'description' => $field_declaration->get_description(), 59 | 'type' => $type_schema['type'], 60 | 'required' => $field_declaration->is_required(), 61 | ); 62 | 63 | if ( ! $field_declaration->is_required() ) { 64 | $arg['default'] = $field_declaration->get_default_value(); 65 | } 66 | 67 | $result[ $field_declaration->get_data_transfer_name() ] = $arg; 68 | } 69 | 70 | return $result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/class-zoninator-api.php: -------------------------------------------------------------------------------- 1 | instance = $instance; 25 | add_action( 'rest_api_init', array( $this, 'rest_api' ) ); 26 | } 27 | 28 | /** 29 | * Rest Api. 30 | */ 31 | public function rest_api() { 32 | include_once dirname( ZONINATOR_FILE ) . '/src/zoninator_rest/class-zoninator-rest-bootstrap.php'; 33 | $this->bootstrap = Zoninator_REST_Bootstrap::create()->load(); 34 | include_once __DIR__ . '/class-zoninator-api-schema-converter.php'; 35 | include_once __DIR__ . '/class-zoninator-api-filter-search.php'; 36 | include_once __DIR__ . '/class-zoninator-api-controller.php'; 37 | $env = $this->bootstrap->environment(); 38 | 39 | $env->define_model( 'Zoninator_Api_Filter_Search' ); 40 | 41 | $env->rest_api( 'zoninator/v1' ) 42 | ->add_endpoint( new Zoninator_Api_Controller( $this->instance ) ); 43 | $env->start(); 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/class-zoninator-zoneposts-widget.php: -------------------------------------------------------------------------------- 1 | 'widget-zone-posts', 12 | 'description' => __( 'Use this widget to display a list of posts from any zone.', 'zoninator' ), 13 | ); 14 | 15 | $this->alt_option_name = 'widget_zone_posts'; 16 | add_action( 'save_post', array( &$this, 'flush_widget_cache' ) ); 17 | add_action( 'deleted_post', array( &$this, 'flush_widget_cache' ) ); 18 | add_action( 'switch_theme', array( &$this, 'flush_widget_cache' ) ); 19 | 20 | parent::__construct( 21 | false, 22 | __( 'Zone Posts', 'zoninator' ), 23 | $widget_ops 24 | ); 25 | } 26 | 27 | public function widget( $args, $instance ) { 28 | $cache_key = 'widget-zone-posts'; 29 | $cache = wp_cache_get( $cache_key, 'widget' ); 30 | 31 | if ( ! is_array( $cache ) ) { 32 | $cache = array(); 33 | } 34 | 35 | if ( isset( $cache[ $args['widget_id'] ] ) ) { 36 | echo wp_kses_post( $cache[ $args['widget_id'] ] ); 37 | return; 38 | } 39 | 40 | ob_start(); 41 | 42 | $zone_id = $instance['zone_id'] ?: 0; 43 | $show_description = $instance['show_description'] ? 1 : 0; 44 | if ( ! $zone_id ) { 45 | return; 46 | } 47 | 48 | $zone = z_get_zone( $zone_id ); 49 | if ( ! $zone ) { 50 | return; 51 | } 52 | 53 | $posts = z_get_posts_in_zone( $zone_id ); 54 | if ( empty( $posts ) ) { 55 | return; 56 | } 57 | 58 | ?> 59 | 60 | 61 | name ) . wp_kses_post( $args['after_title'] ); ?> 62 | 63 | description ) && $show_description ) : ?> 64 |

    description ); ?>

    65 | 68 | 69 | 80 | 81 | 82 | 0, 100 | 'show_description' => 0, 101 | ) 102 | ); 103 | $instance['zone_id'] = absint( $new_instance['zone_id'] ); 104 | $instance['show_description'] = $new_instance['show_description'] ? 1 : 0; 105 | $this->flush_widget_cache(); 106 | $alloptions = wp_cache_get( 'alloptions', 'options' ); 107 | if ( isset( $alloptions['widget-zone-posts'] ) ) { 108 | delete_option( 'widget-zone-posts' ); 109 | } 110 | 111 | return $instance; 112 | } 113 | 114 | public function flush_widget_cache() { 115 | $cache_key = 'widget-zone-posts'; 116 | 117 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- breaking change to rename this filter with the correct prefix. 118 | $block_save_cache_seconds = absint( apply_filters( 'zone_posts_widget_block_save_cache_seconds', 5 ) ); 119 | if ( $block_save_cache_seconds > 0 ) { 120 | // This key will block updating the cache for n seconds so the following cache delete can propagate 121 | // phpcs:ignore -- WordPressVIPMinimum.Performance.LowExpiryCacheTime.CacheTimeUndetermined -- not changing behavior now. 122 | wp_cache_set( $cache_key . '-save_blocked', 1, 'widget', $block_save_cache_seconds ); 123 | } 124 | 125 | wp_cache_delete( $cache_key, 'widget' ); 126 | } 127 | 128 | public function form( $instance ) { 129 | // select - zone 130 | // checkbox - show description 131 | $zones = z_get_zones(); 132 | if ( empty( $zones ) ) { 133 | esc_html_e( 'You need to create at least one zone before you use this widget!', 'zoninator' ); 134 | return; 135 | } 136 | 137 | $zone_id = isset( $instance['zone_id'] ) ? absint( $instance['zone_id'] ) : 0; 138 | $show_description = isset( $instance['show_description'] ) ? (bool) $instance['show_description'] : true; 139 | ?> 140 | 141 |

    142 | 143 | 156 |

    157 | 158 |

    159 | 163 |

    164 | class_loader = $class_loader; 44 | } 45 | 46 | /** 47 | * Check compatibility of PHP Version. 48 | * 49 | * @return bool 50 | */ 51 | public static function is_compatible() { 52 | return version_compare( phpversion(), self::MINIMUM_PHP_VERSION, '>=' ); 53 | } 54 | 55 | /** 56 | * Get Base Dir 57 | * 58 | * @return string 59 | */ 60 | public static function get_base_dir() { 61 | return untrailingslashit( __DIR__ ); 62 | } 63 | 64 | /** 65 | * Create a Bootstrap, unless we are using a really early php version (< 5.3.0) 66 | * 67 | * @param Zoninator_REST_Interfaces_Classloader|null $class_loader The class loader to use. 68 | * @return Zoninator_REST_Bootstrap|null 69 | */ 70 | public static function create( $class_loader = null ) { 71 | if ( empty( $class_loader ) ) { 72 | include_once __DIR__ . '/interfaces/class-zoninator-rest-interfaces-classloader.php'; 73 | include_once __DIR__ . '/class-zoninator-rest-classloader.php'; 74 | $prefix = str_replace( '_Bootstrap', '', self::class ); 75 | $base_dir = self::get_base_dir(); 76 | $class_loader = new Zoninator_REST_Classloader( $prefix, $base_dir ); 77 | } 78 | 79 | return new self( $class_loader ); 80 | } 81 | 82 | /** 83 | * Run the app 84 | * 85 | * @return bool 86 | */ 87 | public function run() { 88 | if ( ! self::is_compatible() ) { 89 | return false; 90 | } 91 | 92 | $this->load() 93 | ->environment()->start(); 94 | return true; 95 | } 96 | 97 | /** 98 | * Optional: Instead of calling load() you can 99 | * register as an auto-loader 100 | * 101 | * @return Zoninator_REST_Bootstrap $this 102 | */ 103 | public function register_autoload() { 104 | if ( function_exists( 'spl_autoload_register' ) ) { 105 | spl_autoload_register( array( $this->class_loader(), 'load_class' ), true ); 106 | } 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Loads all classes 113 | * 114 | * @return Zoninator_REST_Bootstrap $this 115 | * @throws Exception In case a class/file is not found. 116 | */ 117 | public function load() { 118 | $this->class_loader() 119 | ->load_class( 'Interfaces_Data_Store' ) 120 | ->load_class( 'Interfaces_Registrable' ) 121 | ->load_class( 'Interfaces_Type' ) 122 | ->load_class( 'Interfaces_Model' ) 123 | ->load_class( 'Interfaces_Builder' ) 124 | ->load_class( 'Interfaces_Model_Collection' ) 125 | ->load_class( 'Interfaces_Controller' ) 126 | ->load_class( 'Interfaces_Controller_Bundle' ) 127 | ->load_class( 'Interfaces_Permissions_Provider' ) 128 | ->load_class( 'Exception' ) 129 | ->load_class( 'Expect' ) 130 | ->load_class( 'Environment' ) 131 | ->load_class( 'Type' ) 132 | ->load_class( 'Type_String' ) 133 | ->load_class( 'Type_Integer' ) 134 | ->load_class( 'Type_Number' ) 135 | ->load_class( 'Type_Boolean' ) 136 | ->load_class( 'Type_Array' ) 137 | ->load_class( 'Type_TypedArray' ) 138 | ->load_class( 'Type_Nullable' ) 139 | ->load_class( 'Type_Registry' ) 140 | ->load_class( 'Data_Store_Nil' ) 141 | ->load_class( 'Data_Store_Abstract' ) 142 | ->load_class( 'Data_Store_CustomPostType' ) 143 | ->load_class( 'Data_Store_Option' ) 144 | ->load_class( 'Permissions_Any' ) 145 | ->load_class( 'Field_Declaration' ) 146 | ->load_class( 'Field_Declaration_Builder' ) 147 | ->load_class( 'Model' ) 148 | ->load_class( 'Model_Settings' ) 149 | ->load_class( 'Model_Collection' ) 150 | ->load_class( 'Controller' ) 151 | ->load_class( 'Controller_Action' ) 152 | ->load_class( 'Controller_Model' ) 153 | ->load_class( 'Controller_Settings' ) 154 | ->load_class( 'Controller_Route' ) 155 | ->load_class( 'Controller_CRUD' ) 156 | ->load_class( 'Controller_Bundle' ) 157 | ->load_class( 'Controller_Extension' ) 158 | ->load_class( 'Controller_Bundle_Builder' ); 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * Load Unit Testing Base Classes 165 | * 166 | * @return Zoninator_REST_Bootstrap $this 167 | */ 168 | public function load_testing_classes() { 169 | $this->class_loader() 170 | ->load_class( 'Testing_TestCase' ) 171 | ->load_class( 'Testing_Model_TestCase' ) 172 | ->load_class( 'Testing_Controller_TestCase' ); 173 | return $this; 174 | } 175 | 176 | /** 177 | * Get the class loader 178 | * 179 | * @return Zoninator_REST_Classloader 180 | */ 181 | public function class_loader() { 182 | return $this->class_loader; 183 | } 184 | 185 | /** 186 | * Lazy-load the environment 187 | * 188 | * @return Zoninator_REST_Environment 189 | */ 190 | public function environment() { 191 | if ( null === $this->environment ) { 192 | $this->environment = new Zoninator_REST_Environment( $this ); 193 | } 194 | 195 | return $this->environment; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/zoninator_rest/class-zoninator-rest-classloader.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 49 | $this->base_dir = $base_dir; 50 | if ( ! is_dir( $this->base_dir ) ) { 51 | throw new Exception( esc_html( 'base_dir does not exist: ' . $this->base_dir ) ); 52 | } 53 | } 54 | 55 | /** 56 | * Loads a class 57 | * 58 | * @param string $class_name The class to load. 59 | * 60 | * @return Zoninator_REST_Interfaces_Classloader 61 | * @throws Exception Throws in case include_class_file fails. 62 | */ 63 | public function load_class( $class_name ) { 64 | $path = $this->get_path_to_class_file( $class_name ); 65 | return $this->include_class_file( $path ); 66 | } 67 | 68 | /** 69 | * Get path_to_class_file 70 | * 71 | * @param string $class_name The class. 72 | * 73 | * @return string The full path to the file. 74 | */ 75 | public function get_path_to_class_file( $class_name ) { 76 | return path_join( $this->base_dir, $this->class_name_to_relative_path( $class_name ) ); 77 | } 78 | 79 | /** 80 | * Class_name_to_relative_path 81 | * 82 | * @param string $class_name The class name. 83 | * @param null|string $prefix The prefix. 84 | * 85 | * @return string 86 | */ 87 | public function class_name_to_relative_path( $class_name, $prefix = null ) { 88 | $lowercase = strtolower( $this->prefixed_class_name( $class_name, $prefix ) ); 89 | $file_name = 'class-' . str_replace( '_', '-', $lowercase ) . '.php'; 90 | $parts = explode( '_', strtolower( $this->strip_prefix( $class_name, $prefix ) ) ); 91 | array_pop( $parts ); 92 | $parts[] = $file_name; 93 | return implode( DIRECTORY_SEPARATOR, $parts ); 94 | } 95 | 96 | /** 97 | * Prefixed_class_name 98 | * 99 | * @param string $class_name The class name. 100 | * @param null|string $prefix The prefix. 101 | * 102 | * @return string 103 | */ 104 | public function prefixed_class_name( $class_name, $prefix = null ) { 105 | if ( empty( $prefix ) ) { 106 | $prefix = $this->prefix; 107 | } 108 | 109 | return $prefix . '_' . $this->strip_prefix( $class_name, $prefix ); 110 | } 111 | 112 | /** 113 | * Strip_prefix 114 | * 115 | * @param string $class_name The class name. 116 | * @param null|string $prefix The prefix. 117 | * 118 | * @return string 119 | */ 120 | private function strip_prefix( $class_name, $prefix = null ) { 121 | if ( empty( $prefix ) ) { 122 | $prefix = $this->prefix; 123 | } 124 | 125 | return str_replace( $prefix, '', $class_name ); 126 | } 127 | 128 | /** 129 | * Include_class_file 130 | * 131 | * @param string $path_to_the_class The file path. 132 | * 133 | * @return string 134 | * @throws Exception Throws when the file does not exist. 135 | */ 136 | private function include_class_file( $path_to_the_class ) { 137 | if ( isset( $this->loaded_classes[ $path_to_the_class ] ) ) { 138 | return $this; 139 | } 140 | 141 | if ( ! file_exists( $path_to_the_class ) ) { 142 | throw new Exception( esc_html( $path_to_the_class . ' not found' ) ); 143 | } 144 | 145 | $included = include_once $path_to_the_class; 146 | $this->loaded_classes[ $path_to_the_class ] = $included; 147 | 148 | return $this; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/zoninator_rest/class-zoninator-rest-controller.php: -------------------------------------------------------------------------------- 1 | controller_bundle = $controller_bundle; 67 | return $this; 68 | } 69 | 70 | /** 71 | * Set the Environment for this Controller. 72 | * 73 | * @param Zoninator_REST_Environment|null $environment The Environment. 74 | * @return Zoninator_REST_Controller 75 | */ 76 | public function set_environment( $environment ) { 77 | $this->environment = $environment; 78 | return $this; 79 | } 80 | 81 | /** 82 | * Register This Controller 83 | * 84 | * @param Zoninator_REST_Controller_Bundle $bundle The bundle to register with. 85 | * @param Zoninator_REST_Environment $environment The Environment to use. 86 | * @throws Zoninator_REST_Exception Throws. 87 | * 88 | * @return bool|WP_Error true if valid otherwise error. 89 | */ 90 | public function register( $bundle, $environment ) { 91 | $this->set_controller_bundle( $bundle ); 92 | $this->set_environment( $environment ); 93 | $this->setup(); 94 | Zoninator_REST_Expect::that( ! empty( $this->base ), 'Need to put a string with a backslash in $base' ); 95 | $prefix = $this->controller_bundle->get_prefix(); 96 | foreach ( $this->routes as $route ) { 97 | /** 98 | * The route we want to register. 99 | * 100 | * @var Zoninator_REST_Controller_Route $route 101 | */ 102 | $params = $route->as_array(); 103 | $result = register_rest_route( $prefix, $this->base . $params['pattern'], $params['actions'] ); 104 | if ( false === $result ) { 105 | // For now we throw on error, maybe we just need to warn though. 106 | throw new Zoninator_REST_Exception( 'Registration failed' ); 107 | } 108 | } 109 | 110 | return true; 111 | } 112 | 113 | /** 114 | * Create Action 115 | * 116 | * @param string $action_name Action Name. 117 | * @param null|string|array|callable $callback Callback. 118 | * @return Zoninator_REST_Controller_Action 119 | */ 120 | public function action( $action_name, $callback = null ) { 121 | $route_action = new Zoninator_REST_Controller_Action( $this, $action_name ); 122 | if ( null !== $callback ) { 123 | $route_action->callback( $callback ); 124 | } 125 | 126 | return $route_action; 127 | } 128 | 129 | /** 130 | * Do any additional Configuration. Runs inside register before any register_rest_route 131 | * 132 | * This is a good place for overriding classes to define routes and handlers 133 | */ 134 | protected function setup() { 135 | } 136 | 137 | /** 138 | * Succeed 139 | * 140 | * @param array $data The dto. 141 | * 142 | * @return WP_REST_Response 143 | */ 144 | public function ok( $data ) { 145 | return $this->respond( $data, self::HTTP_OK ); 146 | } 147 | 148 | /** 149 | * Created 150 | * 151 | * @param array $data The dto. 152 | * 153 | * @return WP_REST_Response 154 | */ 155 | public function created( $data ) { 156 | return $this->respond( $data, self::HTTP_CREATED ); 157 | } 158 | 159 | /** 160 | * Bad request 161 | * 162 | * @param array|WP_Error $data The dto. 163 | * 164 | * @return WP_REST_Response 165 | */ 166 | public function bad_request( $data ) { 167 | return $this->respond( $data, self::HTTP_BAD_REQUEST ); 168 | } 169 | 170 | /** 171 | * Not Found (404) 172 | * 173 | * @param string $message The message. 174 | * 175 | * @return WP_REST_Response 176 | */ 177 | public function not_found( $message ) { 178 | return $this->respond( 179 | array( 180 | 'message' => $message, 181 | ), 182 | self::HTTP_NOT_FOUND 183 | ); 184 | } 185 | 186 | /** 187 | * Respond 188 | * 189 | * @param array|WP_REST_Response|WP_Error|mixed $data The thing. 190 | * @param int $status The Status. 191 | * 192 | * @return mixed|WP_REST_Response 193 | */ 194 | public function respond( $data, $status ) { 195 | if ( is_a( $data, 'WP_REST_Response' ) ) { 196 | return $data; 197 | } 198 | 199 | return new WP_REST_Response( $data, $status ); 200 | } 201 | 202 | /** 203 | * Permissions for get_items 204 | * 205 | * @param WP_REST_Request $request Request. 206 | * @return bool 207 | */ 208 | public function index_permissions_check( $request ) { 209 | return $this->permissions_check( $request, 'index' ); 210 | } 211 | 212 | /** 213 | * Permissions for get_item 214 | * 215 | * @param WP_REST_Request $request The request. 216 | * @return bool 217 | */ 218 | public function show_permissions_check( $request ) { 219 | return $this->permissions_check( $request, 'show' ); 220 | } 221 | 222 | /** 223 | * Permissions for create_item 224 | * 225 | * @param WP_REST_Request $request Request. 226 | * @return bool 227 | */ 228 | public function create_permissions_check( $request ) { 229 | return $this->permissions_check( $request, 'create' ); 230 | } 231 | 232 | /** 233 | * Permissions for update_item 234 | * 235 | * @param WP_REST_Request $request Request. 236 | * @return bool 237 | */ 238 | public function update_permissions_check( $request ) { 239 | return $this->permissions_check( $request, 'update' ); 240 | } 241 | 242 | /** 243 | * Permissions for delete 244 | * 245 | * @param WP_REST_Request $request Request. 246 | * @return bool 247 | */ 248 | public function delete_permissions_check( $request ) { 249 | return $this->permissions_check( $request, 'delete' ); 250 | } 251 | 252 | /** 253 | * Generic Permissions Check. 254 | * 255 | * @param WP_REST_Request $request Request. 256 | * @param string $action One of (index, show, create, update, delete, any). 257 | * @return bool 258 | */ 259 | public function permissions_check( $request, $action = 'any' ) { 260 | return true; 261 | } 262 | 263 | /** 264 | * Add a route 265 | * 266 | * @param string $pattern The route pattern (e.g. '/'). 267 | * @return Zoninator_REST_Controller_Route 268 | */ 269 | public function add_route( $pattern = '' ) { 270 | $route = new Zoninator_REST_Controller_Route( $this, $pattern ); 271 | $this->routes[ $pattern ] = $route; 272 | return $this->routes[ $pattern ]; 273 | } 274 | 275 | /** 276 | * Get Environment 277 | * 278 | * @return Zoninator_REST_Environment 279 | */ 280 | protected function environment() { 281 | return $this->environment; 282 | } 283 | 284 | /** 285 | * Get base url 286 | * 287 | * @return string 288 | */ 289 | public function get_base() { 290 | return rest_url( $this->controller_bundle->get_prefix() . $this->base ); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/zoninator_rest/class-zoninator-rest-environment.php: -------------------------------------------------------------------------------- 1 | bootstrap = $bootstrap; 81 | $this->type_registry = new Zoninator_REST_Type_Registry(); 82 | $this->type_registry->initialize( $this ); 83 | // initialize our array vars. 84 | $this->array_var( self::MODELS ) 85 | ->array_var( self::REGISTRABLE ) 86 | ->array_var( self::BUNDLES ); 87 | } 88 | 89 | /** 90 | * Push a Builder to the Environment. 91 | * 92 | * All builders are evaluated lazily when needed 93 | * 94 | * @param string $where The queue to push the builder to. 95 | * @param Zoninator_REST_Interfaces_Builder $builder The builder to push. 96 | * 97 | * @return Zoninator_REST_Environment $this 98 | * @throws Zoninator_REST_Exception In case the $builder is not a Mixtape_Interfaces_Builder. 99 | */ 100 | public function push_builder( $where, $builder ) { 101 | Zoninator_REST_Expect::that( is_string( $where ), '$where should be a string' ); 102 | Zoninator_REST_Expect::is_a( $builder, 'Zoninator_REST_Interfaces_Builder' ); 103 | return $this->array_var( $where, $builder ); 104 | } 105 | 106 | /** 107 | * Retrieve a previously defined Zoninator_REST_Model 108 | * 109 | * @param string $class_name the class name. 110 | * 111 | * @return Zoninator_REST_Model the definition. 112 | * @throws Zoninator_REST_Exception Throws in case the model is not registered. 113 | */ 114 | public function model( $class_name ) { 115 | if ( ! class_exists( $class_name ) ) { 116 | throw new Zoninator_REST_Exception( esc_html( $class_name . ' does not exist' ) ); 117 | } 118 | 119 | Zoninator_REST_Expect::that( isset( $this->model_definitions[ $class_name ] ), $class_name . ' definition does not exist' ); 120 | return $this->model_definitions[ $class_name ]; 121 | } 122 | 123 | /** 124 | * Time to build pending models and bundles 125 | * 126 | * @param string $type One of (models, bundles). 127 | * @return Zoninator_REST_Environment 128 | */ 129 | private function load_pending_builders( $type ) { 130 | $things = $this->get( $type ); 131 | if ( ! empty( $things ) && is_array( $things ) ) { 132 | foreach ( $things as $pending ) { 133 | /** 134 | * Our pending builder. 135 | * 136 | * @var Zoninator_REST_Interfaces_Builder $pending Our builder. 137 | */ 138 | if ( self::BUNDLES === $type ) { 139 | $this->add_rest_bundle( $pending->build() ); 140 | } 141 | } 142 | } 143 | 144 | return $this; 145 | } 146 | 147 | /** 148 | * Start things up 149 | * 150 | * This should be called once our Environment is set up to our liking. 151 | * Evaluates all Builders, creating missing REST Api and Model Definitions. 152 | * 153 | * 154 | * It is recommended we hook this into 'rest_api_init'. 155 | * 156 | * @return Zoninator_REST_Environment $this 157 | */ 158 | public function start() { 159 | if ( ! $this->bootstrap->is_compatible() ) { 160 | // Do not even start on an incompatible system. 161 | return $this; 162 | } 163 | 164 | if ( false === $this->has_started ) { 165 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 166 | do_action( 'mt_environment_before_start', $this, get_class( $this ) ); 167 | $this->load_pending_builders( self::MODELS ); 168 | $this->load_pending_builders( self::BUNDLES ); 169 | $registrables = $this->get( self::REGISTRABLE ) ?: array(); 170 | foreach ( $registrables as $registrable ) { 171 | /** 172 | * A Registrable 173 | * 174 | * @var Zoninator_REST_Interfaces_Registrable $registrable 175 | */ 176 | $registrable->register( $this ); 177 | } 178 | 179 | /** 180 | * Use this hook to add/remove rest api bundles 181 | * 182 | * @param array $rest_apis The existing rest apis. 183 | * @param Zoninator_REST_Environment $this The Environment. 184 | */ 185 | $rest_apis = (array) apply_filters( 'mt_environment_get_rest_apis', $this->rest_apis, $this ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 186 | 187 | foreach ( $rest_apis as $bundle ) { 188 | /** 189 | * Register this bundle 190 | */ 191 | $bundle->register( $this ); 192 | } 193 | 194 | $this->has_started = true; 195 | do_action( 'mt_environment_after_start', $this ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 196 | } 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Add Registrable 203 | * 204 | * @param Zoninator_REST_Interfaces_Registrable $registrable_thing Registrable. 205 | * @return Zoninator_REST_Environment 206 | * @throws Zoninator_REST_Exception When not a Zoninator_REST_Interfaces_Registrable. 207 | */ 208 | public function add_registrable( $registrable_thing ) { 209 | Zoninator_REST_Expect::is_a( $registrable_thing, 'Zoninator_REST_Interfaces_Registrable' ); 210 | $this->array_var( self::REGISTRABLE, $registrable_thing ); 211 | return $this->define_var( get_class( $registrable_thing ), $registrable_thing ); 212 | } 213 | 214 | /** 215 | * Has Variable 216 | * 217 | * @param string $name Is this variable Set. 218 | * @return bool 219 | */ 220 | public function has_variable( $name ) { 221 | return isset( $this->variables[ $name ] ); 222 | } 223 | 224 | /** 225 | * Append to an array 226 | * 227 | * @param string $name The VarArray Name. 228 | * @param mixed $thing The thing. 229 | * @return Zoninator_REST_Environment 230 | */ 231 | public function array_var( $name, $thing = null ) { 232 | return $this->define_var( $name, $thing, true ); 233 | } 234 | 235 | /** 236 | * Get A Variable 237 | * 238 | * @param string $name The Variable Name. 239 | * @return mixed|null The variable or null 240 | * 241 | * @throws Zoninator_REST_Exception Name should be a string. 242 | */ 243 | public function get( $name ) { 244 | Zoninator_REST_Expect::that( is_string( $name ), '$name should be a string' ); 245 | $value = $this->has_variable( $name ) ? $this->variables[ $name ] : null; 246 | /** 247 | * Filter the variable value 248 | * 249 | * @param mixed $value The value. 250 | * @param Zoninator_REST_Environment $this The Environment. 251 | * @param string $name The var name. 252 | * 253 | * @return mixed 254 | */ 255 | return apply_filters( 'mt_variable_get', $value, $this, $name ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 256 | } 257 | 258 | /** 259 | * Def. 260 | * 261 | * @param string $name The Variable To Add. 262 | * @param mixed $thing The thing that is associated with the var. 263 | * @param bool $append If true, this var is a list. 264 | * 265 | * @return $this 266 | * 267 | * @throws Zoninator_REST_Exception When name is not a string. 268 | */ 269 | public function define_var( $name, $thing = null, $append = false ) { 270 | Zoninator_REST_Expect::that( is_string( $name ), '$name should be a string' ); 271 | if ( $append && ! $this->has_variable( $name ) ) { 272 | $this->variables[ $name ] = array(); 273 | } 274 | 275 | if ( null !== $thing ) { 276 | if ( $append ) { 277 | $this->variables[ $name ][] = $thing; 278 | } else { 279 | $this->variables[ $name ] = $thing; 280 | } 281 | } 282 | 283 | return $this; 284 | } 285 | 286 | /** 287 | * Auto start on rest_api_init. For more control, use ::start(); 288 | */ 289 | public function auto_start() { 290 | add_action( 'rest_api_init', array( $this, 'start' ) ); 291 | } 292 | 293 | /** 294 | * Get this Environment's bootstrap instance 295 | * 296 | * @return Zoninator_REST_Bootstrap our bootstrap. 297 | */ 298 | public function get_bootstrap() { 299 | return $this->bootstrap; 300 | } 301 | 302 | /** 303 | * Create a new Field Declaration Builder 304 | * 305 | * @param null|string $name Optional, the field name. 306 | * @param null|string $description Optional, the description. 307 | * @param null|string $field_kind The field kind (default 'field'). 308 | * 309 | * @return Zoninator_REST_Field_Declaration_Builder 310 | */ 311 | public function field( $name = null, $description = null, $field_kind = null ) { 312 | $builder = new Zoninator_REST_Field_Declaration_Builder(); 313 | 314 | if ( ! empty( $name ) ) { 315 | $builder->with_name( $name ); 316 | } 317 | 318 | if ( ! empty( $description ) ) { 319 | $builder->with_description( $description ); 320 | } 321 | 322 | if ( empty( $field_kind ) ) { 323 | $field_kind = Zoninator_REST_Field_Declaration::FIELD; 324 | } 325 | 326 | $builder->with_kind( $field_kind ); 327 | 328 | return $builder; 329 | } 330 | 331 | /** 332 | * Get our registered types 333 | * 334 | * @return Zoninator_REST_Type_Registry 335 | */ 336 | public function get_type_registry() { 337 | return $this->type_registry; 338 | } 339 | 340 | /** 341 | * Get a known type definition 342 | * 343 | * @param string $type_name The type name. 344 | * @return Zoninator_REST_Interfaces_Type 345 | * 346 | * @throws Zoninator_REST_Exception When provided with an unknown/invalid type. 347 | */ 348 | public function type( $type_name ) { 349 | return $this->get_type_registry()->definition( $type_name ); 350 | } 351 | 352 | /** 353 | * Define a new REST API Bundle. 354 | * 355 | * @param null|string|Zoninator_REST_Interfaces_Controller_Bundle $maybe_bundle_or_prefix The bundle name. 356 | * @return Zoninator_REST_Controller_Bundle_Builder 357 | */ 358 | public function rest_api( $maybe_bundle_or_prefix = null ) { 359 | if ( is_a( $maybe_bundle_or_prefix, 'Zoninator_REST_Interfaces_Controller_Bundle' ) ) { 360 | $builder = new Zoninator_REST_Controller_Bundle_Builder( $maybe_bundle_or_prefix ); 361 | } else { 362 | $builder = new Zoninator_REST_Controller_Bundle_Builder(); 363 | if ( is_string( $maybe_bundle_or_prefix ) ) { 364 | $builder->with_prefix( $maybe_bundle_or_prefix ); 365 | } 366 | 367 | $builder->with_environment( $this ); 368 | } 369 | 370 | $this->push_builder( self::BUNDLES, $builder ); 371 | return $builder; 372 | } 373 | 374 | /** 375 | * Define a new Model 376 | * 377 | * @param string $declaration A Model class string. 378 | * 379 | * @return Zoninator_REST_Model 380 | */ 381 | public function define_model( $declaration ) { 382 | Zoninator_REST_Expect::that( class_exists( $declaration ), '$declaration string should be an existing class' ); 383 | Zoninator_REST_Expect::that( in_array( 'Zoninator_REST_Interfaces_Model', class_implements( $declaration ), true ), '$declaration does not implement Zoninator_REST_Interfaces_Model' ); 384 | 385 | /** 386 | * Create an empty Model to act as our factory (I know this is weird, see php5.2) 387 | * 388 | * @var Zoninator_REST_Model $factory 389 | */ 390 | $factory = new $declaration(); 391 | $factory->with_environment( $this ); 392 | $factory->with_data_store( new Zoninator_REST_Data_Store_Nil() ); 393 | $factory->with_permissions_provider( new Zoninator_REST_Permissions_Any() ); 394 | $this->model_definitions[ $declaration ] = $factory; 395 | return $factory; 396 | } 397 | 398 | /** 399 | * Add a Bundle to our bundles (muse be Mixtape_Interfaces_Rest_Api_Controller_Bundle) 400 | * 401 | * @param Zoninator_REST_Interfaces_Controller_Bundle $bundle the bundle. 402 | * 403 | * @return Zoninator_REST_Environment $this 404 | * @throws Zoninator_REST_Exception In case it's not a Zoninator_REST_Interfaces_Controller_Bundle. 405 | */ 406 | private function add_rest_bundle( $bundle ) { 407 | Zoninator_REST_Expect::is_a( $bundle, 'Zoninator_REST_Interfaces_Controller_Bundle' ); 408 | $key = $bundle->get_prefix(); 409 | $this->rest_apis[ $key ] = $bundle; 410 | return $this; 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/zoninator_rest/class-zoninator-rest-exception.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 30 | } 31 | 32 | /** 33 | * The name 34 | * 35 | * @return string 36 | */ 37 | public function name() { 38 | return $this->identifier; 39 | } 40 | 41 | /** 42 | * The default value 43 | */ 44 | public function default_value() { 45 | return null; 46 | } 47 | 48 | /** 49 | * Cast value to be Type 50 | * 51 | * @param mixed $value The value that needs casting. 52 | * 53 | * @return mixed 54 | */ 55 | public function cast( $value ) { 56 | return $value; 57 | } 58 | 59 | /** 60 | * Sanitize this value 61 | * 62 | * @param mixed $value The value to sanitize. 63 | * 64 | * @return mixed 65 | */ 66 | public function sanitize( $value ) { 67 | return $value; 68 | } 69 | 70 | /** 71 | * Get this type's JSON Schema. 72 | * 73 | * @return array 74 | */ 75 | public function schema() { 76 | return array( 77 | 'type' => $this->name(), 78 | ); 79 | } 80 | 81 | /** 82 | * Get our "Any" type 83 | * 84 | * @return Zoninator_REST_Type 85 | */ 86 | public static function any() { 87 | return new self( 'any' ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/bundle/class-zoninator-rest-controller-bundle-builder.php: -------------------------------------------------------------------------------- 1 | bundle = $bundle; 45 | } 46 | 47 | /** 48 | * Build it 49 | * 50 | * @return Zoninator_REST_Interfaces_Controller_Bundle 51 | */ 52 | public function build() { 53 | if ( is_a( $this->bundle, 'Zoninator_REST_Interfaces_Controller_Bundle' ) ) { 54 | return $this->bundle; 55 | } 56 | 57 | return new Zoninator_REST_Controller_Bundle( $this->bundle_prefix, $this->endpoint_builders ); 58 | } 59 | 60 | /** 61 | * Prefix. 62 | * 63 | * @param string $bundle_prefix Prefix. 64 | * @return Zoninator_REST_Controller_Bundle_Builder $this 65 | */ 66 | public function with_prefix( $bundle_prefix ) { 67 | $this->bundle_prefix = $bundle_prefix; 68 | return $this; 69 | } 70 | 71 | /** 72 | * Env. 73 | * 74 | * @param Zoninator_REST_Environment $env Env. 75 | * @return Zoninator_REST_Controller_Bundle_Builder $this 76 | */ 77 | public function with_environment( $env ) { 78 | return $this; 79 | } 80 | 81 | /** 82 | * Endpoint. 83 | * 84 | * Adds a new Zoninator_REST_Controller_Builder to our builders and returns it for further setup. 85 | * 86 | * @param null|Zoninator_REST_Interfaces_Controller $controller_object The (optional) controller object. 87 | * @return Zoninator_REST_Controller_Bundle_Builder $this 88 | */ 89 | public function add_endpoint( $controller_object = null ) { 90 | Zoninator_REST_Expect::is_a( $controller_object, 'Zoninator_REST_Interfaces_Controller' ); 91 | $this->endpoint_builders[] = $controller_object; 92 | return $this; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-action.php: -------------------------------------------------------------------------------- 1 | WP_REST_Server::READABLE, 25 | 'show' => WP_REST_Server::READABLE, 26 | 'create' => WP_REST_Server::CREATABLE, 27 | 'update' => WP_REST_Server::EDITABLE, 28 | 'delete' => WP_REST_Server::DELETABLE, 29 | 'any' => WP_REST_Server::ALLMETHODS, 30 | ); 31 | 32 | /** 33 | * The action name 34 | * 35 | * @var string 36 | */ 37 | private $action_name; 38 | 39 | /** 40 | * The Handler 41 | * 42 | * @var null|array|string 43 | */ 44 | private $handler; 45 | 46 | /** 47 | * The Permissions Callback 48 | * 49 | * @var null|array|string 50 | */ 51 | private $permission_callback; 52 | 53 | /** 54 | * The Args 55 | * 56 | * @var null|array|string 57 | */ 58 | private $args; 59 | 60 | /** 61 | * Zoninator_REST_Controller_Action constructor. 62 | * 63 | * @param Zoninator_REST_Controller $controller Controller. 64 | * @param string $action_name The action Name. 65 | */ 66 | public function __construct( $controller, $action_name ) { 67 | $is_known_action = in_array( $action_name, array_keys( $this->actions_to_http_methods ), true ); 68 | Zoninator_REST_Expect::that( $is_known_action, 'Unknown method: ' . $action_name ); 69 | 70 | $this->controller = $controller; 71 | $this->action_name = $action_name; 72 | } 73 | 74 | /** 75 | * Get Name 76 | * 77 | * @return string 78 | */ 79 | public function name() { 80 | return $this->action_name; 81 | } 82 | 83 | /** 84 | * Set Permissions 85 | * 86 | * @param mixed $a_callable A Callable. 87 | * 88 | * @return Zoninator_REST_Controller_Action 89 | */ 90 | public function permissions( $a_callable ) { 91 | $this->permission_callback = $a_callable; 92 | return $this; 93 | } 94 | 95 | /** 96 | * Set Handler 97 | * 98 | * @param mixed $a_callable A Callable. 99 | * 100 | * @return Zoninator_REST_Controller_Action 101 | */ 102 | public function callback( $a_callable ) { 103 | $this->handler = $a_callable; 104 | return $this; 105 | } 106 | 107 | /** 108 | * Set Handler 109 | * 110 | * @param mixed $a_callable A Callable. 111 | * 112 | * @return Zoninator_REST_Controller_Action 113 | */ 114 | public function args( $a_callable ) { 115 | $this->args = $a_callable; 116 | return $this; 117 | } 118 | 119 | /** 120 | * Used in register rest route 121 | * 122 | * @return array 123 | */ 124 | public function as_array() { 125 | $callable_func = $this->expect_callable( $this->handler ); 126 | if ( null !== $this->permission_callback ) { 127 | $permission_callback = $this->expect_callable( $this->permission_callback ); 128 | } else { 129 | $permission_callback = $this->expect_callable( array( $this->controller, $this->action_name . '_permissions_check' ) ); 130 | } 131 | 132 | if ( null !== $this->args ) { 133 | $args = call_user_func( $this->expect_callable( $this->args ), $this->actions_to_http_methods[ $this->action_name ] ); 134 | } else { 135 | $args = $this->controller->get_endpoint_args_for_item_schema( $this->actions_to_http_methods[ $this->action_name ] ); 136 | } 137 | 138 | return array( 139 | 'methods' => $this->actions_to_http_methods[ $this->action_name ], 140 | 'callback' => $callable_func, 141 | 'permission_callback' => $permission_callback, 142 | 'args' => $args, 143 | ); 144 | } 145 | 146 | /** 147 | * Expect a callable 148 | * 149 | * @param mixed $callable_func A Callable. 150 | * @return array 151 | * @throws Zoninator_REST_Exception If not a callable. 152 | */ 153 | private function expect_callable( $callable_func ) { 154 | if ( ! is_callable( $callable_func ) ) { 155 | // Check if controller has a public method called $callable_func. 156 | if ( is_string( $callable_func ) && method_exists( $this->controller, $callable_func ) ) { 157 | return array( $this->controller, $callable_func ); 158 | } 159 | 160 | Zoninator_REST_Expect::that( is_callable( $callable_func ), 'Callable Expected: $callable_func' ); 161 | } 162 | 163 | return $callable_func; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-bundle.php: -------------------------------------------------------------------------------- 1 | prefix = $bundle_prefix; 46 | $this->endpoints = $endpoints; 47 | } 48 | 49 | /** 50 | * Register this bundle with the environment. 51 | * 52 | * @param Zoninator_REST_Environment $environment The Environment. 53 | * @return Zoninator_REST_Controller_Bundle $this 54 | * @throws Zoninator_REST_Exception When no prefix is defined. 55 | */ 56 | public function register( $environment ) { 57 | Zoninator_REST_Expect::that( null !== $this->prefix, 'prefix should be defined' ); 58 | $this->environment = $environment; 59 | /** 60 | * Add/remove endpoints. Useful for extensions 61 | * 62 | * @param array $endpoints An array of Zoninator_REST_Interfaces_Controller 63 | * @param $bundle Zoninator_REST_Controller_Bundle The bundle instance. 64 | * 65 | * @return array 66 | */ 67 | $endpoints = (array) apply_filters( 68 | 'mt_rest_api_controller_bundle_get_endpoints', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 69 | $this->endpoints, 70 | $this 71 | ); 72 | 73 | foreach ( $endpoints as $endpoint ) { 74 | /** 75 | * Controller 76 | */ 77 | $endpoint->register( $this, $this->environment ); 78 | } 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get Prefix. 85 | * 86 | * @return string 87 | */ 88 | public function get_prefix() { 89 | return $this->prefix; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-crud.php: -------------------------------------------------------------------------------- 1 | add_route( '/' ) 22 | ->add_action( $this->action( 'index', array( $this, 'get_items' ) ) ) 23 | ->add_action( $this->action( 'create', array( $this, 'create_item' ) ) ); 24 | 25 | $this->add_route( '/(?P\d+)' ) 26 | ->add_action( $this->action( 'show', array( $this, 'get_item' ) ) ) 27 | ->add_action( $this->action( 'update', array( $this, 'update_item' ) ) ) 28 | ->add_action( $this->action( 'delete', array( $this, 'delete_item' ) ) ); 29 | } 30 | 31 | /** 32 | * Get Items. 33 | * 34 | * @param WP_REST_Request $request Request. 35 | * @return WP_REST_Response 36 | */ 37 | public function get_items( $request ) { 38 | $item_id = isset( $request['id'] ) ? absint( $request['id'] ) : null; 39 | 40 | if ( null === $item_id ) { 41 | $models = $this->get_model_data_store()->get_entities(); 42 | $data = $this->prepare_dto( $models ); 43 | return $this->ok( $data ); 44 | } 45 | 46 | $model = $this->model_prototype->get_data_store()->get_entity( $item_id ); 47 | if ( empty( $model ) ) { 48 | return $this->not_found( __( 'Model not found', 'zoninator' ) ); 49 | } 50 | 51 | return $this->ok( $this->prepare_dto( $model ) ); 52 | } 53 | 54 | /** 55 | * Get Item 56 | * 57 | * @param WP_REST_Request $request Request. 58 | * @return WP_REST_Response 59 | */ 60 | public function get_item( $request ) { 61 | return $this->get_items( $request ); 62 | } 63 | 64 | /** 65 | * Create Item 66 | * 67 | * @param WP_REST_Request $request Request. 68 | * @return WP_REST_Response 69 | */ 70 | public function create_item( $request ) { 71 | $is_update = false; 72 | return $this->create_or_update( $request, $is_update ); 73 | } 74 | 75 | /** 76 | * Update Item 77 | * 78 | * @param WP_REST_Request $request Request. 79 | * @return WP_REST_Response 80 | */ 81 | public function update_item( $request ) { 82 | $is_update = true; 83 | return $this->create_or_update( $request, $is_update ); 84 | } 85 | 86 | /** 87 | * Create Or Update Item 88 | * 89 | * @param WP_REST_Request $request Request. 90 | * @param bool $is_update Is Update. 91 | * 92 | * @return WP_REST_Response 93 | */ 94 | protected function create_or_update( $request, $is_update = false ) { 95 | $model_to_update = null; 96 | if ( $is_update ) { 97 | $id = isset( $request['id'] ) ? absint( $request['id'] ) : null; 98 | if ( ! empty( $id ) ) { 99 | $model_to_update = $this->model_prototype->get_data_store()->get_entity( $id ); 100 | if ( empty( $model_to_update ) ) { 101 | return $this->not_found( 'Model does not exist' ); 102 | } 103 | } 104 | } 105 | 106 | if ( $is_update && $model_to_update ) { 107 | $model = $model_to_update->update_from_array( $request->get_params(), $is_update ); 108 | } else { 109 | $model = $this->get_model_prototype()->new_from_array( $request->get_params() ); 110 | } 111 | 112 | if ( is_wp_error( $model ) ) { 113 | $wp_err = $model; 114 | return $this->bad_request( $wp_err ); 115 | } 116 | 117 | $validation = $model->validate(); 118 | if ( is_wp_error( $validation ) ) { 119 | return $this->bad_request( $validation ); 120 | } 121 | 122 | $id_or_error = $this->model_data_store->upsert( $model ); 123 | 124 | if ( is_wp_error( $id_or_error ) ) { 125 | return $this->bad_request( $id_or_error ); 126 | } 127 | 128 | $dto = $this->prepare_dto( 129 | array( 130 | 'id' => absint( $id_or_error ), 131 | ) 132 | ); 133 | 134 | return $is_update ? $this->ok( $dto ) : $this->created( $dto ); 135 | } 136 | 137 | /** 138 | * Delete an Item 139 | * 140 | * @param WP_REST_Request $request Request. 141 | * @return WP_REST_Response 142 | */ 143 | public function delete_item( $request ) { 144 | $id = isset( $request['id'] ) ? absint( $request['id'] ) : null; 145 | if ( empty( $id ) ) { 146 | return $this->bad_request( 'No Model ID provided' ); 147 | } 148 | 149 | $model = $this->model_prototype->get_data_store()->get_entity( $id ); 150 | if ( null === $model ) { 151 | return $this->not_found( 'Model does not exist' ); 152 | } 153 | 154 | $result = $this->model_data_store->delete( $model ); 155 | return $this->ok( $result ); 156 | } 157 | 158 | /** 159 | * Model To Dto 160 | * 161 | * @param Zoninator_REST_Interfaces_Model $model The Model. 162 | * @return array 163 | */ 164 | protected function model_to_dto( $model ) { 165 | $result = parent::model_to_dto( $model ); 166 | $result['_links'] = $this->add_links( $model ); 167 | return $result; 168 | } 169 | 170 | /** 171 | * Add Links 172 | * 173 | * @param Zoninator_REST_Interfaces_Model $model Model. 174 | * @return array 175 | */ 176 | protected function add_links( $model ) { 177 | $base_url = rest_url() . $this->controller_bundle->get_prefix() . $this->base . '/'; 178 | 179 | $result = array( 180 | 'collection' => array( 181 | array( 182 | 'href' => esc_url( $base_url ), 183 | ), 184 | ), 185 | ); 186 | if ( $model->get_id() ) { 187 | $result['self'] = array( 188 | array( 189 | 'href' => esc_url( $base_url . $model->get_id() ), 190 | ), 191 | ); 192 | } 193 | 194 | if ( $model->has( 'author' ) ) { 195 | $result['author'] = array( 196 | array( 197 | 'href' => esc_url( rest_url() . 'wp/v2/users/' . $model->get( 'author' ) ), 198 | ), 199 | ); 200 | } 201 | 202 | return $result; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-extension.php: -------------------------------------------------------------------------------- 1 | model_definition_name = $model_definition_name; 49 | $this->object_to_extend = $object_to_extend; 50 | } 51 | 52 | /** 53 | * Register This Controller 54 | * 55 | * @param Zoninator_REST_Environment $environment The Environment to use. 56 | * @throws Zoninator_REST_Exception Throws. 57 | * 58 | * @return bool|WP_Error true if valid otherwise error. 59 | */ 60 | public function register( $environment ) { 61 | $this->environment = $environment; 62 | $this->model_definition = $this->environment->model( $this->model_definition_name ); 63 | if ( ! $this->model_definition ) { 64 | return new WP_Error( 'model-not-found' ); 65 | } 66 | 67 | $fields = $this->model_definition->get_fields(); 68 | foreach ( $fields as $field ) { 69 | $this->register_field( $field ); 70 | } 71 | 72 | return true; 73 | } 74 | 75 | /** 76 | * Register Field 77 | * 78 | * @param Zoninator_REST_Field_Declaration $field Field. 79 | */ 80 | private function register_field( $field ) { 81 | register_rest_field( 82 | $this->object_to_extend, 83 | $field->get_data_transfer_name(), 84 | array( 85 | 'get_callback' => $field->get_reader(), 86 | 'update_callback' => $field->get_updater(), 87 | 'schema' => $field->as_item_schema_property(), 88 | ) 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-model.php: -------------------------------------------------------------------------------- 1 | base = $base; 47 | $this->model_class_name = $model_class_name; 48 | } 49 | 50 | /** 51 | * Get our model factory 52 | * 53 | * @return Zoninator_REST_Model 54 | */ 55 | protected function get_model_prototype() { 56 | return $this->model_prototype; 57 | } 58 | 59 | /** 60 | * Register this controller, initialize model-related object fields. 61 | * 62 | * @param Zoninator_REST_Controller_Bundle $bundle The bundle to use. 63 | * @param Zoninator_REST_Environment $environment The Environment. 64 | * 65 | * @throws Zoninator_REST_Exception If an invalid model is provided. 66 | * 67 | * @return bool|WP_Error true if valid otherwise error. 68 | */ 69 | public function register( $bundle, $environment ) { 70 | $this->model_prototype = $environment->model( $this->model_class_name ); 71 | $this->model_data_store = $this->model_prototype->get_data_store(); 72 | return parent::register( $bundle, $environment ); 73 | } 74 | 75 | /** 76 | * Retrieves the item's schema, conforming to JSON Schema. 77 | * 78 | * In our case, it gets fields/types from our definition's declared fields. 79 | * 80 | * @access public 81 | * 82 | * @return array Item schema data. 83 | */ 84 | public function get_item_schema() { 85 | $model_definition = $this->get_model_prototype(); 86 | $fields = $model_definition->get_fields(); 87 | $properties = array(); 88 | $required = array(); 89 | foreach ( $fields as $field_declaration ) { 90 | /** 91 | * Our declaration 92 | * 93 | * @var Zoninator_REST_Field_Declaration $field_declaration 94 | */ 95 | $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property(); 96 | if ( $field_declaration->is_required() ) { 97 | $required[] = $field_declaration->get_data_transfer_name(); 98 | } 99 | } 100 | 101 | $schema = array( 102 | '$schema' => 'http://json-schema.org/schema#', 103 | 'title' => $model_definition->get_name(), 104 | 'type' => 'object', 105 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- breaking change to fix prefix. 106 | 'properties' => (array) apply_filters( 'mixtape_rest_api_schema_properties', $properties, $this->get_model_prototype() ), 107 | ); 108 | 109 | if ( array() !== $required ) { 110 | $schema['required'] = $required; 111 | } 112 | 113 | return $this->add_additional_fields_schema( $schema ); 114 | } 115 | 116 | /** 117 | * Get Model DataStore 118 | * 119 | * @return Zoninator_REST_Interfaces_Data_Store 120 | */ 121 | protected function get_model_data_store() { 122 | return $this->model_data_store; 123 | } 124 | 125 | /** 126 | * Generic Permissions Check. 127 | * 128 | * @param WP_REST_Request $request Request. 129 | * @param string $action One of (index, show, create, update, delete). 130 | * @return bool 131 | */ 132 | public function permissions_check( $request, $action = 'any' ) { 133 | return $this->get_model_prototype()->permissions_check( $request, $action ); 134 | } 135 | 136 | /** 137 | * Prepare Entity to be a DTO 138 | * 139 | * @param array|Zoninator_REST_Model_Collection|Zoninator_REST_Interfaces_Model $entity The Entity. 140 | * @return array 141 | */ 142 | protected function prepare_dto( $entity ) { 143 | if ( is_a( $entity, 'Zoninator_REST_Model_Collection' ) ) { 144 | $results = array(); 145 | foreach ( $entity->get_items() as $model ) { 146 | $results[] = $this->model_to_dto( $model ); 147 | } 148 | 149 | return $results; 150 | } 151 | 152 | if ( is_a( $entity, 'Zoninator_REST_Interfaces_Model' ) ) { 153 | return $this->model_to_dto( $entity ); 154 | } 155 | 156 | return $entity; 157 | } 158 | 159 | /** 160 | * Map a model to a Data Transfer Object (plain array) 161 | * 162 | * @param Zoninator_REST_Interfaces_Model $model The Model. 163 | * @return array 164 | */ 165 | protected function model_to_dto( $model ) { 166 | return $model->to_dto(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-route.php: -------------------------------------------------------------------------------- 1 | pattern = $pattern; 39 | } 40 | 41 | /** 42 | * Add/Get an action 43 | * 44 | * @param Zoninator_REST_Controller_Action $action Action. 45 | * 46 | * @return Zoninator_REST_Controller_Route 47 | */ 48 | public function add_action( $action ) { 49 | $this->actions[ $action->name() ] = $action; 50 | return $this; 51 | } 52 | 53 | /** 54 | * Gets Route info to use in Register rest route. 55 | * 56 | * @throws Zoninator_REST_Exception If invalid callable. 57 | * @return array 58 | */ 59 | public function as_array() { 60 | $result = array(); 61 | $result['pattern'] = $this->pattern; 62 | $result['actions'] = array(); 63 | foreach ( $this->actions as $route_action ) { 64 | /** 65 | * The route action. 66 | * 67 | * @var Zoninator_REST_Controller_Action $route_action 68 | */ 69 | $result['actions'][] = $route_action->as_array(); 70 | } 71 | 72 | return $result; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/zoninator_rest/controller/class-zoninator-rest-controller-settings.php: -------------------------------------------------------------------------------- 1 | add_route() 22 | ->add_action( $this->action( 'index', array( $this, 'get_items' ) ) ) 23 | ->add_action( $this->action( 'update', array( $this, 'create_item' ) ) ); 24 | } 25 | 26 | /** 27 | * Get Settings 28 | * 29 | * @param WP_REST_Request $request The request. 30 | * @return WP_REST_Response 31 | */ 32 | public function get_items( $request ) { 33 | $model = $this->model_prototype->get_data_store()->get_entity( null ); 34 | if ( empty( $model ) ) { 35 | return $this->not_found( __( 'Settings not found', 'zoninator' ) ); 36 | } 37 | 38 | return $this->ok( $this->prepare_dto( $model ) ); 39 | } 40 | 41 | /** 42 | * Create or Update settings. 43 | * 44 | * @param WP_REST_Request $request Request. 45 | * @return WP_REST_Response 46 | */ 47 | public function create_item( $request ) { 48 | return $this->create_or_update( $request ); 49 | } 50 | 51 | /** 52 | * Create or Update a Model 53 | * 54 | * @param WP_REST_Request $request Request. 55 | * @return WP_REST_Response 56 | */ 57 | protected function create_or_update( $request ) { 58 | $is_update = $request->get_method() !== 'POST'; 59 | $model_to_update = $this->model_prototype->get_data_store()->get_entity( null ); 60 | if ( empty( $model_to_update ) ) { 61 | return $this->not_found( 'Model does not exist' ); 62 | } 63 | 64 | $model = $model_to_update->update_from_array( $request->get_params(), true ); 65 | 66 | if ( is_wp_error( $model ) ) { 67 | return $this->bad_request( $model ); 68 | } 69 | 70 | $validation = $model->validate(); 71 | if ( is_wp_error( $validation ) ) { 72 | return $this->bad_request( $validation ); 73 | } 74 | 75 | $id_or_error = $this->model_data_store->upsert( $model ); 76 | 77 | if ( is_wp_error( $id_or_error ) ) { 78 | return $this->bad_request( $id_or_error ); 79 | } 80 | 81 | $model = $this->model_prototype->get_data_store()->get_entity( null ); 82 | $dto = $this->prepare_dto( $model ); 83 | 84 | return $is_update ? $this->ok( $dto ) : $this->created( $dto ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/class-zoninator-rest-data-mapper.php: -------------------------------------------------------------------------------- 1 | definition = $definition; 38 | $this->serializer = $serializer; 39 | } 40 | 41 | /** 42 | * Transform raw data to model data 43 | * 44 | * @param array $data Data. 45 | * @param array $field_declarations Declarations. 46 | * @return array 47 | */ 48 | public function raw_data_to_model_data( $data, $field_declarations ) { 49 | $raw_data = array(); 50 | $post_array_keys = array_keys( $data ); 51 | foreach ( $field_declarations as $declaration ) { 52 | /** 53 | * Declaration 54 | * 55 | * @var Zoninator_REST_Field_Declaration $declaration 56 | */ 57 | $key = $declaration->get_name(); 58 | $mapping = $declaration->get_map_from(); 59 | $value = null; 60 | if ( in_array( $key, $post_array_keys, true ) ) { 61 | // simplest case: we got a $key for this, so just map it. 62 | $value = $this->serializer->deserialize( $declaration, $data[ $key ] ); 63 | } elseif ( in_array( $mapping, $post_array_keys, true ) ) { 64 | $value = $this->serializer->deserialize( $declaration, $data[ $mapping ] ); 65 | } else { 66 | $value = $declaration->get_default_value(); 67 | } 68 | 69 | $raw_data[ $key ] = $declaration->cast_value( $value ); 70 | } 71 | 72 | return $raw_data; 73 | } 74 | 75 | /** 76 | * Transform Model to raw data array 77 | * 78 | * @param Zoninator_REST_Interfaces_Model $model Model. 79 | * @param null|string $field_type Type. 80 | * @return array 81 | */ 82 | public function model_to_data( $model, $field_type = null ) { 83 | $field_values_to_insert = array(); 84 | foreach ( $this->definition->get_field_declarations( $field_type ) as $field_declaration ) { 85 | /** 86 | * Declaration 87 | * 88 | * @var Zoninator_REST_Field_Declaration $field_declaration 89 | */ 90 | $what_to_map_to = $field_declaration->get_map_from(); 91 | $value = $model->get( $field_declaration->get_name() ); 92 | $field_values_to_insert[ $what_to_map_to ] = $this->serializer->serialize( $field_declaration, $value ); 93 | } 94 | 95 | return $field_values_to_insert; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/class-zoninator-rest-data-serializer.php: -------------------------------------------------------------------------------- 1 | model_declaration = $model_definition->get_model_declaration(); 30 | } 31 | 32 | /** 33 | * Deserialize 34 | * 35 | * @param Zoninator_REST_Field_Declaration $field_declaration Declaration. 36 | * @param mixed $value Value. 37 | * @return mixed the deserialized value 38 | */ 39 | public function deserialize( $field_declaration, $value ) { 40 | $deserializer = $field_declaration->get_deserializer(); 41 | return $deserializer ? $this->model_declaration->call( $deserializer, array( $value ) ) : $value; 42 | } 43 | 44 | /** 45 | * Serialize 46 | * 47 | * @param Zoninator_REST_Field_Declaration $field_declaration Declaration. 48 | * @param mixed $value Value. 49 | * @return mixed 50 | * @throws Zoninator_REST_Exception If call fails. 51 | */ 52 | public function serialize( $field_declaration, $value ) { 53 | $serializer = $field_declaration->get_serializer(); 54 | if ( isset( $serializer ) && ! empty( $serializer ) ) { 55 | return $this->model_declaration->call( $serializer, array( $value ) ); 56 | } 57 | 58 | return $value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/store/class-zoninator-rest-data-store-abstract.php: -------------------------------------------------------------------------------- 1 | args = $args; 38 | Zoninator_REST_Expect::is_a( $model_prototype, 'Zoninator_REST_Interfaces_Model' ); 39 | $this->set_model_factory( $model_prototype ); 40 | } 41 | 42 | /** 43 | * Set Definition 44 | * 45 | * @param Zoninator_REST_Model $factory Def. 46 | * 47 | * @return Zoninator_REST_Interfaces_Data_Store $this 48 | */ 49 | private function set_model_factory( $factory ) { 50 | $this->model_prototype = $factory; 51 | $this->configure(); 52 | return $this; 53 | } 54 | 55 | /** 56 | * Configure 57 | */ 58 | protected function configure() { 59 | } 60 | 61 | /** 62 | * Get Definition 63 | * 64 | * @return Zoninator_REST_Model 65 | */ 66 | public function get_model_prototype() { 67 | return $this->model_prototype; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/store/class-zoninator-rest-data-store-builder.php: -------------------------------------------------------------------------------- 1 | store_class = $data_store_class; 51 | return $this; 52 | } 53 | 54 | /** 55 | * Set Args 56 | * 57 | * @param array $args Args. 58 | * @return Zoninator_REST_Data_Store_Builder $this 59 | */ 60 | public function with_args( $args ) { 61 | $this->args = $args; 62 | return $this; 63 | } 64 | 65 | /** 66 | * Set Model Definition 67 | * 68 | * @param string|Zoninator_REST_Model_Definition $model_definition Def. 69 | * @return Zoninator_REST_Data_Store_Builder $this 70 | */ 71 | public function with_model_definition( $model_definition ) { 72 | $this->model_definition = $model_definition; 73 | return $this; 74 | } 75 | 76 | /** 77 | * Build 78 | * 79 | * @return Zoninator_REST_Interfaces_Data_Store 80 | */ 81 | public function build() { 82 | $store_class = $this->store_class; 83 | return new $store_class( $this->model_definition, $this->args ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/store/class-zoninator-rest-data-store-customposttype.php: -------------------------------------------------------------------------------- 1 | post_type = $args['post_type'] ?? 'post'; 31 | parent::__construct( $model_prototype, $args ); 32 | } 33 | 34 | /** 35 | * Get Entities 36 | * 37 | * @param null|mixed $filter Filter. 38 | * 39 | * @return Zoninator_REST_Model_Collection 40 | */ 41 | public function get_entities( $filter = null ) { 42 | $query = new WP_Query( 43 | array( 44 | 'post_type' => $this->post_type, 45 | 'post_status' => 'any', 46 | ) 47 | ); 48 | $posts = $query->get_posts(); 49 | $collection = array(); 50 | foreach ( $posts as $post ) { 51 | $collection[] = $this->create_from_post( $post ); 52 | } 53 | 54 | return new Zoninator_REST_Model_Collection( $collection ); 55 | } 56 | 57 | /** 58 | * Get Entity 59 | * 60 | * @param int $id The id of the entity. 61 | * @return Zoninator_REST_Model|null 62 | */ 63 | public function get_entity( $id ) { 64 | $post = get_post( absint( $id ) ); 65 | if ( empty( $post ) || $post->post_type !== $this->post_type ) { 66 | return null; 67 | } 68 | 69 | return $this->create_from_post( $post ); 70 | } 71 | 72 | /** 73 | * Create from Post. 74 | * 75 | * @param WP_Post $post Post. 76 | * @return Zoninator_REST_Model 77 | * @throws Zoninator_REST_Exception If something goes wrong. 78 | */ 79 | private function create_from_post( $post ) { 80 | $raw_post_data = $post->to_array(); 81 | $raw_meta_data = get_post_meta( $post->ID ); // assumes we are only ever adding one postmeta per key. 82 | 83 | $flattened_meta = array(); 84 | foreach ( $raw_meta_data as $key => $value_arr ) { 85 | $flattened_meta[ $key ] = $value_arr[0]; 86 | } 87 | 88 | $merged_data = array_merge( $raw_post_data, $flattened_meta ); 89 | 90 | return $this->get_model_prototype()->create( 91 | $merged_data, 92 | array( 93 | 'deserialize' => true, 94 | ) 95 | ); 96 | } 97 | 98 | /** 99 | * Delete 100 | * 101 | * @param Zoninator_REST_Interfaces_Model $model Model. 102 | * @param array $args Args. 103 | * @return mixed 104 | */ 105 | public function delete( $model, $args = array() ) { 106 | $id = $model->get_id(); 107 | 108 | $args = wp_parse_args( 109 | $args, 110 | array( 111 | 'force_delete' => false, 112 | ) 113 | ); 114 | 115 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 116 | do_action( 'mixtape_data_store_delete_model_before', $model, $id ); 117 | 118 | if ( $args['force_delete'] ) { 119 | $result = wp_delete_post( $model->get_id() ); 120 | $model->set( 'id', 0 ); 121 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 122 | do_action( 'mixtape_data_store_delete_model', $model, $id ); 123 | } else { 124 | $result = wp_trash_post( $model->get_id() ); 125 | $model->set( 'status', 'trash' ); 126 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 127 | do_action( 'mixtape_data_store_trash_model', $model, $id ); 128 | } 129 | 130 | if ( false === $result ) { 131 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 132 | do_action( 'mixtape_data_store_delete_model_fail', $model, $id ); 133 | return new WP_Error( 'delete-failed', 'delete-failed' ); 134 | } 135 | 136 | return $result; 137 | } 138 | 139 | /** 140 | * Upsert 141 | * 142 | * @param Zoninator_REST_Interfaces_Model $model Model. 143 | * 144 | * @return mixed|WP_Error 145 | */ 146 | public function upsert( $model ) { 147 | $id = $model->get_id(); 148 | $updating = ! empty( $id ); 149 | $fields = $model->serialize( Zoninator_REST_Field_Declaration::FIELD ); 150 | $meta_fields = $model->serialize( Zoninator_REST_Field_Declaration::META ); 151 | if ( ! isset( $fields['post_type'] ) ) { 152 | $fields['post_type'] = $this->post_type; 153 | } 154 | 155 | if ( isset( $fields['ID'] ) && empty( $fields['ID'] ) ) { 156 | // ID of 0 is not acceptable on CPTs, so remove it. 157 | unset( $fields['ID'] ); 158 | } 159 | 160 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 161 | do_action( 'mixtape_data_store_model_upsert_before', $model ); 162 | 163 | $id_or_error = wp_insert_post( $fields, true ); 164 | if ( is_wp_error( $id_or_error ) ) { 165 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 166 | do_action( 'mixtape_data_store_model_upsert_error', $model ); 167 | return $id_or_error; 168 | } 169 | 170 | $model->set( 'id', absint( $id_or_error ) ); 171 | foreach ( $meta_fields as $meta_key => $meta_value ) { 172 | if ( $updating ) { 173 | $id_or_bool = update_post_meta( $id_or_error, $meta_key, $meta_value ); 174 | } else { 175 | $id_or_bool = add_post_meta( $id_or_error, $meta_key, $meta_value ); 176 | } 177 | 178 | if ( false === $id_or_bool ) { 179 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 180 | do_action( 'mixtape_data_store_model_upsert_error', $model ); 181 | // Something was wrong with this update/create. TODO: Should we stop mid create/update? 182 | return new WP_Error( 183 | 'mixtape-error-creating-meta', 184 | 'There was an error updating/creating an entity field', 185 | array( 186 | 'field_key' => $meta_key, 187 | 'field_value' => $meta_value, 188 | ) 189 | ); 190 | } 191 | } 192 | 193 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 194 | do_action( 'mixtape_data_store_model_upsert_after', $model ); 195 | 196 | return absint( $id_or_error ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/zoninator_rest/data/store/class-zoninator-rest-data-store-nil.php: -------------------------------------------------------------------------------- 1 | does_not_exist_guard = new stdClass(); 33 | } 34 | 35 | /** 36 | * Get Entities 37 | * 38 | * @param null|mixed $filter Filter. 39 | * @return Zoninator_REST_Interfaces_Model 40 | */ 41 | public function get_entities( $filter = null ) { 42 | // there is only one option bag and one option bag global per data store. 43 | return $this->get_entity( null ); 44 | } 45 | 46 | /** 47 | * Get Entity 48 | * 49 | * @param int $id The id of the entity. 50 | * @return Zoninator_REST_Interfaces_Model 51 | */ 52 | public function get_entity( $id ) { 53 | $field_declarations = $this->get_model_prototype()->get_fields(); 54 | $raw_data = array(); 55 | foreach ( $field_declarations as $field_declaration ) { 56 | /** 57 | * Field Declaration 58 | * 59 | * @var Zoninator_REST_Field_Declaration $field_declaration 60 | */ 61 | $option = get_option( $field_declaration->get_map_from(), $this->does_not_exist_guard ); 62 | if ( $this->does_not_exist_guard !== $option ) { 63 | $raw_data[ $field_declaration->get_map_from() ] = $option; 64 | } 65 | } 66 | 67 | return $this->get_model_prototype()->create( 68 | $raw_data, 69 | array( 70 | 'deserialize' => true, 71 | ) 72 | ); 73 | } 74 | 75 | /** 76 | * Delete 77 | * 78 | * @param Zoninator_REST_Interfaces_Model $model Model. 79 | * @param array $args Args. 80 | * @return mixed 81 | */ 82 | public function delete( $model, $args = array() ) { 83 | $options_to_delete = array_keys( $model->serialize() ); 84 | foreach ( $options_to_delete as $option_to_delete ) { 85 | if ( false !== get_option( $option_to_delete, false ) ) { 86 | $result = delete_option( $option_to_delete ); 87 | if ( false === $result ) { 88 | return new WP_Error( 'delete-option-failed' ); 89 | } 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | 96 | /** 97 | * Update/Insert 98 | * 99 | * @param Zoninator_REST_Interfaces_Model $model Model. 100 | * @return mixed 101 | */ 102 | public function upsert( $model ) { 103 | $fields_for_insert = $model->serialize(); 104 | foreach ( $fields_for_insert as $option_name => $option_value ) { 105 | $previous_value = get_option( $option_name, $this->does_not_exist_guard ); 106 | if ( $this->does_not_exist_guard !== $previous_value ) { 107 | update_option( $option_name, $option_value ); 108 | } else { 109 | add_option( $option_name, $option_value ); 110 | } 111 | } 112 | 113 | return true; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/zoninator_rest/field/class-zoninator-rest-field-declaration.php: -------------------------------------------------------------------------------- 1 | field_kinds, true ) ) { 190 | throw new Zoninator_REST_Exception( esc_html( 'every field should have a kind (one of ' . implode( ',', $this->field_kinds ) . ')' ) ); 191 | } 192 | 193 | $this->name = $args['name']; 194 | $this->description = $this->value_or_default( $args, 'description', '' ); 195 | 196 | $this->kind = $args['kind']; 197 | $this->type = $this->value_or_default( $args, 'type', Zoninator_REST_Type::any() ); 198 | $this->choices = $this->value_or_default( $args, 'choices', null ); 199 | $this->default_value = $this->value_or_default( $args, 'default_value' ); 200 | 201 | $this->map_from = $this->value_or_default( $args, 'map_from' ); 202 | $this->data_transfer_name = $this->value_or_default( $args, 'data_transfer_name', $this->get_name() ); 203 | 204 | $this->primary = $this->value_or_default( $args, 'primary', false ); 205 | $this->required = $this->value_or_default( $args, 'required', false ); 206 | $this->supported_outputs = $this->value_or_default( $args, 'supported_outputs', array( 'json' ) ); 207 | 208 | $this->sanitizer = $this->value_or_default( $args, 'sanitizer' ); 209 | $this->validations = $this->value_or_default( $args, 'validations', array() ); 210 | 211 | $this->serializer = $this->value_or_default( $args, 'serializer' ); 212 | $this->deserializer = $this->value_or_default( $args, 'deserializer' ); 213 | 214 | $this->before_get = $this->value_or_default( $args, 'before_get' ); 215 | $this->before_set = $this->value_or_default( $args, 'before_set' ); 216 | 217 | $this->reader = $this->value_or_default( $args, 'reader' ); 218 | $this->updater = $this->value_or_default( $args, 'updater' ); 219 | } 220 | 221 | /** 222 | * Get possible choices if set 223 | * 224 | * @return null|array 225 | */ 226 | public function get_choices() { 227 | return $this->choices; 228 | } 229 | 230 | /** 231 | * Get Sanitizer 232 | * 233 | * @return callable|null 234 | */ 235 | public function get_sanitizer() { 236 | return $this->sanitizer; 237 | } 238 | 239 | /** 240 | * Value or Default 241 | * 242 | * @param array $args Args. 243 | * @param string $name Name. 244 | * @param mixed $default_value Default. 245 | */ 246 | private function value_or_default( $args, $name, $default_value = null ) { 247 | return $args[ $name ] ?? $default_value; 248 | } 249 | 250 | /** 251 | * Is Kind 252 | * 253 | * @param string $kind The kind. 254 | * @return bool 255 | */ 256 | public function is_kind( $kind ) { 257 | if ( ! in_array( $kind, $this->field_kinds, true ) ) { 258 | return false; 259 | } 260 | 261 | return $this->kind === $kind; 262 | } 263 | 264 | /** 265 | * Get default value 266 | * 267 | * @return mixed 268 | */ 269 | public function get_default_value() { 270 | if ( null !== $this->default_value && ! empty( $this->default_value ) ) { 271 | return ( is_array( $this->default_value ) && is_callable( $this->default_value ) ) ? call_user_func( $this->default_value ) : $this->default_value; 272 | } 273 | 274 | return $this->type->default_value(); 275 | } 276 | 277 | /** 278 | * Cast a value 279 | * 280 | * @param mixed $value Val. 281 | * @return mixed 282 | */ 283 | public function cast_value( $value ) { 284 | return $this->type->cast( $value ); 285 | } 286 | 287 | /** 288 | * Supports this type of output. 289 | * 290 | * @param string $type Type. 291 | * @return bool 292 | */ 293 | public function supports_output_type( $type ) { 294 | return in_array( $type, $this->supported_outputs, true ); 295 | } 296 | 297 | /** 298 | * As Item Schema Property 299 | * 300 | * @return array 301 | */ 302 | public function as_item_schema_property() { 303 | $schema = $this->type->schema(); 304 | $schema['context'] = array( 'view', 'edit' ); 305 | $schema['description'] = $this->get_description(); 306 | 307 | if ( $this->get_choices() ) { 308 | $schema['enum'] = (array) $this->get_choices(); 309 | } 310 | 311 | return $schema; 312 | } 313 | 314 | /** 315 | * Get Map From 316 | */ 317 | public function get_map_from() { 318 | if ( null !== $this->map_from && ! empty( $this->map_from ) ) { 319 | return $this->map_from; 320 | } 321 | 322 | return $this->get_name(); 323 | } 324 | 325 | /** 326 | * Get Kind 327 | * 328 | * @return mixed 329 | */ 330 | public function get_kind() { 331 | return $this->kind; 332 | } 333 | 334 | /** 335 | * Get Name 336 | * 337 | * @return mixed 338 | */ 339 | public function get_name() { 340 | return $this->name; 341 | } 342 | 343 | /** 344 | * Is Primary 345 | * 346 | * @return bool 347 | */ 348 | public function is_primary() { 349 | return (bool) $this->primary; 350 | } 351 | 352 | /** 353 | * Is Required 354 | * 355 | * @return bool 356 | */ 357 | public function is_required() { 358 | return (bool) $this->required; 359 | } 360 | 361 | /** 362 | * Get Description 363 | * 364 | * @return string 365 | */ 366 | public function get_description() { 367 | if ( null !== $this->description && ! empty( $this->description ) ) { 368 | return $this->description; 369 | } 370 | 371 | return ucfirst( str_replace( '_', ' ', $this->get_name() ) ); 372 | } 373 | 374 | /** 375 | * Get Dto name 376 | * 377 | * @return string 378 | */ 379 | public function get_data_transfer_name() { 380 | return $this->data_transfer_name ?? $this->get_name(); 381 | } 382 | 383 | /** 384 | * Get Validations 385 | * 386 | * @return array 387 | */ 388 | public function get_validations() { 389 | return $this->validations; 390 | } 391 | 392 | /** 393 | * Get Before get 394 | * 395 | * @return callable|null 396 | */ 397 | public function before_get() { 398 | return $this->before_get; 399 | } 400 | 401 | /** 402 | * Get Serializer 403 | * 404 | * @return callable|null 405 | */ 406 | public function get_serializer() { 407 | return $this->serializer; 408 | } 409 | 410 | /** 411 | * Get Deserializer 412 | * 413 | * @return callable|null 414 | */ 415 | public function get_deserializer() { 416 | return $this->deserializer; 417 | } 418 | 419 | /** 420 | * Get Type 421 | * 422 | * @return Zoninator_REST_Interfaces_Type 423 | */ 424 | public function get_type() { 425 | return $this->type; 426 | } 427 | 428 | /** 429 | * Before Set 430 | * 431 | * @return callable|null 432 | */ 433 | public function before_set() { 434 | return $this->before_set; 435 | } 436 | 437 | /** 438 | * Get Reader 439 | * 440 | * @return callable|null 441 | */ 442 | public function get_reader() { 443 | return $this->reader; 444 | } 445 | 446 | /** 447 | * Get Updater 448 | * 449 | * @return callable|null 450 | */ 451 | public function get_updater() { 452 | return $this->updater; 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /src/zoninator_rest/field/declaration/class-zoninator-rest-field-declaration-builder.php: -------------------------------------------------------------------------------- 1 | args = array( 25 | 'name' => '', 26 | 'kind' => Zoninator_REST_Field_Declaration::FIELD, 27 | 'type' => Zoninator_REST_Type::any(), 28 | 'required' => false, 29 | 'map_from' => null, 30 | 31 | 'sanitizer' => null, 32 | 33 | 'serializer' => null, 34 | 'deserializer' => null, 35 | 36 | 'default_value' => null, 37 | 'data_transfer_name' => null, 38 | 'supported_outputs' => array( 'json' ), 39 | 'description' => null, 40 | 'validations' => array(), 41 | 'choices' => null, 42 | 'contexts' => array( 'view', 'edit' ), 43 | 'before_set' => null, 44 | 'before_get' => null, 45 | 'reader' => null, 46 | 'updater' => null, 47 | ); 48 | } 49 | 50 | /** 51 | * Build it 52 | * 53 | * @return Zoninator_REST_Field_Declaration 54 | */ 55 | public function build() { 56 | return new Zoninator_REST_Field_Declaration( $this->args ); 57 | } 58 | 59 | /** 60 | * Default Value. 61 | * 62 | * @param mixed $default_value Default. 63 | * @return Zoninator_REST_Field_Declaration_Builder 64 | */ 65 | public function with_default( $default_value ) { 66 | return $this->with( 'default_value', $default_value ); 67 | } 68 | 69 | /** 70 | * With Name 71 | * 72 | * @param string $name Name. 73 | * @return Zoninator_REST_Field_Declaration_Builder 74 | */ 75 | public function with_name( $name ) { 76 | return $this->with( 'name', $name ); 77 | } 78 | 79 | /** 80 | * With Kind 81 | * 82 | * @param string $kind Kind. 83 | * @return Zoninator_REST_Field_Declaration_Builder 84 | */ 85 | public function with_kind( $kind ) { 86 | return $this->with( 'kind', $kind ); 87 | } 88 | 89 | /** 90 | * With Map From 91 | * 92 | * @param string $mapped_from Mapped From. 93 | * @return Zoninator_REST_Field_Declaration_Builder 94 | */ 95 | public function with_map_from( $mapped_from ) { 96 | return $this->with( 'map_from', $mapped_from ); 97 | } 98 | 99 | /** 100 | * With Sanitizer 101 | * 102 | * @param callable $sanitizer Sanitizer. 103 | * @return Zoninator_REST_Field_Declaration_Builder 104 | */ 105 | public function with_sanitizer( $sanitizer ) { 106 | $this->expect_is_callable( $sanitizer, __METHOD__ ); 107 | return $this->with( 'sanitizer', $sanitizer ); 108 | } 109 | 110 | /** 111 | * With Serializer 112 | * 113 | * @param callable $serializer Serializer. 114 | * @return Zoninator_REST_Field_Declaration_Builder 115 | */ 116 | public function with_serializer( $serializer ) { 117 | return $this->with( 'serializer', $serializer ); 118 | } 119 | 120 | /** 121 | * With Deserializer 122 | * 123 | * @param callable $deserializer Deserializer. 124 | * @return Zoninator_REST_Field_Declaration_Builder 125 | */ 126 | public function with_deserializer( $deserializer ) { 127 | return $this->with( 'deserializer', $deserializer ); 128 | } 129 | 130 | /** 131 | * With Required 132 | * 133 | * @param bool $required Req. 134 | * @return Zoninator_REST_Field_Declaration_Builder 135 | */ 136 | public function with_required( $required = true ) { 137 | return $this->with( 'required', $required ); 138 | } 139 | 140 | /** 141 | * With Supported Outputs 142 | * 143 | * @param array $supported_outputs Outputs. 144 | * @return Zoninator_REST_Field_Declaration_Builder 145 | */ 146 | public function with_supported_outputs( $supported_outputs = array() ) { 147 | return $this->with( 'supported_outputs', (array) $supported_outputs ); 148 | } 149 | 150 | /** 151 | * Set the type definition of this field declaration 152 | * 153 | * @param Zoninator_REST_Interfaces_Type $value_type Type. 154 | * @return Zoninator_REST_Field_Declaration_Builder $this 155 | * 156 | * @throws Zoninator_REST_Exception When not a type. 157 | */ 158 | public function with_type( $value_type ) { 159 | if ( ! is_a( $value_type, 'Zoninator_REST_Interfaces_Type' ) ) { 160 | throw new Zoninator_REST_Exception( esc_html( get_class( $value_type ) . ' is not a Mixtape_Interfaces_Type' ) ); 161 | } 162 | 163 | return $this->with( 'type', $value_type ); 164 | } 165 | 166 | /** 167 | * With Dto Name 168 | * 169 | * @param string $dto_name Dto Name. 170 | * @return Zoninator_REST_Field_Declaration_Builder 171 | */ 172 | public function with_dto_name( $dto_name ) { 173 | return $this->with( 'data_transfer_name', $dto_name ); 174 | } 175 | 176 | /** 177 | * With Description 178 | * 179 | * @param string $description Description. 180 | * @return Zoninator_REST_Field_Declaration_Builder 181 | */ 182 | public function with_description( $description ) { 183 | return $this->with( 'description', $description ); 184 | } 185 | 186 | /** 187 | * With Validations 188 | * 189 | * @param array|mixed $validations Validations. 190 | * @return Zoninator_REST_Field_Declaration_Builder 191 | */ 192 | public function with_validations( $validations ) { 193 | if ( is_callable( $validations ) || ! is_array( $validations ) ) { 194 | $validations = array( $validations ); 195 | } 196 | 197 | return $this->with( 'validations', $validations ); 198 | } 199 | 200 | /** 201 | * Before Set 202 | * 203 | * @param callable $before_set Before set. 204 | * @return Zoninator_REST_Field_Declaration_Builder 205 | */ 206 | public function with_before_set( $before_set ) { 207 | return $this->with( 'before_set', $before_set ); 208 | } 209 | 210 | /** 211 | * Before Get 212 | * 213 | * @param callable $before_get Before get. 214 | * @return Zoninator_REST_Field_Declaration_Builder 215 | */ 216 | public function with_before_get( $before_get ) { 217 | return $this->with( 'before_get', $before_get ); 218 | } 219 | 220 | /** 221 | * Choices. 222 | * 223 | * @param array|mixed $choices Choices. 224 | * 225 | * @return $this|Zoninator_REST_Field_Declaration_Builder 226 | */ 227 | public function with_choices( $choices ) { 228 | if ( empty( $choices ) ) { 229 | return $this; 230 | } 231 | 232 | return $this->with( 'choices', is_array( $choices ) ? $choices : array( $choices ) ); 233 | } 234 | 235 | /** 236 | * Set 237 | * 238 | * @param string $name Name. 239 | * @param mixed $value Value. 240 | * @return Zoninator_REST_Field_Declaration_Builder $this 241 | */ 242 | private function with( $name, $value ) { 243 | $this->args[ $name ] = $value; 244 | return $this; 245 | } 246 | 247 | /** 248 | * Derived Field 249 | * 250 | * @param callable $func The func. 251 | * 252 | * @return Zoninator_REST_Field_Declaration_Builder 253 | */ 254 | public function derived( $func = null ) { 255 | if ( $func ) { 256 | $this->with_map_from( $func ); 257 | } 258 | 259 | return $this->with_kind( Zoninator_REST_Field_Declaration::DERIVED ); 260 | } 261 | 262 | /** 263 | * Set Updater 264 | * 265 | * @param callable $func Func. 266 | * @return Zoninator_REST_Field_Declaration_Builder $this 267 | * @throws Zoninator_REST_Exception When no callable. 268 | */ 269 | public function with_updater( $func ) { 270 | return $this->with( 'updater', $func ); 271 | } 272 | 273 | /** 274 | * Set reader 275 | * 276 | * @param callable $func Func. 277 | * @return Zoninator_REST_Field_Declaration_Builder $this 278 | * @throws Zoninator_REST_Exception When no callable. 279 | */ 280 | public function with_reader( $func ) { 281 | return $this->with( 'reader', $func ); 282 | } 283 | 284 | /** 285 | * Callable test 286 | * 287 | * @param callable|mixed $thing Thing to test. 288 | * @param string $func The caller. 289 | * 290 | * @throws Zoninator_REST_Exception If not callable. 291 | */ 292 | private function expect_is_callable( $thing, $func ) { 293 | Zoninator_REST_Expect::that( is_callable( $thing ), $func . ' Expected a callable' ); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/zoninator_rest/interfaces/class-zoninator-rest-interfaces-builder.php: -------------------------------------------------------------------------------- 1 | models = $models; 32 | } 33 | 34 | /** 35 | * Get the contents of this collection. 36 | * 37 | * @return Iterator 38 | */ 39 | public function get_items() { 40 | return new ArrayIterator( $this->models ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/class-zoninator-rest-model-declaration.php: -------------------------------------------------------------------------------- 1 | model_definition = $def; 32 | return $this; 33 | } 34 | 35 | /** 36 | * Get definition. 37 | * 38 | * @return Zoninator_REST_Model_Definition 39 | */ 40 | public function definition() { 41 | return $this->model_definition; 42 | } 43 | 44 | /** 45 | * Declare fields 46 | * 47 | * @param Zoninator_REST_Environment $env The Environment. 48 | * 49 | * @return void 50 | * @throws Zoninator_REST_Exception Override this. 51 | */ 52 | public function declare_fields( $env ) { 53 | throw new Zoninator_REST_Exception( 'Override me: ' . __FUNCTION__ ); 54 | } 55 | 56 | /** 57 | * Get the id 58 | * 59 | * @param Zoninator_REST_Interfaces_Model $model The model. 60 | * 61 | * @return mixed|null 62 | */ 63 | public function get_id( $model ) { 64 | return $model->get( 'id' ); 65 | } 66 | 67 | /** 68 | * Set the id 69 | * 70 | * @param Zoninator_REST_Interfaces_Model $model The model. 71 | * @param mixed $new_id The new id. 72 | * 73 | * @return mixed|null 74 | */ 75 | public function set_id( $model, $new_id ) { 76 | return $model->set( 'id', $new_id ); 77 | } 78 | 79 | /** 80 | * Call a method. 81 | * 82 | * @param string $method The method. 83 | * @param array $args The args. 84 | * 85 | * @return mixed 86 | * @throws Zoninator_REST_Exception Throw if method nonexistent. 87 | */ 88 | public function call( $method, $args = array() ) { 89 | if ( is_callable( $method ) ) { 90 | return $this->perform_call( $method, $args ); 91 | } 92 | 93 | Zoninator_REST_Expect::that( method_exists( $this, $method ), $method . ' does not exist' ); 94 | return $this->perform_call( array( $this, $method ), $args ); 95 | } 96 | 97 | /** 98 | * Get name 99 | * 100 | * @return string 101 | */ 102 | public function get_name() { 103 | return strtolower( get_class( $this ) ); 104 | } 105 | 106 | /** 107 | * Perform call 108 | * 109 | * @param mixed $a_callable A Callable. 110 | * @param array $args The args. 111 | * 112 | * @return mixed 113 | */ 114 | private function perform_call( $a_callable, $args ) { 115 | return call_user_func_array( $a_callable, $args ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/class-zoninator-rest-model-definition.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 86 | $this->model_declaration = $model_declaration; 87 | $this->model_class = get_class( $model_declaration ); 88 | $this->permissions_provider = $permissions_provider; 89 | $this->name = strtolower( $this->model_class ); 90 | 91 | $this->set_data_store( $data_store ); 92 | } 93 | 94 | /** 95 | * Get Model Class 96 | * 97 | * @return string 98 | */ 99 | public function get_model_class() { 100 | return $this->model_class; 101 | } 102 | 103 | /** 104 | * Get Data Store 105 | * 106 | * @return Zoninator_REST_Interfaces_Data_Store 107 | */ 108 | public function get_data_store() { 109 | return $this->data_store; 110 | } 111 | 112 | /** 113 | * Set the Data Store 114 | * 115 | * @param Zoninator_REST_Interfaces_Data_Store|Zoninator_REST_Data_Store_Builder $data_store A builder or a Data store. 116 | * @return $this 117 | * @throws Zoninator_REST_Exception Throws when Data Store Invalid. 118 | */ 119 | public function set_data_store( $data_store ) { 120 | if ( is_a( $data_store, 'Zoninator_REST_Data_Store_Builder' ) ) { 121 | $this->data_store = $data_store 122 | ->with_model_definition( $this ) 123 | ->build(); 124 | } else { 125 | $this->data_store = $data_store; 126 | } 127 | 128 | // at this point we should have a data store. 129 | Zoninator_REST_Expect::is_a( $this->data_store, 'Zoninator_REST_Interfaces_Data_Store' ); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Environment 136 | * 137 | * @return Zoninator_REST_Environment 138 | */ 139 | public function environment() { 140 | return $this->environment; 141 | } 142 | 143 | /** 144 | * Get this Definition's Field Declarations 145 | * 146 | * @param null|string $filter_by_type The type to filter with. 147 | * 148 | * @return array|null 149 | */ 150 | public function get_field_declarations( $filter_by_type = null ) { 151 | $model_declaration = $this->get_model_declaration()->set_definition( $this ); 152 | 153 | Zoninator_REST_Expect::is_a( $model_declaration, 'Zoninator_REST_Interfaces_Model_Declaration' ); 154 | 155 | if ( null === $this->field_declarations ) { 156 | $fields = $model_declaration->declare_fields( $this->environment() ); 157 | 158 | $this->field_declarations = $this->initialize_field_map( $fields ); 159 | } 160 | 161 | if ( null === $filter_by_type ) { 162 | return $this->field_declarations; 163 | } 164 | 165 | $filtered = array(); 166 | 167 | foreach ( $this->field_declarations as $field_declaration ) { 168 | /** 169 | * The field declaration. 170 | * 171 | * @var Zoninator_REST_Field_Declaration $field_declaration 172 | */ 173 | if ( $field_declaration->get_kind() === $filter_by_type ) { 174 | $filtered[] = $field_declaration; 175 | } 176 | } 177 | 178 | return $filtered; 179 | } 180 | 181 | /** 182 | * Create a new Model Instance 183 | * 184 | * @param array $data The data. 185 | * 186 | * @return Zoninator_REST_Model 187 | * @throws Zoninator_REST_Exception Throws if data not an array. 188 | */ 189 | public function create_instance( $data ) { 190 | if ( is_array( $data ) ) { 191 | return new Zoninator_REST_Model( $this, $data ); 192 | } 193 | 194 | throw new Zoninator_REST_Exception( 'does not understand entity' ); 195 | } 196 | 197 | /** 198 | * * Merge values from array with current values. 199 | * Note: Values change in place. 200 | * 201 | * @param Zoninator_REST_Interfaces_Model $model The model. 202 | * @param array $data The data (key-value assumed). 203 | * @param bool $updating Is this an update?. 204 | * 205 | * @return Zoninator_REST_Interfaces_Model|WP_Error 206 | * @throws Zoninator_REST_Exception Throws. 207 | */ 208 | public function update_model_from_array( $model, $data, $updating = false ) { 209 | $mapped_data = $this->map_data( $data, $updating ); 210 | foreach ( $mapped_data as $name => $value ) { 211 | $model->set( $name, $value ); 212 | } 213 | 214 | return $model->sanitize(); 215 | } 216 | 217 | /** 218 | * Get Model Declaration 219 | * 220 | * @return Zoninator_REST_Interfaces_Model_Declaration 221 | */ 222 | public function get_model_declaration() { 223 | return $this->model_declaration; 224 | } 225 | 226 | /** 227 | * Creates a new Model From a Request 228 | * 229 | * @param array $data The request. 230 | * @return Zoninator_REST_Model|WP_Error 231 | */ 232 | public function new_from_array( $data ) { 233 | $field_data = $this->map_data( $data, false ); 234 | return $this->create_instance( $field_data )->sanitize(); 235 | } 236 | 237 | /** 238 | * Get field DTO Mappings 239 | * 240 | * @return array 241 | */ 242 | public function get_dto_field_mappings() { 243 | $mappings = array(); 244 | foreach ( $this->get_field_declarations() as $field_declaration ) { 245 | /** 246 | * Declaration 247 | * 248 | * @var Zoninator_REST_Field_Declaration $field_declaration 249 | */ 250 | if ( ! $field_declaration->supports_output_type( 'json' ) ) { 251 | continue; 252 | } 253 | 254 | $mappings[ $field_declaration->get_data_transfer_name() ] = $field_declaration->get_name(); 255 | } 256 | 257 | return $mappings; 258 | } 259 | 260 | /** 261 | * Prepare the Model for Data Transfer 262 | * 263 | * @param Zoninator_REST_Interfaces_Model $model The model. 264 | * 265 | * @return array 266 | */ 267 | public function model_to_dto( $model ) { 268 | $result = array(); 269 | foreach ( $this->get_dto_field_mappings() as $mapping_name => $field_name ) { 270 | $value = $model->get( $field_name ); 271 | $result[ $mapping_name ] = $value; 272 | } 273 | 274 | return $result; 275 | } 276 | 277 | /** 278 | * Get Name 279 | * 280 | * @return string 281 | */ 282 | public function get_name() { 283 | return $this->name; 284 | } 285 | 286 | /** 287 | * Check permissions 288 | * 289 | * @param WP_REST_Request $request The request. 290 | * @param string $action The action. 291 | * @return bool 292 | */ 293 | public function permissions_check( $request, $action ) { 294 | return $this->permissions_provider->permissions_check( $request, $action ); 295 | } 296 | 297 | /** 298 | * Map data names 299 | * 300 | * @param array $data The data to map. 301 | * @param bool $updating Are we Updating. 302 | * @return array 303 | */ 304 | private function map_data( $data, $updating = false ) { 305 | $request_data = array(); 306 | $fields = $this->get_field_declarations(); 307 | foreach ( $fields as $field ) { 308 | /** 309 | * Field 310 | * 311 | * @var Zoninator_REST_Field_Declaration $field Field. 312 | */ 313 | if ( $field->is_kind( Zoninator_REST_Field_Declaration::DERIVED ) ) { 314 | continue; 315 | } 316 | 317 | $dto_name = $field->get_data_transfer_name(); 318 | $field_name = $field->get_name(); 319 | if ( isset( $data[ $dto_name ] ) && ! ( $updating && $field->is_primary() ) ) { 320 | $value = $data[ $dto_name ]; 321 | $request_data[ $field_name ] = $value; 322 | } 323 | } 324 | 325 | return $request_data; 326 | } 327 | 328 | /** 329 | * Initialize_field_map 330 | * 331 | * @param array $declared_field_builders Array. 332 | * 333 | * @return array 334 | */ 335 | private function initialize_field_map( $declared_field_builders ) { 336 | $fields = array(); 337 | foreach ( $declared_field_builders as $field_builder ) { 338 | /** 339 | * Builder 340 | * 341 | * @var Zoninator_REST_Field_Declaration $field Field Builder. 342 | */ 343 | $field = $field_builder->build(); 344 | $fields[ $field->get_name() ] = $field; 345 | } 346 | 347 | return $fields; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/class-zoninator-rest-model-settings.php: -------------------------------------------------------------------------------- 1 | get_environment(); 59 | $settings_per_group = $this->get_settings(); 60 | $fields = array(); 61 | 62 | foreach ( $settings_per_group as $group_data ) { 63 | $group_fields = $group_data[1]; 64 | 65 | foreach ( $group_fields as $field_data ) { 66 | $field_builder = $this->field_declaration_builder_from_data( $env, $field_data ); 67 | $fields[] = $field_builder; 68 | } 69 | } 70 | 71 | return $fields; 72 | } 73 | 74 | /** 75 | * Convert bool to bit 76 | * 77 | * @param mixed $value Val. 78 | * @return string 79 | */ 80 | public function bool_to_bit( $value ) { 81 | return ( ! empty( $value ) && 'false' !== $value ) ? '1' : ''; 82 | } 83 | 84 | /** 85 | * Convert bit to bool 86 | * 87 | * @param mixed $value Val. 88 | * @return bool 89 | */ 90 | public function bit_to_bool( $value ) { 91 | return ! empty( $value ) && '0' !== $value; 92 | } 93 | 94 | /** 95 | * Get ID 96 | * 97 | * @return string 98 | */ 99 | public function get_id() { 100 | return strtolower( get_class( $this ) ); 101 | } 102 | 103 | /** 104 | * Set ID 105 | * 106 | * @param mixed $new_id New ID. 107 | * @return Zoninator_REST_Interfaces_Model $this 108 | */ 109 | public function set_id( $new_id ) { 110 | return $this; 111 | } 112 | 113 | /** 114 | * Build declarations from array 115 | * 116 | * @param Zoninator_REST_Environment $env Environment. 117 | * @param array $field_data Data. 118 | * @return Zoninator_REST_Field_Declaration_Builder 119 | */ 120 | private function field_declaration_builder_from_data( $env, $field_data ) { 121 | $field_name = $field_data['name']; 122 | $field_builder = $env->field( $field_name ); 123 | $default_value = $field_data['std'] ?? $this->default_for_attribute( $field_data, 'std' ); 124 | $label = $field_data['label'] ?? $field_name; 125 | $description = $field_data['desc'] ?? $label; 126 | $setting_type = $field_data['type'] ?? null; 127 | $choices = isset( $field_data['options'] ) ? array_keys( $field_data['options'] ) : null; 128 | $field_type = 'string'; 129 | 130 | if ( 'checkbox' === $setting_type ) { 131 | $field_type = 'boolean'; 132 | if ( $default_value ) { 133 | // convert our default value as well. 134 | $default_value = $this->bit_to_bool( $default_value ); 135 | } 136 | 137 | $field_builder 138 | ->with_serializer( array( $this, 'bool_to_bit' ) ) 139 | ->with_deserializer( array( $this, 'bit_to_bool' ) ); 140 | } elseif ( 'select' === $setting_type ) { 141 | $field_type = 'string'; 142 | } elseif ( is_numeric( $default_value ) ) { 143 | // try to guess numeric fields, although this is not perfect. 144 | $field_type = is_float( $default_value ) ? 'float' : 'integer'; 145 | } 146 | 147 | if ( $default_value ) { 148 | $field_builder->with_default( $default_value ); 149 | } 150 | 151 | $field_builder 152 | ->with_description( $description ) 153 | ->with_dto_name( $field_name ) 154 | ->with_type( $env->type( $field_type ) ); 155 | if ( $choices ) { 156 | $field_builder->with_choices( $choices ); 157 | } 158 | 159 | $this->on_field_setup( $field_name, $field_builder, $field_data, $env ); 160 | 161 | return $field_builder; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/class-zoninator-rest-model-validationdata.php: -------------------------------------------------------------------------------- 1 | value = $value; 46 | $this->model = $model; 47 | $this->field = $field; 48 | } 49 | 50 | /** 51 | * Get Value 52 | * 53 | * @return mixed $this->value the value that needs validation 54 | */ 55 | public function get_value() { 56 | return $this->value; 57 | } 58 | 59 | /** 60 | * Get Model 61 | * 62 | * @return Zoninator_REST_Interfaces_Model 63 | */ 64 | public function get_model() { 65 | return $this->model; 66 | } 67 | 68 | /** 69 | * Get Field 70 | * 71 | * @return Zoninator_REST_Field_Declaration 72 | */ 73 | public function get_field() { 74 | return $this->field; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/declaration/class-zoninator-rest-model-declaration-settings.php: -------------------------------------------------------------------------------- 1 | get_settings(); 58 | $fields = array(); 59 | 60 | foreach ( $settings_per_group as $group_data ) { 61 | $group_fields = $group_data[1]; 62 | 63 | foreach ( $group_fields as $field_data ) { 64 | $field_builder = $this->field_declaration_builder_from_data( $env, $field_data ); 65 | $fields[] = $field_builder; 66 | } 67 | } 68 | 69 | return $fields; 70 | } 71 | 72 | /** 73 | * Convert bool to bit 74 | * 75 | * @param mixed $value Val. 76 | * @return string 77 | */ 78 | public function bool_to_bit( $value ) { 79 | return ( ! empty( $value ) && 'false' !== $value ) ? '1' : ''; 80 | } 81 | 82 | /** 83 | * Convert bit to bool 84 | * 85 | * @param mixed $value Val. 86 | * @return bool 87 | */ 88 | public function bit_to_bool( $value ) { 89 | return ! empty( $value ) && '0' !== $value; 90 | } 91 | 92 | /** 93 | * Get ID 94 | * 95 | * @param Zoninator_REST_Interfaces_Model $model Model. 96 | * @return string 97 | */ 98 | public function get_id( $model ) { 99 | return strtolower( get_class( $this ) ); 100 | } 101 | 102 | /** 103 | * Set ID 104 | * 105 | * @param Zoninator_REST_Interfaces_Model $model Model. 106 | * @param mixed $new_id New ID. 107 | * @return Zoninator_REST_Interfaces_Model $this 108 | */ 109 | public function set_id( $model, $new_id ) { 110 | return $this; 111 | } 112 | 113 | /** 114 | * Build declarations from array 115 | * 116 | * @param Zoninator_REST_Environment $env Environment. 117 | * @param array $field_data Data. 118 | * @return Zoninator_REST_Field_Declaration_Builder 119 | */ 120 | private function field_declaration_builder_from_data( $env, $field_data ) { 121 | $field_name = $field_data['name']; 122 | $field_builder = $env->field( $field_name ); 123 | $default_value = $field_data['std'] ?? $this->default_for_attribute( $field_data, 'std' ); 124 | $label = $field_data['label'] ?? $field_name; 125 | $description = $field_data['desc'] ?? $label; 126 | $setting_type = $field_data['type'] ?? null; 127 | $choices = isset( $field_data['options'] ) ? array_keys( $field_data['options'] ) : null; 128 | $field_type = 'string'; 129 | 130 | if ( 'checkbox' === $setting_type ) { 131 | $field_type = 'boolean'; 132 | if ( $default_value ) { 133 | // convert our default value as well. 134 | $default_value = $this->bit_to_bool( $default_value ); 135 | } 136 | 137 | $field_builder 138 | ->with_serializer( array( $this, 'bool_to_bit' ) ) 139 | ->with_deserializer( array( $this, 'bit_to_bool' ) ); 140 | } elseif ( 'select' === $setting_type ) { 141 | $field_type = 'string'; 142 | } elseif ( is_numeric( $default_value ) ) { 143 | // try to guess numeric fields, although this is not perfect. 144 | $field_type = is_float( $default_value ) ? 'float' : 'integer'; 145 | } 146 | 147 | if ( $default_value ) { 148 | $field_builder->with_default( $default_value ); 149 | } 150 | 151 | $field_builder 152 | ->with_description( $description ) 153 | ->with_dto_name( $field_name ) 154 | ->with_type( $env->type( $field_type ) ); 155 | if ( $choices ) { 156 | $field_builder->with_choices( $choices ); 157 | } 158 | 159 | $this->on_field_setup( $field_name, $field_builder, $field_data, $env ); 160 | return $field_builder; 161 | } 162 | 163 | /** 164 | * Permissions Check 165 | * 166 | * @param WP_REST_Request $request Request. 167 | * @param string $action Action. 168 | * @return bool 169 | */ 170 | public function permissions_check( $request, $action ) { 171 | return true; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/zoninator_rest/model/definition/class-zoninator-rest-model-definition-builder.php: -------------------------------------------------------------------------------- 1 | with_data_store( new Zoninator_REST_Data_Store_Nil() ) 49 | ->with_permissions_provider( new Zoninator_REST_Permissions_Any() ); 50 | } 51 | 52 | /** 53 | * With Declaration 54 | * 55 | * @param Zoninator_REST_Interfaces_Model_Declaration|Zoninator_REST_Interfaces_Permissions_Provider $declaration D. 56 | * @return Zoninator_REST_Model_Definition_Builder 57 | */ 58 | public function with_declaration( $declaration ) { 59 | if ( is_string( $declaration ) && class_exists( $declaration ) ) { 60 | $declaration = new $declaration(); 61 | } 62 | 63 | Zoninator_REST_Expect::is_a( $declaration, 'Zoninator_REST_Interfaces_Model_Declaration' ); 64 | $this->declaration = $declaration; 65 | if ( is_a( $declaration, 'Zoninator_REST_Interfaces_Permissions_Provider' ) ) { 66 | $this->with_permissions_provider( $declaration ); 67 | } 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * With Data Store 74 | * 75 | * @param null|Zoninator_REST_Interfaces_Builder $data_store Data Store. 76 | * 77 | * @return Zoninator_REST_Model_Definition_Builder $this 78 | */ 79 | public function with_data_store( $data_store = null ) { 80 | $this->data_store = $data_store; 81 | return $this; 82 | } 83 | 84 | /** 85 | * With Permissions Provider 86 | * 87 | * @param Zoninator_REST_Interfaces_Permissions_Provider $permissions_provider Provider. 88 | */ 89 | public function with_permissions_provider( $permissions_provider ) { 90 | $this->permissions_provider = $permissions_provider; 91 | } 92 | 93 | /** 94 | * With Environment 95 | * 96 | * @param Zoninator_REST_Environment $environment Environment. 97 | * 98 | * @return Zoninator_REST_Model_Definition_Builder $this 99 | */ 100 | public function with_environment( $environment ) { 101 | $this->environment = $environment; 102 | return $this; 103 | } 104 | 105 | /** 106 | * Build 107 | * 108 | * @return Zoninator_REST_Model_Definition 109 | */ 110 | public function build() { 111 | return new Zoninator_REST_Model_Definition( $this->environment, $this->declaration, $this->data_store, $this->permissions_provider ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/zoninator_rest/permissions/class-zoninator-rest-permissions-any.php: -------------------------------------------------------------------------------- 1 | unsigned = $unsigned; 31 | parent::__construct( 'integer' ); 32 | } 33 | 34 | /** 35 | * Default 36 | * 37 | * @return int 38 | */ 39 | public function default_value() { 40 | return 0; 41 | } 42 | 43 | /** 44 | * Cast 45 | * 46 | * @param mixed $value Val. 47 | * @return int 48 | */ 49 | public function cast( $value ) { 50 | if ( ! is_numeric( $value ) ) { 51 | return $this->default_value(); 52 | } 53 | 54 | return $this->unsigned ? absint( $value ) : intval( $value, 10 ); 55 | } 56 | 57 | /** 58 | * Sanitize 59 | * 60 | * @param mixed $value Val. 61 | * @return int 62 | */ 63 | public function sanitize( $value ) { 64 | return $this->cast( $value ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/zoninator_rest/type/class-zoninator-rest-type-nullable.php: -------------------------------------------------------------------------------- 1 | name() ); 30 | $this->item_type_definition = $item_type_definition; 31 | } 32 | 33 | /** 34 | * Default value as always null. 35 | */ 36 | public function default_value() { 37 | return null; 38 | } 39 | 40 | /** 41 | * Cast 42 | * 43 | * @param mixed $value Value. 44 | * @return mixed|null 45 | */ 46 | public function cast( $value ) { 47 | if ( null === $value ) { 48 | return null; 49 | } 50 | 51 | return $this->item_type_definition->cast( $value ); 52 | } 53 | 54 | /** 55 | * Sanitize. 56 | * 57 | * @param mixed $value Value. 58 | * @return mixed|null 59 | */ 60 | public function sanitize( $value ) { 61 | if ( null === $value ) { 62 | return null; 63 | } 64 | 65 | return $this->item_type_definition->sanitize( $value ); 66 | } 67 | 68 | /** 69 | * Schema 70 | */ 71 | public function schema() { 72 | $schema = parent::schema(); 73 | $schema['type'] = array_unique( array_merge( $schema['type'], array( 'null' ) ) ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/zoninator_rest/type/class-zoninator-rest-type-number.php: -------------------------------------------------------------------------------- 1 | default_value(); 42 | } 43 | 44 | return floatval( $value ); 45 | } 46 | 47 | /** 48 | * Sanitize 49 | * 50 | * @param mixed $value The value to sanitize. 51 | * @return float 52 | */ 53 | public function sanitize( $value ) { 54 | return $this->cast( $value ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/zoninator_rest/type/class-zoninator-rest-type-registry.php: -------------------------------------------------------------------------------- 1 | types[ $identifier ] = $instance; 46 | return $this; 47 | } 48 | 49 | /** 50 | * Get a type definition 51 | * 52 | * @param string $type The type name. 53 | * @return Zoninator_REST_Interfaces_Type 54 | * 55 | * @throws Zoninator_REST_Exception In case of type name not confirming to syntax. 56 | */ 57 | public function definition( $type ) { 58 | $types = $this->get_types(); 59 | 60 | if ( ! isset( $types[ $type ] ) ) { 61 | // maybe lazy-register missing compound type. 62 | $parts = explode( ':', $type ); 63 | if ( count( $parts ) > 1 ) { 64 | $container_type = $parts[0]; 65 | if ( ! in_array( $container_type, $this->container_types, true ) ) { 66 | throw new Zoninator_REST_Exception( esc_html( $container_type . ' is not a known container type' ) ); 67 | } 68 | 69 | $item_type = $parts[1]; 70 | if ( empty( $item_type ) ) { 71 | throw new Zoninator_REST_Exception( esc_html( $type . ': invalid syntax' ) ); 72 | } 73 | 74 | $item_type_definition = $this->definition( $item_type ); 75 | 76 | if ( 'array' === $container_type ) { 77 | $this->define( $type, new Zoninator_REST_Type_TypedArray( $item_type_definition ) ); 78 | $types = $this->get_types(); 79 | } 80 | 81 | if ( 'nullable' === $container_type ) { 82 | $this->define( $type, new Zoninator_REST_Type_Nullable( $item_type_definition ) ); 83 | $types = $this->get_types(); 84 | } 85 | } 86 | } 87 | 88 | if ( ! isset( $types[ $type ] ) ) { 89 | throw new Zoninator_REST_Exception(); 90 | } 91 | 92 | return $types[ $type ]; 93 | } 94 | 95 | /** 96 | * Get Types 97 | * 98 | * @return array 99 | */ 100 | private function get_types() { 101 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 102 | return (array) apply_filters( 'mixtape_type_registry_get_types', $this->types, $this ); 103 | } 104 | 105 | /** 106 | * Initialize the type registry 107 | * 108 | * @param Zoninator_REST_Environment $environment The Environment. 109 | */ 110 | public function initialize( $environment ) { 111 | if ( null !== $this->types ) { 112 | return; 113 | } 114 | 115 | $this->types = apply_filters( 116 | 'mixtape_type_registry_register_types', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 117 | array( 118 | 'any' => new Zoninator_REST_Type( 'any' ), 119 | 'string' => new Zoninator_REST_Type_String(), 120 | 'integer' => new Zoninator_REST_Type_Integer(), 121 | 'int' => new Zoninator_REST_Type_Integer(), 122 | 'uint' => new Zoninator_REST_Type_Integer( true ), 123 | 'number' => new Zoninator_REST_Type_Number(), 124 | 'float' => new Zoninator_REST_Type_Number(), 125 | 'boolean' => new Zoninator_REST_Type_Boolean(), 126 | 'array' => new Zoninator_REST_Type_Array(), 127 | ), 128 | $this, 129 | $environment 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/zoninator_rest/type/class-zoninator-rest-type-string.php: -------------------------------------------------------------------------------- 1 | cast( $v ); 53 | } 54 | 55 | return '(' . implode( ',', $cast_ones ) . ')'; 56 | } 57 | 58 | return (string) $value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/zoninator_rest/type/class-zoninator-rest-type-typedarray.php: -------------------------------------------------------------------------------- 1 | name() ); 33 | $this->item_type_definition = $item_type_definition; 34 | } 35 | 36 | /** 37 | * Get the default value 38 | * 39 | * @return array 40 | */ 41 | public function default_value() { 42 | return array(); 43 | } 44 | 45 | /** 46 | * Cast the value to be a typed array 47 | * 48 | * @param mixed $value an array of mixed. 49 | * @return array 50 | */ 51 | public function cast( $value ) { 52 | $new_value = array(); 53 | 54 | foreach ( $value as $v ) { 55 | $new_value[] = $this->item_type_definition->cast( $v ); 56 | } 57 | 58 | return $new_value; 59 | } 60 | 61 | /** 62 | * Get this type's JSON Schema 63 | * 64 | * @return array 65 | */ 66 | public function schema() { 67 | $schema = parent::schema(); 68 | $schema['type'] = 'array'; 69 | $schema['items'] = $this->item_type_definition->schema(); 70 | return $schema; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /zoninator.php: -------------------------------------------------------------------------------- 1 | 16 | 17 | This program is free software; you can redistribute it and/or modify 18 | it under the terms of the GNU General Public License as published by 19 | the Free Software Foundation; either version 2 of the License, or 20 | (at your option) any later version. 21 | 22 | This program is distributed in the hope that it will be useful, 23 | but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | GNU General Public License for more details. 26 | 27 | You should have received a copy of the GNU General Public License 28 | along with this program; if not, write to the Free Software 29 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 30 | 31 | */ 32 | 33 | define( 'ZONINATOR_VERSION', '0.10.1' ); 34 | define( 'ZONINATOR_FILE', __FILE__ ); 35 | 36 | require_once __DIR__ . '/functions.php'; 37 | require_once __DIR__ . '/src/class-zoninator-zoneposts-widget.php'; 38 | require_once __DIR__ . '/src/class-zoninator.php'; 39 | 40 | function Zoninator() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid, Universal.Files.SeparateFunctionsFromOO.Mixed -- Windows is case-sensitive, so changing this is a breaking change. 41 | global $zoninator; 42 | if ( ! isset( $zoninator ) || null === $zoninator ) { 43 | $zoninator = new Zoninator(); 44 | } 45 | 46 | return $zoninator; 47 | } 48 | 49 | Zoninator(); 50 | --------------------------------------------------------------------------------