Completed
Branch FET/download-plugin-prompt (7814ea)
by
unknown
04:11 queued 15s
created
core/libraries/rest_api/controllers/model/Meta.php 2 patches
Indentation   +114 added lines, -114 removed lines patch added patch discarded remove patch
@@ -25,124 +25,124 @@
 block discarded – undo
25 25
  */
26 26
 class Meta extends Base
27 27
 {
28
-    /**
29
-     * @param WP_REST_Request $request
30
-     * @param string          $version
31
-     * @return WP_REST_Response
32
-     */
33
-    public static function handleRequestModelsMeta(WP_REST_Request $request, string $version): WP_REST_Response
34
-    {
35
-        $controller = new Meta();
36
-        try {
37
-            $controller->setRequestedVersion($version);
38
-            return $controller->sendResponse($controller->getModelsMetadataEntity());
39
-        } catch (Exception $e) {
40
-            return $controller->sendResponse($e);
41
-        }
42
-    }
28
+	/**
29
+	 * @param WP_REST_Request $request
30
+	 * @param string          $version
31
+	 * @return WP_REST_Response
32
+	 */
33
+	public static function handleRequestModelsMeta(WP_REST_Request $request, string $version): WP_REST_Response
34
+	{
35
+		$controller = new Meta();
36
+		try {
37
+			$controller->setRequestedVersion($version);
38
+			return $controller->sendResponse($controller->getModelsMetadataEntity());
39
+		} catch (Exception $e) {
40
+			return $controller->sendResponse($e);
41
+		}
42
+	}
43 43
 
44 44
 
45
-    /**
46
-     * Gets the model metadata resource entity
47
-     *
48
-     * @return array for JSON response, describing all the models available in teh requested version
49
-     * @throws EE_Error
50
-     */
51
-    protected function getModelsMetadataEntity(): array
52
-    {
53
-        $response = [];
54
-        foreach ($this->getModelVersionInfo()->modelsForRequestedVersion() as $model_name => $model_classname) {
55
-            $model       = $this->getModelVersionInfo()->loadModel($model_name);
56
-            $fields_json = [];
57
-            foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field_obj) {
58
-                if ($this->getModelVersionInfo()->fieldIsIgnored($field_obj)) {
59
-                    continue;
60
-                }
61
-                if ($field_obj instanceof EE_Boolean_Field) {
62
-                    $datatype = 'Boolean';
63
-                } elseif ($field_obj->get_wpdb_data_type() == '%d') {
64
-                    $datatype = 'Number';
65
-                } elseif ($field_name instanceof EE_Serialized_Text_Field) {
66
-                    $datatype = 'Object';
67
-                } else {
68
-                    $datatype = 'String';
69
-                }
70
-                $default_value                      = ModelDataTranslator::prepareFieldValueForJson(
71
-                    $field_obj,
72
-                    $field_obj->get_default_value(),
73
-                    $this->getModelVersionInfo()->requestedVersion()
74
-                );
75
-                $field_json                         = [
76
-                    'name'                => $field_name,
77
-                    'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78
-                    'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
79
-                    'has_pretty_format'   => $this->getModelVersionInfo()->fieldHasPrettyFormat($field_obj),
80
-                    'type'                => str_replace('EE_', '', get_class($field_obj)),
81
-                    'datatype'            => $datatype,
82
-                    'nullable'            => $field_obj->is_nullable(),
83
-                    'default'             => $default_value,
84
-                    'table_alias'         => $field_obj->get_table_alias(),
85
-                    'table_column'        => $field_obj->get_table_column(),
86
-                ];
87
-                $fields_json[ $field_json['name'] ] = $field_json;
88
-            }
89
-            $fields_json                       = array_merge(
90
-                $fields_json,
91
-                $this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92
-            );
93
-            $response[ $model_name ]['fields'] = apply_filters(
94
-                'FHEE__Meta__handle_request_models_meta__fields',
95
-                $fields_json,
96
-                $model
97
-            );
98
-            $relations_json                    = [];
99
-            foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
-                $relation_json                    = [
101
-                    'name'   => $relation_name,
102
-                    'type'   => str_replace('EE_', '', get_class($relation_obj)),
103
-                    'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104
-                ];
105
-                $relations_json[ $relation_name ] = $relation_json;
106
-            }
107
-            $response[ $model_name ]['relations'] = apply_filters(
108
-                'FHEE__Meta__handle_request_models_meta__relations',
109
-                $relations_json,
110
-                $model
111
-            );
112
-        }
113
-        return $response;
114
-    }
45
+	/**
46
+	 * Gets the model metadata resource entity
47
+	 *
48
+	 * @return array for JSON response, describing all the models available in teh requested version
49
+	 * @throws EE_Error
50
+	 */
51
+	protected function getModelsMetadataEntity(): array
52
+	{
53
+		$response = [];
54
+		foreach ($this->getModelVersionInfo()->modelsForRequestedVersion() as $model_name => $model_classname) {
55
+			$model       = $this->getModelVersionInfo()->loadModel($model_name);
56
+			$fields_json = [];
57
+			foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field_obj) {
58
+				if ($this->getModelVersionInfo()->fieldIsIgnored($field_obj)) {
59
+					continue;
60
+				}
61
+				if ($field_obj instanceof EE_Boolean_Field) {
62
+					$datatype = 'Boolean';
63
+				} elseif ($field_obj->get_wpdb_data_type() == '%d') {
64
+					$datatype = 'Number';
65
+				} elseif ($field_name instanceof EE_Serialized_Text_Field) {
66
+					$datatype = 'Object';
67
+				} else {
68
+					$datatype = 'String';
69
+				}
70
+				$default_value                      = ModelDataTranslator::prepareFieldValueForJson(
71
+					$field_obj,
72
+					$field_obj->get_default_value(),
73
+					$this->getModelVersionInfo()->requestedVersion()
74
+				);
75
+				$field_json                         = [
76
+					'name'                => $field_name,
77
+					'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78
+					'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
79
+					'has_pretty_format'   => $this->getModelVersionInfo()->fieldHasPrettyFormat($field_obj),
80
+					'type'                => str_replace('EE_', '', get_class($field_obj)),
81
+					'datatype'            => $datatype,
82
+					'nullable'            => $field_obj->is_nullable(),
83
+					'default'             => $default_value,
84
+					'table_alias'         => $field_obj->get_table_alias(),
85
+					'table_column'        => $field_obj->get_table_column(),
86
+				];
87
+				$fields_json[ $field_json['name'] ] = $field_json;
88
+			}
89
+			$fields_json                       = array_merge(
90
+				$fields_json,
91
+				$this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92
+			);
93
+			$response[ $model_name ]['fields'] = apply_filters(
94
+				'FHEE__Meta__handle_request_models_meta__fields',
95
+				$fields_json,
96
+				$model
97
+			);
98
+			$relations_json                    = [];
99
+			foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
+				$relation_json                    = [
101
+					'name'   => $relation_name,
102
+					'type'   => str_replace('EE_', '', get_class($relation_obj)),
103
+					'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104
+				];
105
+				$relations_json[ $relation_name ] = $relation_json;
106
+			}
107
+			$response[ $model_name ]['relations'] = apply_filters(
108
+				'FHEE__Meta__handle_request_models_meta__relations',
109
+				$relations_json,
110
+				$model
111
+			);
112
+		}
113
+		return $response;
114
+	}
115 115
 
116 116
 
117
-    /**
118
-     * Adds EE metadata to the index
119
-     *
120
-     * @param WP_REST_Response $rest_response_obj
121
-     * @return WP_REST_Response
122
-     */
123
-    public static function filterEeMetadataIntoIndex(WP_REST_Response $rest_response_obj): WP_REST_Response
124
-    {
125
-        $response_data = $rest_response_obj->get_data();
126
-        $addons        = [];
127
-        foreach (EE_Registry::instance()->addons as $addon) {
128
-            $addon_json                    = [
129
-                'name'    => $addon->name(),
130
-                'version' => $addon->version(),
131
-            ];
132
-            $addons[ $addon_json['name'] ] = $addon_json;
133
-        }
134
-        $response_data['ee'] = [
135
-            'version'              => EEM_System_Status::instance()->get_ee_version(),
136
-            // @codingStandardsIgnoreStart
137
-            'documentation_url'    => 'https://github.com/eventespresso/event-espresso-core/tree/master/docs/C--REST-API',
138
-            // @codingStandardsIgnoreEnd
139
-            'addons'               => $addons,
140
-            'maintenance_mode'     => EE_Maintenance_Mode::instance()->real_level(),
141
-            'served_core_versions' => array_keys(EED_Core_Rest_Api::versions_served()),
142
-        ];
143
-        $rest_response_obj->set_data($response_data);
144
-        return $rest_response_obj;
145
-    }
117
+	/**
118
+	 * Adds EE metadata to the index
119
+	 *
120
+	 * @param WP_REST_Response $rest_response_obj
121
+	 * @return WP_REST_Response
122
+	 */
123
+	public static function filterEeMetadataIntoIndex(WP_REST_Response $rest_response_obj): WP_REST_Response
124
+	{
125
+		$response_data = $rest_response_obj->get_data();
126
+		$addons        = [];
127
+		foreach (EE_Registry::instance()->addons as $addon) {
128
+			$addon_json                    = [
129
+				'name'    => $addon->name(),
130
+				'version' => $addon->version(),
131
+			];
132
+			$addons[ $addon_json['name'] ] = $addon_json;
133
+		}
134
+		$response_data['ee'] = [
135
+			'version'              => EEM_System_Status::instance()->get_ee_version(),
136
+			// @codingStandardsIgnoreStart
137
+			'documentation_url'    => 'https://github.com/eventespresso/event-espresso-core/tree/master/docs/C--REST-API',
138
+			// @codingStandardsIgnoreEnd
139
+			'addons'               => $addons,
140
+			'maintenance_mode'     => EE_Maintenance_Mode::instance()->real_level(),
141
+			'served_core_versions' => array_keys(EED_Core_Rest_Api::versions_served()),
142
+		];
143
+		$rest_response_obj->set_data($response_data);
144
+		return $rest_response_obj;
145
+	}
146 146
 }
147 147
 
148 148
 
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -67,12 +67,12 @@  discard block
 block discarded – undo
67 67
                 } else {
68 68
                     $datatype = 'String';
69 69
                 }
70
-                $default_value                      = ModelDataTranslator::prepareFieldValueForJson(
70
+                $default_value = ModelDataTranslator::prepareFieldValueForJson(
71 71
                     $field_obj,
72 72
                     $field_obj->get_default_value(),
73 73
                     $this->getModelVersionInfo()->requestedVersion()
74 74
                 );
75
-                $field_json                         = [
75
+                $field_json = [
76 76
                     'name'                => $field_name,
77 77
                     'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78 78
                     'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
@@ -84,27 +84,27 @@  discard block
 block discarded – undo
84 84
                     'table_alias'         => $field_obj->get_table_alias(),
85 85
                     'table_column'        => $field_obj->get_table_column(),
86 86
                 ];
87
-                $fields_json[ $field_json['name'] ] = $field_json;
87
+                $fields_json[$field_json['name']] = $field_json;
88 88
             }
89
-            $fields_json                       = array_merge(
89
+            $fields_json = array_merge(
90 90
                 $fields_json,
91 91
                 $this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92 92
             );
93
-            $response[ $model_name ]['fields'] = apply_filters(
93
+            $response[$model_name]['fields'] = apply_filters(
94 94
                 'FHEE__Meta__handle_request_models_meta__fields',
95 95
                 $fields_json,
96 96
                 $model
97 97
             );
98
-            $relations_json                    = [];
98
+            $relations_json = [];
99 99
             foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
-                $relation_json                    = [
100
+                $relation_json = [
101 101
                     'name'   => $relation_name,
102 102
                     'type'   => str_replace('EE_', '', get_class($relation_obj)),
103 103
                     'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104 104
                 ];
105
-                $relations_json[ $relation_name ] = $relation_json;
105
+                $relations_json[$relation_name] = $relation_json;
106 106
             }
107
-            $response[ $model_name ]['relations'] = apply_filters(
107
+            $response[$model_name]['relations'] = apply_filters(
108 108
                 'FHEE__Meta__handle_request_models_meta__relations',
109 109
                 $relations_json,
110 110
                 $model
@@ -125,11 +125,11 @@  discard block
 block discarded – undo
125 125
         $response_data = $rest_response_obj->get_data();
126 126
         $addons        = [];
127 127
         foreach (EE_Registry::instance()->addons as $addon) {
128
-            $addon_json                    = [
128
+            $addon_json = [
129 129
                 'name'    => $addon->name(),
130 130
                 'version' => $addon->version(),
131 131
             ];
132
-            $addons[ $addon_json['name'] ] = $addon_json;
132
+            $addons[$addon_json['name']] = $addon_json;
133 133
         }
134 134
         $response_data['ee'] = [
135 135
             'version'              => EEM_System_Status::instance()->get_ee_version(),
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1644 added lines, -1644 removed lines patch added patch discarded remove patch
@@ -50,1648 +50,1648 @@
 block discarded – undo
50 50
  */
51 51
 class Read extends Base
52 52
 {
53
-    /**
54
-     * @var CalculatedModelFields
55
-     */
56
-    protected $fields_calculator;
57
-
58
-
59
-    /**
60
-     * Read constructor.
61
-     *
62
-     * @param CalculatedModelFields $fields_calculator
63
-     */
64
-    public function __construct(CalculatedModelFields $fields_calculator)
65
-    {
66
-        parent::__construct();
67
-        $this->fields_calculator = $fields_calculator;
68
-    }
69
-
70
-
71
-    /**
72
-     * @param string $version
73
-     * @return Read
74
-     * @since $VID:$
75
-     */
76
-    public static function getModelReadController(string $version): Read
77
-    {
78
-        /** @var Read $controller */
79
-        $controller = LoaderFactory::getLoader()->getNew(Read::class);
80
-        $controller->setRequestedVersion($version);
81
-        return $controller;
82
-    }
83
-
84
-
85
-    /**
86
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
87
-     *
88
-     * @param WP_REST_Request $request
89
-     * @param string          $version
90
-     * @param string          $model_name
91
-     * @return WP_REST_Response
92
-     * @throws InvalidArgumentException
93
-     * @throws InvalidDataTypeException
94
-     * @throws InvalidInterfaceException
95
-     */
96
-    public static function handleRequestGetAll(
97
-        WP_REST_Request $request,
98
-        string $version,
99
-        string $model_name
100
-    ): WP_REST_Response {
101
-        $controller = Read::getModelReadController($version);
102
-        try {
103
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104
-                return $controller->sendResponse(Read::endpointParsingError($model_name));
105
-            }
106
-            return $controller->sendResponse(
107
-                $controller->getEntitiesFromModel(
108
-                    $controller->getModelVersionInfo()->loadModel($model_name),
109
-                    $request
110
-                )
111
-            );
112
-        } catch (Exception $e) {
113
-            return $controller->sendResponse($e);
114
-        }
115
-    }
116
-
117
-
118
-    /**
119
-     * Prepares and returns schema for any OPTIONS request.
120
-     *
121
-     * @param string $version    The API endpoint version being used.
122
-     * @param string $model_name Something like `Event` or `Registration`
123
-     * @return array
124
-     * @throws InvalidArgumentException
125
-     * @throws InvalidDataTypeException
126
-     * @throws InvalidInterfaceException
127
-     */
128
-    public static function handleSchemaRequest(string $version, string $model_name): array
129
-    {
130
-        $controller = Read::getModelReadController($version);
131
-        try {
132
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133
-                return [];
134
-            }
135
-            // get the model for this version
136
-            $model        = $controller->getModelVersionInfo()->loadModel($model_name);
137
-            $model_schema = new JsonModelSchema(
138
-                $model,
139
-                LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields')
140
-            );
141
-            return $model_schema->getModelSchemaForRelations(
142
-                $controller->getModelVersionInfo()->relationSettings($model),
143
-                $controller->customizeSchemaForRestResponse(
144
-                    $model,
145
-                    $model_schema->getModelSchemaForFields(
146
-                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
147
-                        $model_schema->getInitialSchemaStructure()
148
-                    )
149
-                )
150
-            );
151
-        } catch (Exception $e) {
152
-            return [];
153
-        }
154
-    }
155
-
156
-
157
-    /**
158
-     * This loops through each field in the given schema for the model and does the following:
159
-     * - add any extra fields that are REST API specific and related to existing fields.
160
-     * - transform default values into the correct format for a REST API response.
161
-     *
162
-     * @param EEM_Base $model
163
-     * @param array    $schema
164
-     * @return array  The final schema.
165
-     * @throws EE_Error
166
-     * @throws EE_Error
167
-     */
168
-    protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema): array
169
-    {
170
-        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
171
-            $schema = $this->translateDefaultsForRestResponse(
172
-                $field_name,
173
-                $field,
174
-                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
175
-            );
176
-        }
177
-        return $schema;
178
-    }
179
-
180
-
181
-    /**
182
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
183
-     * response.
184
-     *
185
-     * @param                      $field_name
186
-     * @param EE_Model_Field_Base  $field
187
-     * @param array                $schema
188
-     * @return array
189
-     * @throws RestException  if a default value has a PHP object, which we should never do
190
-     *                                  (but if we did, let's know about it ASAP, so let the exception bubble up)
191
-     * @throws EE_Error
192
-     *
193
-     */
194
-    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195
-    {
196
-        if (isset($schema['properties'][ $field_name ]['default'])) {
197
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
198
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
199
-                    if ($default_key === 'raw') {
200
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
201
-                            ModelDataTranslator::prepareFieldValueForJson(
202
-                                $field,
203
-                                $default_value,
204
-                                $this->getModelVersionInfo()->requestedVersion()
205
-                            );
206
-                    }
207
-                }
208
-            } else {
209
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210
-                    $field,
211
-                    $schema['properties'][ $field_name ]['default'],
212
-                    $this->getModelVersionInfo()->requestedVersion()
213
-                );
214
-            }
215
-        }
216
-        return $schema;
217
-    }
218
-
219
-
220
-    /**
221
-     * Adds additional fields to the schema
222
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
223
-     * needs to be added to the schema.
224
-     *
225
-     * @param                      $field_name
226
-     * @param EE_Model_Field_Base  $field
227
-     * @param array                $schema
228
-     * @return array
229
-     */
230
-    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231
-    {
232
-        if ($field instanceof EE_Datetime_Field) {
233
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
234
-            // modify the description
235
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
236
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237
-                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238
-            );
239
-        }
240
-        return $schema;
241
-    }
242
-
243
-
244
-    /**
245
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
246
-     *
247
-     * @return string
248
-     */
249
-    protected function getRouteFromRequest(): string
250
-    {
251
-        if (
252
-            isset($GLOBALS['wp'])
253
-            && $GLOBALS['wp'] instanceof WP
254
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
255
-        ) {
256
-            return $GLOBALS['wp']->query_vars['rest_route'];
257
-        } else {
258
-            /** @var RequestInterface $request */
259
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
260
-            return $request->serverParamIsSet('PATH_INFO')
261
-                ? $request->getServerParam('PATH_INFO')
262
-                : '/';
263
-        }
264
-    }
265
-
266
-
267
-    /**
268
-     * Gets a single entity related to the model indicated in the path and its id
269
-     *
270
-     * @param WP_REST_Request $request
271
-     * @param string          $version
272
-     * @param string          $model_name
273
-     * @return WP_REST_Response
274
-     * @throws InvalidDataTypeException
275
-     * @throws InvalidInterfaceException
276
-     * @throws InvalidArgumentException
277
-     */
278
-    public static function handleRequestGetOne(
279
-        WP_REST_Request $request,
280
-        string $version,
281
-        string $model_name
282
-    ): WP_REST_Response {
283
-        $controller = Read::getModelReadController($version);
284
-        try {
285
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286
-                return $controller->sendResponse(Read::endpointParsingError($model_name));
287
-            }
288
-            return $controller->sendResponse(
289
-                $controller->getEntityFromModel(
290
-                    $controller->getModelVersionInfo()->loadModel($model_name),
291
-                    $request
292
-                )
293
-            );
294
-        } catch (Exception $e) {
295
-            return $controller->sendResponse($e);
296
-        }
297
-    }
298
-
299
-
300
-    /**
301
-     * Gets all the related entities (or if its a belongs-to relation just the one)
302
-     * to the item with the given id
303
-     *
304
-     * @param WP_REST_Request $request
305
-     * @param string          $version
306
-     * @param string          $model_name
307
-     * @param string          $related_model_name
308
-     * @return WP_REST_Response
309
-     * @throws InvalidDataTypeException
310
-     * @throws InvalidInterfaceException
311
-     * @throws InvalidArgumentException
312
-     */
313
-    public static function handleRequestGetRelated(
314
-        WP_REST_Request $request,
315
-        string $version,
316
-        string $model_name,
317
-        string $related_model_name
318
-    ): WP_REST_Response {
319
-        $controller = Read::getModelReadController($version);
320
-        try {
321
-            $main_model = $controller->validateModel($model_name);
322
-            $controller->validateModel($related_model_name);
323
-            return $controller->sendResponse(
324
-                $controller->getEntitiesFromRelation(
325
-                    $request->get_param('id'),
326
-                    $main_model->related_settings_for($related_model_name),
327
-                    $request
328
-                )
329
-            );
330
-        } catch (Exception $e) {
331
-            return $controller->sendResponse($e);
332
-        }
333
-    }
334
-
335
-
336
-    /**
337
-     * Gets a collection for the given model and filters
338
-     *
339
-     * @param EEM_Base        $model
340
-     * @param WP_REST_Request $request
341
-     * @return array
342
-     * @throws EE_Error
343
-     * @throws InvalidArgumentException
344
-     * @throws InvalidDataTypeException
345
-     * @throws InvalidInterfaceException
346
-     * @throws ReflectionException
347
-     * @throws RestException
348
-     */
349
-    public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350
-    {
351
-        $query_params = $this->createModelQueryParams($model, $request->get_params());
352
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354
-            throw new RestException(
355
-                sprintf('rest_%s_cannot_list', $model_name_plural),
356
-                sprintf(
357
-                    esc_html__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
358
-                    $model_name_plural,
359
-                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
360
-                ),
361
-                ['status' => 403]
362
-            );
363
-        }
364
-        if (! $request->get_header('no_rest_headers')) {
365
-            $this->setHeadersFromQueryParams($model, $query_params);
366
-        }
367
-        /** @type array $results */
368
-        $results      = $model->get_all_wpdb_results($query_params);
369
-        $nice_results = [];
370
-        foreach ($results as $result) {
371
-            $nice_results[] = $this->createEntityFromWpdbResult(
372
-                $model,
373
-                $result,
374
-                $request
375
-            );
376
-        }
377
-        return $nice_results;
378
-    }
379
-
380
-
381
-    /**
382
-     * Gets the collection for given relation object
383
-     * The same as Read::get_entities_from_model(), except if the relation
384
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
385
-     * the join-model-object into the results
386
-     *
387
-     * @param array                  $primary_model_query_params  query params for finding the item from which
388
-     *                                                            relations will be based
389
-     * @param EE_Model_Relation_Base $relation
390
-     * @param WP_REST_Request        $request
391
-     * @return array
392
-     * @throws EE_Error
393
-     * @throws InvalidArgumentException
394
-     * @throws InvalidDataTypeException
395
-     * @throws InvalidInterfaceException
396
-     * @throws ReflectionException
397
-     * @throws RestException
398
-     * @throws ModelConfigurationException
399
-     */
400
-    protected function getEntitiesFromRelationUsingModelQueryParams(
401
-        array $primary_model_query_params,
402
-        EE_Model_Relation_Base $relation,
403
-        WP_REST_Request $request
404
-    ): array {
405
-        $context       = $this->validateContext($request->get_param('caps'));
406
-        $model         = $relation->get_this_model();
407
-        $related_model = $relation->get_other_model();
408
-        if (! isset($primary_model_query_params[0])) {
409
-            $primary_model_query_params[0] = [];
410
-        }
411
-        // check if they can access the 1st model object
412
-        $primary_model_query_params = [
413
-            0       => $primary_model_query_params[0],
414
-            'limit' => 1,
415
-        ];
416
-        if ($model instanceof EEM_Soft_Delete_Base) {
417
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
418
-                $primary_model_query_params
419
-            );
420
-        }
421
-        $restricted_query_params          = $primary_model_query_params;
422
-        $restricted_query_params['caps']  = $context;
423
-        $restricted_query_params['limit'] = 1;
424
-        $this->setDebugInfo('main model query params', $restricted_query_params);
425
-        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
426
-        $primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
427
-        $primary_model_row  = null;
428
-        if (is_array($primary_model_rows)) {
429
-            $primary_model_row = reset($primary_model_rows);
430
-        }
431
-        if (
432
-            ! (
433
-                $primary_model_row
434
-                && Capabilities::currentUserHasPartialAccessTo($related_model, $context)
435
-            )
436
-        ) {
437
-            if ($relation instanceof EE_Belongs_To_Relation) {
438
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
439
-            } else {
440
-                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
441
-                    $related_model->get_this_model_name()
442
-                );
443
-            }
444
-            throw new RestException(
445
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
446
-                sprintf(
447
-                    esc_html__(
448
-                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
449
-                        'event_espresso'
450
-                    ),
451
-                    $related_model_name_maybe_plural,
452
-                    $relation->get_this_model()->get_this_model_name(),
453
-                    implode(
454
-                        ',',
455
-                        array_keys(
456
-                            Capabilities::getMissingPermissions($related_model, $context)
457
-                        )
458
-                    )
459
-                ),
460
-                ['status' => 403]
461
-            );
462
-        }
463
-
464
-        $this->checkPassword(
465
-            $model,
466
-            $primary_model_row,
467
-            $restricted_query_params,
468
-            $request
469
-        );
470
-        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
473
-                              . '.'
474
-                              . $where_condition_key ] = $where_condition_value;
475
-        }
476
-        $query_params['default_where_conditions'] = 'none';
477
-        $query_params['caps']                     = $context;
478
-        if (! $request->get_header('no_rest_headers')) {
479
-            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480
-        }
481
-        /** @type array $results */
482
-        $results      = $relation->get_other_model()->get_all_wpdb_results($query_params);
483
-        $nice_results = [];
484
-        foreach ($results as $result) {
485
-            $nice_result = $this->createEntityFromWpdbResult(
486
-                $relation->get_other_model(),
487
-                $result,
488
-                $request
489
-            );
490
-            if ($relation instanceof EE_HABTM_Relation) {
491
-                // put the unusual stuff (properties from the HABTM relation) first, and make sure
492
-                // if there are conflicts we prefer the properties from the main model
493
-                $join_model_result = $this->createEntityFromWpdbResult(
494
-                    $relation->get_join_model(),
495
-                    $result,
496
-                    $request
497
-                );
498
-                $joined_result     = array_merge($join_model_result, $nice_result);
499
-                // but keep the meta stuff from the main model
500
-                if (isset($nice_result['meta'])) {
501
-                    $joined_result['meta'] = $nice_result['meta'];
502
-                }
503
-                $nice_result = $joined_result;
504
-            }
505
-            $nice_results[] = $nice_result;
506
-        }
507
-        if ($relation instanceof EE_Belongs_To_Relation) {
508
-            return array_shift($nice_results);
509
-        } else {
510
-            return $nice_results;
511
-        }
512
-    }
513
-
514
-
515
-    /**
516
-     * Gets the collection for given relation object
517
-     * The same as Read::get_entities_from_model(), except if the relation
518
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
519
-     * the join-model-object into the results
520
-     *
521
-     * @param string                 $id the ID of the thing we are fetching related stuff from
522
-     * @param EE_Model_Relation_Base $relation
523
-     * @param WP_REST_Request        $request
524
-     * @return array
525
-     * @throws EE_Error
526
-     * @throws ReflectionException
527
-     */
528
-    public function getEntitiesFromRelation(
529
-        string $id,
530
-        EE_Model_Relation_Base $relation,
531
-        WP_REST_Request $request
532
-    ): array {
533
-        if (! $relation->get_this_model()->has_primary_key_field()) {
534
-            throw new EE_Error(
535
-                sprintf(
536
-                    esc_html__(
537
-                    // @codingStandardsIgnoreStart
538
-                        'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
539
-                        // @codingStandardsIgnoreEnd
540
-                        'event_espresso'
541
-                    ),
542
-                    $relation->get_this_model()->get_this_model_name()
543
-                )
544
-            );
545
-        }
546
-        // can we edit that main item?
547
-        // if not, show nothing but an error
548
-        // otherwise, please proceed
549
-        return $this->getEntitiesFromRelationUsingModelQueryParams(
550
-            [
551
-                [
552
-                    $relation->get_this_model()->primary_key_name() => $id,
553
-                ],
554
-            ],
555
-            $relation,
556
-            $request
557
-        );
558
-    }
559
-
560
-
561
-    /**
562
-     * Sets the headers that are based on the model and query params,
563
-     * like the total records. This should only be called on the original request
564
-     * from the client, not on subsequent internal
565
-     *
566
-     * @param EEM_Base $model
567
-     * @param array    $query_params
568
-     * @return void
569
-     * @throws EE_Error
570
-     * @throws EE_Error
571
-     */
572
-    protected function setHeadersFromQueryParams(EEM_Base $model, array $query_params)
573
-    {
574
-        $this->setDebugInfo('model query params', $query_params);
575
-        $this->setDebugInfo(
576
-            'missing caps',
577
-            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578
-        );
579
-        // normally the limit to a 2-part array, where the 2nd item is the limit
580
-        if (! isset($query_params['limit'])) {
581
-            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582
-        }
583
-        if (is_array($query_params['limit'])) {
584
-            $limit_parts = $query_params['limit'];
585
-        } else {
586
-            $limit_parts = explode(',', $query_params['limit']);
587
-            if (count($limit_parts) == 1) {
588
-                $limit_parts = [0, $limit_parts[0]];
589
-            }
590
-        }
591
-        // remove the group by and having parts of the query, as those will
592
-        // make the sql query return an array of values, instead of just a single value
593
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
594
-        $count = $model->count($query_params, null, true);
595
-        $pages = $count / $limit_parts[1];
596
-        $this->setResponseHeader('Total', $count, false);
597
-        $this->setResponseHeader('PageSize', $limit_parts[1], false);
598
-        $this->setResponseHeader('TotalPages', ceil($pages), false);
599
-    }
600
-
601
-
602
-    /**
603
-     * Changes database results into REST API entities
604
-     *
605
-     * @param EEM_Base             $model
606
-     * @param array                $db_row     like results from $wpdb->get_results()
607
-     * @param WP_REST_Request|null $rest_request
608
-     * @param string|null          $deprecated no longer used
609
-     * @return array ready for being converted into json for sending to client
610
-     * @throws EE_Error
611
-     * @throws ReflectionException
612
-     * @throws RestException
613
-     * @throws RestPasswordIncorrectException
614
-     * @throws RestPasswordRequiredException
615
-     */
616
-    public function createEntityFromWpdbResult(
617
-        EEM_Base $model,
618
-        array $db_row,
619
-        ?WP_REST_Request $rest_request,
620
-        string $deprecated = null
621
-    ): array {
622
-        if (! $rest_request instanceof WP_REST_Request) {
623
-            // ok so this was called in the old style, where the 3rd arg was
624
-            // $include, and the 4th arg was $context
625
-            // now setup the request just to avoid fatal errors, although we won't be able
626
-            // to truly make use of it because it's kinda devoid of info
627
-            $rest_request = new WP_REST_Request();
628
-            $rest_request->set_param('include', $rest_request);
629
-            $rest_request->set_param('caps', $deprecated);
630
-        }
631
-        if ($rest_request->get_param('caps') == null) {
632
-            $rest_request->set_param('caps', EEM_Base::caps_read);
633
-        }
634
-        $current_user_full_access_to_entity = $model->currentUserCan(
635
-            EEM_Base::caps_read_admin,
636
-            $model->deduce_fields_n_values_from_cols_n_values($db_row)
637
-        );
638
-        $entity_array                       = $this->createBareEntityFromWpdbResults($model, $db_row);
639
-        $entity_array                       = $this->addExtraFields($model, $db_row, $entity_array);
640
-        $entity_array['_links']             = $this->getEntityLinks($model, $db_row, $entity_array);
641
-        // when it's a regular read request for a model with a password and the password wasn't provided
642
-        // remove the password protected fields
643
-        $has_protected_fields = false;
644
-        try {
645
-            $this->checkPassword(
646
-                $model,
647
-                $db_row,
648
-                $model->alter_query_params_to_restrict_by_ID(
649
-                    $model->get_index_primary_key_string(
650
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
651
-                    )
652
-                ),
653
-                $rest_request
654
-            );
655
-        } catch (RestPasswordRequiredException $e) {
656
-            if ($model->hasPassword()) {
657
-                // just remove protected fields
658
-                $has_protected_fields = true;
659
-                $entity_array         = Capabilities::filterOutPasswordProtectedFields(
660
-                    $entity_array,
661
-                    $model,
662
-                    $this->getModelVersionInfo()
663
-                );
664
-            } else {
665
-                // that's a problem. None of this should be accessible if no password was provided
666
-                throw $e;
667
-            }
668
-        }
669
-
670
-        $entity_array['_calculated_fields'] =
671
-            $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
672
-        $entity_array                       = apply_filters(
673
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
674
-            $entity_array,
675
-            $model,
676
-            $rest_request->get_param('caps'),
677
-            $rest_request,
678
-            $this
679
-        );
680
-        // add an empty protected property for now. If it's still around after we remove everything the request didn't
681
-        // want, we'll populate it then. k?
682
-        $entity_array['_protected'] = [];
683
-        // remove any properties the request didn't want. This way _protected won't bother mentioning them
684
-        $entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
685
-        $entity_array =
686
-            $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
-        // if they still wanted the _protected property, add it.
688
-        if (isset($entity_array['_protected'])) {
689
-            $entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
-        }
691
-        $entity_array = apply_filters(
692
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
-            $entity_array,
694
-            $model,
695
-            $rest_request->get_param('caps'),
696
-            $rest_request,
697
-            $this
698
-        );
699
-        if (! $current_user_full_access_to_entity) {
700
-            $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
-                $entity_array,
702
-                $model,
703
-                $rest_request->get_param('caps'),
704
-                $this->getModelVersionInfo()
705
-            );
706
-        } else {
707
-            $result_without_inaccessible_fields = $entity_array;
708
-        }
709
-        $this->setDebugInfo(
710
-            'inaccessible fields',
711
-            array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
-        );
713
-        return apply_filters(
714
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
-            $result_without_inaccessible_fields,
716
-            $model,
717
-            $rest_request->get_param('caps')
718
-        );
719
-    }
720
-
721
-
722
-    /**
723
-     * Returns an array describing which fields can be protected, and which actually were removed this request
724
-     *
725
-     * @param EEM_Base $model
726
-     * @param array    $results_so_far
727
-     * @param bool     $protected
728
-     * @return array results
729
-     * @throws EE_Error
730
-     * @since 4.9.74.p
731
-     */
732
-    protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733
-    {
734
-        if (! $model->hasPassword() || ! $protected) {
735
-            return $results_so_far;
736
-        }
737
-        $password_field  = $model->getPasswordField();
738
-        $all_protected   = array_merge(
739
-            [$password_field->get_name()],
740
-            $password_field->protectedFields()
741
-        );
742
-        $fields_included = array_keys($results_so_far);
743
-        $fields_included = array_intersect(
744
-            $all_protected,
745
-            $fields_included
746
-        );
747
-        foreach ($fields_included as $field_name) {
748
-            $results_so_far['_protected'][] = $field_name;
749
-        }
750
-        return $results_so_far;
751
-    }
752
-
753
-
754
-    /**
755
-     * Creates a REST entity array (JSON object we're going to return in the response, but
756
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
757
-     * from $wpdb->get_row( $sql, ARRAY_A)
758
-     *
759
-     * @param EEM_Base $model
760
-     * @param array    $db_row
761
-     * @return array entity mostly ready for converting to JSON and sending in the response
762
-     * @throws EE_Error
763
-     * @throws ReflectionException
764
-     * @throws RestException
765
-     */
766
-    protected function createBareEntityFromWpdbResults(EEM_Base $model, array $db_row): array
767
-    {
768
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
769
-        $result = array_intersect_key(
770
-            $result,
771
-            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
772
-        );
773
-        // if this is a CPT, we need to set the global $post to it,
774
-        // otherwise shortcodes etc won't work properly while rendering it
775
-        if ($model instanceof EEM_CPT_Base) {
776
-            $do_chevy_shuffle = true;
777
-        } else {
778
-            $do_chevy_shuffle = false;
779
-        }
780
-        if ($do_chevy_shuffle) {
781
-            global $post;
782
-            $old_post = $post;
783
-            $post     = get_post($result[ $model->primary_key_name() ]);
784
-            if (! $post instanceof WP_Post) {
785
-                // well that's weird, because $result is what we JUST fetched from the database
786
-                throw new RestException(
787
-                    'error_fetching_post_from_database_results',
788
-                    esc_html__(
789
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
790
-                        'event_espresso'
791
-                    )
792
-                );
793
-            }
794
-            $model_object_classname          = 'EE_' . $model->get_this_model_name();
795
-            $post->{$model_object_classname} = EE_Registry::instance()->load_class(
796
-                $model_object_classname,
797
-                $result,
798
-                false,
799
-                false
800
-            );
801
-        }
802
-        foreach ($result as $field_name => $field_value) {
803
-            $field_obj = $model->field_settings_for($field_name);
804
-            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
-                unset($result[ $field_name ]);
806
-            } elseif (
807
-                $this->isSubclassOfOne(
808
-                    $field_obj,
809
-                    $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810
-                )
811
-            ) {
812
-                $result[ $field_name ] = [
813
-                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814
-                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815
-                ];
816
-            } elseif (
817
-                $this->isSubclassOfOne(
818
-                    $field_obj,
819
-                    $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820
-                )
821
-            ) {
822
-                $result[ $field_name ] = [
823
-                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824
-                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825
-                ];
826
-            } elseif ($field_obj instanceof EE_Datetime_Field) {
827
-                $field_value = $field_obj->prepare_for_set_from_db($field_value);
828
-                // if the value is null, but we're not supposed to permit null, then set to the field's default
829
-                if (is_null($field_value)) {
830
-                    $field_value = $field_obj->getDefaultDateTimeObj();
831
-                }
832
-                if (is_null($field_value)) {
833
-                    $gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
834
-                        $field_obj,
835
-                        $field_value,
836
-                        $this->getModelVersionInfo()->requestedVersion()
837
-                    );
838
-                } else {
839
-                    $timezone = $field_value->getTimezone();
840
-                    EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
841
-                    $gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
842
-                        $field_obj,
843
-                        $field_value,
844
-                        $this->getModelVersionInfo()->requestedVersion()
845
-                    );
846
-                    EEH_DTT_Helper::setTimezone($field_value, $timezone);
847
-                    $local_date = ModelDataTranslator::prepareFieldValuesForJson(
848
-                        $field_obj,
849
-                        $field_value,
850
-                        $this->getModelVersionInfo()->requestedVersion()
851
-                    );
852
-                }
853
-                $result[ $field_name . '_gmt' ] = $gmt_date;
854
-                $result[ $field_name ]          = $local_date;
855
-            } else {
856
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857
-            }
858
-        }
859
-        if ($do_chevy_shuffle) {
860
-            $post = $old_post;
861
-        }
862
-        return $result;
863
-    }
864
-
865
-
866
-    /**
867
-     * Takes a value all the way from the DB representation, to the model object's representation, to the
868
-     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
869
-     * representation using $field_obj->prepare_for_set_from_db())
870
-     *
871
-     * @param EE_Model_Field_Base $field_obj
872
-     * @param mixed               $value  as it's stored on a model object
873
-     * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
874
-     * @return mixed
875
-     * @throws RestException if $value contains a PHP object
876
-     * @throws EE_Error
877
-     */
878
-    protected function prepareFieldObjValueForJson(
879
-        EE_Model_Field_Base $field_obj,
880
-        $value,
881
-        string $format = 'normal'
882
-    ) {
883
-        $value = $field_obj->prepare_for_set_from_db($value);
884
-        switch ($format) {
885
-            case 'pretty':
886
-                $value = $field_obj->prepare_for_pretty_echoing($value);
887
-                break;
888
-            case 'normal':
889
-            default:
890
-                $value = $field_obj->prepare_for_get($value);
891
-                break;
892
-        }
893
-        return ModelDataTranslator::prepareFieldValuesForJson(
894
-            $field_obj,
895
-            $value,
896
-            $this->getModelVersionInfo()->requestedVersion()
897
-        );
898
-    }
899
-
900
-
901
-    /**
902
-     * Adds a few extra fields to the entity response
903
-     *
904
-     * @param EEM_Base $model
905
-     * @param array    $db_row
906
-     * @param array    $entity_array
907
-     * @return array modified entity
908
-     * @throws EE_Error
909
-     * @throws EE_Error
910
-     */
911
-    protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912
-    {
913
-        if ($model instanceof EEM_CPT_Base) {
914
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
915
-        }
916
-        return $entity_array;
917
-    }
918
-
919
-
920
-    /**
921
-     * Gets links we want to add to the response
922
-     *
923
-     * @param EEM_Base        $model
924
-     * @param array           $db_row
925
-     * @param array           $entity_array
926
-     * @return array the _links item in the entity
927
-     * @throws EE_Error
928
-     * @throws EE_Error
929
-     * @global WP_REST_Server $wp_rest_server
930
-     */
931
-    protected function getEntityLinks(EEM_Base $model, array $db_row, array $entity_array): array
932
-    {
933
-        // add basic links
934
-        $links = [];
935
-        if ($model->has_primary_key_field()) {
936
-            $links['self'] = [
937
-                [
938
-                    'href' => $this->getVersionedLinkTo(
939
-                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940
-                        . '/'
941
-                        . $entity_array[ $model->primary_key_name() ]
942
-                    ),
943
-                ],
944
-            ];
945
-        }
946
-        $links['collection'] = [
947
-            [
948
-                'href' => $this->getVersionedLinkTo(
949
-                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
950
-                ),
951
-            ],
952
-        ];
953
-        // add links to related models
954
-        if ($model->has_primary_key_field()) {
955
-            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956
-                $related_model_part                                                      =
957
-                    Read::getRelatedEntityName($relation_name, $relation_obj);
958
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
959
-                    [
960
-                        'href'   => $this->getVersionedLinkTo(
961
-                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962
-                            . '/'
963
-                            . $entity_array[ $model->primary_key_name() ]
964
-                            . '/'
965
-                            . $related_model_part
966
-                        ),
967
-                        'single' => $relation_obj instanceof EE_Belongs_To_Relation,
968
-                    ],
969
-                ];
970
-            }
971
-        }
972
-        return $links;
973
-    }
974
-
975
-
976
-    /**
977
-     * Adds the included models indicated in the request to the entity provided
978
-     *
979
-     * @param EEM_Base        $model
980
-     * @param WP_REST_Request $rest_request
981
-     * @param array           $entity_array
982
-     * @param array           $db_row
983
-     * @param boolean         $included_items_protected if the original item is password protected, don't include any
984
-     *                                                  related models.
985
-     * @return array the modified entity
986
-     * @throws EE_Error
987
-     * @throws ReflectionException
988
-     */
989
-    protected function includeRequestedModels(
990
-        EEM_Base $model,
991
-        WP_REST_Request $rest_request,
992
-        array $entity_array,
993
-        array $db_row = [],
994
-        bool $included_items_protected = false
995
-    ): array {
996
-        // if $db_row not included, hope the entity array has what we need
997
-        if (! $db_row) {
998
-            $db_row = $entity_array;
999
-        }
1000
-        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001
-        foreach ($relation_settings as $relation_name => $relation_obj) {
1002
-            $related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1003
-                $rest_request->get_param('include'),
1004
-                $relation_name
1005
-            );
1006
-            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
1007
-                $rest_request->get_param('calculate'),
1008
-                $relation_name
1009
-            );
1010
-            // did they specify they wanted to include a related model, or
1011
-            // specific fields from a related model?
1012
-            // or did they specify to calculate a field from a related model?
1013
-            if ($related_fields_to_include || $related_fields_to_calculate) {
1014
-                // if so, we should include at least some part of the related model
1015
-                $pretend_related_request = new WP_REST_Request();
1016
-                $pretend_related_request->set_query_params(
1017
-                    [
1018
-                        'caps'      => $rest_request->get_param('caps'),
1019
-                        'include'   => $related_fields_to_include,
1020
-                        'calculate' => $related_fields_to_calculate,
1021
-                        'password'  => $rest_request->get_param('password'),
1022
-                    ]
1023
-                );
1024
-                $pretend_related_request->add_header('no_rest_headers', true);
1025
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1026
-                    $model->get_index_primary_key_string(
1027
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
1028
-                    )
1029
-                );
1030
-                if (! $included_items_protected) {
1031
-                    try {
1032
-                        $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033
-                            $primary_model_query_params,
1034
-                            $relation_obj,
1035
-                            $pretend_related_request
1036
-                        );
1037
-                    } catch (RestException $e) {
1038
-                        $related_results = new WP_Error('entity_relations_error', $e->getMessage());
1039
-                    }
1040
-                } else {
1041
-                    // they're protected, hide them.
1042
-                    $related_results              = null;
1043
-                    $entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1044
-                }
1045
-                if ($related_results instanceof WP_Error) {
1046
-                    $related_results = $relation_obj instanceof EE_Belongs_To_Relation
1047
-                        ? null
1048
-                        : [];
1049
-                }
1050
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1051
-            }
1052
-        }
1053
-        return $entity_array;
1054
-    }
1055
-
1056
-
1057
-    /**
1058
-     * If the user has requested only specific properties (including meta properties like _links or _protected)
1059
-     * remove everything else.
1060
-     *
1061
-     * @param EEM_Base        $model
1062
-     * @param WP_REST_Request $rest_request
1063
-     * @param                 $entity_array
1064
-     * @return array
1065
-     * @throws EE_Error
1066
-     * @since 4.9.74.p
1067
-     */
1068
-    protected function includeOnlyRequestedProperties(
1069
-        EEM_Base $model,
1070
-        WP_REST_Request $rest_request,
1071
-        $entity_array
1072
-    ): array {
1073
-        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1074
-        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1075
-        // if they passed in * or didn't specify any includes, return everything
1076
-        if (
1077
-            ! in_array('*', $includes_for_this_model)
1078
-            && ! empty($includes_for_this_model)
1079
-        ) {
1080
-            if ($model->has_primary_key_field()) {
1081
-                // always include the primary key. ya just gotta know that at least
1082
-                $includes_for_this_model[] = $model->primary_key_name();
1083
-            }
1084
-            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1085
-                $includes_for_this_model[] = '_calculated_fields';
1086
-            }
1087
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1088
-        }
1089
-        return $entity_array;
1090
-    }
1091
-
1092
-
1093
-    /**
1094
-     * Returns a new array with all the names of models removed. Eg
1095
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1096
-     *
1097
-     * @param array $model_names
1098
-     * @return array
1099
-     */
1100
-    private function removeModelNamesFromArray(array $model_names): array
1101
-    {
1102
-        return array_diff($model_names, array_keys(EE_Registry::instance()->non_abstract_db_models));
1103
-    }
1104
-
1105
-
1106
-    /**
1107
-     * Gets the calculated fields for the response
1108
-     *
1109
-     * @param EEM_Base        $model
1110
-     * @param array           $wpdb_row
1111
-     * @param WP_REST_Request $rest_request
1112
-     * @param boolean         $row_is_protected whether this row is password protected or not
1113
-     * @return stdClass the _calculations item in the entity
1114
-     * @throws RestException if a default value has a PHP object, which should never do (and if we
1115
-     * @throws EE_Error
1116
-     *                                          did, let's know about it ASAP, so let the exception bubble up)
1117
-     */
1118
-    protected function getEntityCalculations(
1119
-        EEM_Base $model,
1120
-        array $wpdb_row,
1121
-        WP_REST_Request $rest_request,
1122
-        bool $row_is_protected = false
1123
-    ): stdClass {
1124
-        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1125
-            $rest_request->get_param('calculate'),
1126
-            ''
1127
-        );
1128
-        // note: setting calculate=* doesn't do anything
1129
-        $calculated_fields_to_return = new stdClass();
1130
-        $protected_fields            = [];
1131
-        foreach ($calculated_fields as $field_to_calculate) {
1132
-            try {
1133
-                // it's password protected, so they shouldn't be able to read this. Remove the value
1134
-                $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135
-                if (
1136
-                    $row_is_protected
1137
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
-                    && $schema['properties'][ $field_to_calculate ]['protected']
1139
-                ) {
1140
-                    $calculated_value   = null;
1141
-                    $protected_fields[] = $field_to_calculate;
1142
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1144
-                            case 'boolean':
1145
-                                $calculated_value = false;
1146
-                                break;
1147
-                            case 'integer':
1148
-                                $calculated_value = 0;
1149
-                                break;
1150
-                            case 'string':
1151
-                                $calculated_value = '';
1152
-                                break;
1153
-                            case 'array':
1154
-                                $calculated_value = [];
1155
-                                break;
1156
-                            case 'object':
1157
-                                $calculated_value = new stdClass();
1158
-                                break;
1159
-                        }
1160
-                    }
1161
-                } else {
1162
-                    $calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1163
-                        null,
1164
-                        $this->fields_calculator->retrieveCalculatedFieldValue(
1165
-                            $model,
1166
-                            $field_to_calculate,
1167
-                            $wpdb_row,
1168
-                            $rest_request,
1169
-                            $this
1170
-                        ),
1171
-                        $this->getModelVersionInfo()->requestedVersion()
1172
-                    );
1173
-                }
1174
-                $calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1175
-            } catch (RestException $e) {
1176
-                // if we don't have permission to read it, just leave it out. but let devs know about the problem
1177
-                $this->setResponseHeader(
1178
-                    'Notices-Field-Calculation-Errors['
1179
-                    . $e->getStringCode()
1180
-                    . ']['
1181
-                    . $model->get_this_model_name()
1182
-                    . ']['
1183
-                    . $field_to_calculate
1184
-                    . ']',
1185
-                    $e->getMessage()
1186
-                );
1187
-            }
1188
-        }
1189
-        $calculated_fields_to_return->_protected = $protected_fields;
1190
-        return $calculated_fields_to_return;
1191
-    }
1192
-
1193
-
1194
-    /**
1195
-     * Gets the full URL to the resource, taking the requested version into account
1196
-     *
1197
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1198
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1199
-     * @throws EE_Error
1200
-     * @throws EE_Error
1201
-     */
1202
-    public function getVersionedLinkTo(string $link_part_after_version_and_slash): string
1203
-    {
1204
-        return rest_url(
1205
-            EED_Core_Rest_Api::get_versioned_route_to(
1206
-                $link_part_after_version_and_slash,
1207
-                $this->getModelVersionInfo()->requestedVersion()
1208
-            )
1209
-        );
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * Gets the correct lowercase name for the relation in the API according
1215
-     * to the relation's type
1216
-     *
1217
-     * @param string                 $relation_name
1218
-     * @param EE_Model_Relation_Base $relation_obj
1219
-     * @return string
1220
-     */
1221
-    public static function getRelatedEntityName(string $relation_name, EE_Model_Relation_Base $relation_obj): string
1222
-    {
1223
-        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1224
-            return strtolower($relation_name);
1225
-        } else {
1226
-            return EEH_Inflector::pluralize_and_lower($relation_name);
1227
-        }
1228
-    }
1229
-
1230
-
1231
-    /**
1232
-     * Gets the one model object with the specified id for the specified model
1233
-     *
1234
-     * @param EEM_Base        $model
1235
-     * @param WP_REST_Request $request
1236
-     * @return array
1237
-     * @throws EE_Error
1238
-     * @throws EE_Error
1239
-     * @throws ReflectionException
1240
-     */
1241
-    public function getEntityFromModel(EEM_Base $model, WP_REST_Request $request): array
1242
-    {
1243
-        $context = $this->validateContext($request->get_param('caps'));
1244
-        return $this->getOneOrReportPermissionError($model, $request, $context);
1245
-    }
1246
-
1247
-
1248
-    /**
1249
-     * If a context is provided which isn't valid, maybe it was added in a future
1250
-     * version so just treat it as a default read
1251
-     *
1252
-     * @param string $context
1253
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1254
-     */
1255
-    public function validateContext(string $context): string
1256
-    {
1257
-        if (! $context) {
1258
-            $context = EEM_Base::caps_read;
1259
-        }
1260
-        $valid_contexts = EEM_Base::valid_cap_contexts();
1261
-        if (in_array($context, $valid_contexts)) {
1262
-            return $context;
1263
-        } else {
1264
-            return EEM_Base::caps_read;
1265
-        }
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * Verifies the passed in value is an allowable default where conditions value.
1271
-     *
1272
-     * @param $default_query_params
1273
-     * @return string
1274
-     */
1275
-    public function validateDefaultQueryParams($default_query_params): string
1276
-    {
1277
-        $valid_default_where_conditions_for_api_calls = [
1278
-            EEM_Base::default_where_conditions_all,
1279
-            EEM_Base::default_where_conditions_minimum_all,
1280
-            EEM_Base::default_where_conditions_minimum_others,
1281
-        ];
1282
-        if (! $default_query_params) {
1283
-            $default_query_params = EEM_Base::default_where_conditions_all;
1284
-        }
1285
-        if (
1286
-            in_array(
1287
-                $default_query_params,
1288
-                $valid_default_where_conditions_for_api_calls,
1289
-                true
1290
-            )
1291
-        ) {
1292
-            return $default_query_params;
1293
-        }
1294
-        return EEM_Base::default_where_conditions_all;
1295
-    }
1296
-
1297
-
1298
-    /**
1299
-     * Translates API filter get parameter into model query params @param EEM_Base $model
1300
-     *
1301
-     * @param array $query_params
1302
-     * @return array model query params (@see
1303
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1304
-     *               or FALSE to indicate that absolutely no results should be returned
1305
-     * @throws EE_Error
1306
-     * @throws RestException
1307
-     * @see
1308
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1309
-     *               Note: right now the query parameter keys for fields (and related fields) can be left as-is, but
1310
-     *               it's quite possible this will change someday. Also, this method's contents might be candidate for
1311
-     *               moving to Model_Data_Translator
1312
-     *
1313
-     */
1314
-    public function createModelQueryParams(EEM_Base $model, array $query_params): array
1315
-    {
1316
-        $model_query_params = [];
1317
-        if (isset($query_params['where'])) {
1318
-            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1319
-                $query_params['where'],
1320
-                $model,
1321
-                $this->getModelVersionInfo()->requestedVersion()
1322
-            );
1323
-        }
1324
-        if (isset($query_params['order_by'])) {
1325
-            $order_by = $query_params['order_by'];
1326
-        } elseif (isset($query_params['orderby'])) {
1327
-            $order_by = $query_params['orderby'];
1328
-        } else {
1329
-            $order_by = null;
1330
-        }
1331
-        if ($order_by !== null) {
1332
-            if (is_array($order_by)) {
1333
-                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1334
-            } else {
1335
-                // it's a single item
1336
-                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1337
-            }
1338
-            $model_query_params['order_by'] = $order_by;
1339
-        }
1340
-        if (isset($query_params['group_by'])) {
1341
-            $group_by = $query_params['group_by'];
1342
-        } elseif (isset($query_params['groupby'])) {
1343
-            $group_by = $query_params['groupby'];
1344
-        } else {
1345
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1346
-        }
1347
-        // make sure they're all real names
1348
-        if (is_array($group_by)) {
1349
-            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1350
-        }
1351
-        if ($group_by !== null) {
1352
-            $model_query_params['group_by'] = $group_by;
1353
-        }
1354
-        if (isset($query_params['having'])) {
1355
-            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1356
-                $query_params['having'],
1357
-                $model,
1358
-                $this->getModelVersionInfo()->requestedVersion()
1359
-            );
1360
-        }
1361
-        if (isset($query_params['order'])) {
1362
-            $model_query_params['order'] = $query_params['order'];
1363
-        }
1364
-        if (isset($query_params['mine'])) {
1365
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1366
-        }
1367
-        if (isset($query_params['limit'])) {
1368
-            // limit should be either a string like '23' or '23,43', or an array with two items in it
1369
-            if (! is_array($query_params['limit'])) {
1370
-                $limit_array = explode(',', (string) $query_params['limit']);
1371
-            } else {
1372
-                $limit_array = $query_params['limit'];
1373
-            }
1374
-            $sanitized_limit = [];
1375
-            foreach ($limit_array as $limit_part) {
1376
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377
-                    throw new EE_Error(
1378
-                        sprintf(
1379
-                            esc_html__(
1380
-                            // @codingStandardsIgnoreStart
1381
-                                'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1382
-                                // @codingStandardsIgnoreEnd
1383
-                                'event_espresso'
1384
-                            ),
1385
-                            wp_json_encode($query_params['limit'])
1386
-                        )
1387
-                    );
1388
-                }
1389
-                $sanitized_limit[] = (int) $limit_part;
1390
-            }
1391
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1392
-        } else {
1393
-            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1394
-        }
1395
-        if (isset($query_params['caps'])) {
1396
-            $model_query_params['caps'] = $this->validateContext($query_params['caps']);
1397
-        } else {
1398
-            $model_query_params['caps'] = EEM_Base::caps_read;
1399
-        }
1400
-        if (isset($query_params['default_where_conditions'])) {
1401
-            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1402
-                $query_params['default_where_conditions']
1403
-            );
1404
-        }
1405
-        // if this is a model protected by a password on another model, exclude the password protected
1406
-        // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1407
-        // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1408
-        if (
1409
-            ! $model->hasPassword()
1410
-            && $model->restrictedByRelatedModelPassword()
1411
-            && $model_query_params['caps'] === EEM_Base::caps_read
1412
-        ) {
1413
-            if (empty($query_params['password'])) {
1414
-                $model_query_params['exclude_protected'] = true;
1415
-            }
1416
-        }
1417
-
1418
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1419
-    }
1420
-
1421
-
1422
-    /**
1423
-     * Changes the REST-style query params for use in the models
1424
-     *
1425
-     * @param EEM_Base $model
1426
-     * @param array    $query_params sub-array from @see EEM_Base::get_all()
1427
-     * @return array
1428
-     * @deprecated
1429
-     */
1430
-    public function prepareRestQueryParamsKeyForModels(EEM_Base $model, array $query_params): array
1431
-    {
1432
-        $model_ready_query_params = [];
1433
-        foreach ($query_params as $key => $value) {
1434
-            $model_ready_query_params[ $key ] = is_array($value)
1435
-                ? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436
-                : $value;
1437
-        }
1438
-        return $model_ready_query_params;
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * @param EEM_Base $model
1444
-     * @param array    $query_params
1445
-     * @return array
1446
-     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1447
-     */
1448
-    public function prepareRestQueryParamsValuesForModels(EEM_Base $model, array $query_params): array
1449
-    {
1450
-        $model_ready_query_params = [];
1451
-        foreach ($query_params as $key => $value) {
1452
-            if (is_array($value)) {
1453
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454
-            } else {
1455
-                $model_ready_query_params[ $key ] = $value;
1456
-            }
1457
-        }
1458
-        return $model_ready_query_params;
1459
-    }
1460
-
1461
-
1462
-    /**
1463
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1464
-     * If no prefix is specified, returns items with no period.
1465
-     *
1466
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1467
-     * @param string       $prefix            "Event" or "foobar"
1468
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1469
-     *                                        we only return strings starting with that and a period; if no prefix was
1470
-     *                                        specified we return all items containing NO periods
1471
-     */
1472
-    public function explodeAndGetItemsPrefixedWith($string_to_explode, string $prefix): array
1473
-    {
1474
-        if (is_string($string_to_explode)) {
1475
-            $exploded_contents = explode(',', $string_to_explode);
1476
-        } elseif (is_array($string_to_explode)) {
1477
-            $exploded_contents = $string_to_explode;
1478
-        } else {
1479
-            $exploded_contents = [];
1480
-        }
1481
-        // if the string was empty, we want an empty array
1482
-        $exploded_contents    = array_filter($exploded_contents);
1483
-        $contents_with_prefix = [];
1484
-        foreach ($exploded_contents as $item) {
1485
-            $item = trim($item);
1486
-            // if no prefix was provided, so we look for items with no "." in them
1487
-            if (! $prefix) {
1488
-                // does this item have a period?
1489
-                if (strpos($item, '.') === false) {
1490
-                    // if not, then its what we're looking for
1491
-                    $contents_with_prefix[] = $item;
1492
-                }
1493
-            } elseif (strpos($item, $prefix . '.') === 0) {
1494
-                // this item has the prefix and a period, grab it
1495
-                $contents_with_prefix[] = substr(
1496
-                    $item,
1497
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1498
-                );
1499
-            } elseif ($item === $prefix) {
1500
-                // this item is JUST the prefix
1501
-                // so let's grab everything after, which is a blank string
1502
-                $contents_with_prefix[] = '';
1503
-            }
1504
-        }
1505
-        return $contents_with_prefix;
1506
-    }
1507
-
1508
-
1509
-    /**
1510
-     * @param array|string $include_string @see Read:handle_request_get_all
1511
-     * @param string|null  $model_name
1512
-     * @return array of fields for this model. If $model_name is provided, then
1513
-     *                                     the fields for that model, with the model's name removed from each.
1514
-     *                                     If $include_string was blank or '*' returns an empty array
1515
-     * @throws EE_Error
1516
-     * @throws EE_Error
1517
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1518
-     *                                     Deprecated because its return values were really quite confusing- sometimes
1519
-     *                                     it returned an empty array (when the include string was blank or '*') or
1520
-     *                                     sometimes it returned array('*') (when you provided a model and a model of
1521
-     *                                     that kind was found). Parses the $include_string so we fetch all the field
1522
-     *                                     names relating to THIS model
1523
-     *                                     (ie have NO period in them), or for the provided model (ie start with the
1524
-     *                                     model name and then a period).
1525
-     */
1526
-    public function extractIncludesForThisModel($include_string, string $model_name = ''): array
1527
-    {
1528
-        if (is_array($include_string)) {
1529
-            $include_string = implode(',', $include_string);
1530
-        }
1531
-        if ($include_string === '*' || $include_string === '') {
1532
-            return [];
1533
-        }
1534
-        $includes                    = explode(',', $include_string);
1535
-        $extracted_fields_to_include = [];
1536
-        if ($model_name) {
1537
-            foreach ($includes as $field_to_include) {
1538
-                $field_to_include = trim($field_to_include);
1539
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1540
-                    // found the model name at the exact start
1541
-                    $field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1542
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1543
-                } elseif ($field_to_include == $model_name) {
1544
-                    $extracted_fields_to_include[] = '*';
1545
-                }
1546
-            }
1547
-        } else {
1548
-            // look for ones with no period
1549
-            foreach ($includes as $field_to_include) {
1550
-                $field_to_include = trim($field_to_include);
1551
-                if (
1552
-                    strpos($field_to_include, '.') === false
1553
-                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1554
-                ) {
1555
-                    $extracted_fields_to_include[] = $field_to_include;
1556
-                }
1557
-            }
1558
-        }
1559
-        return $extracted_fields_to_include;
1560
-    }
1561
-
1562
-
1563
-    /**
1564
-     * Gets the single item using the model according to the request in the context given, otherwise
1565
-     * returns that it's inaccessible to the current user
1566
-     *
1567
-     * @param EEM_Base        $model
1568
-     * @param WP_REST_Request $request
1569
-     * @param null            $context
1570
-     * @return array
1571
-     * @throws EE_Error
1572
-     * @throws ReflectionException
1573
-     */
1574
-    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null): array
1575
-    {
1576
-        $query_params = [[$model->primary_key_name() => $request->get_param('id')], 'limit' => 1];
1577
-        if ($model instanceof EEM_Soft_Delete_Base) {
1578
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1579
-        }
1580
-        $restricted_query_params         = $query_params;
1581
-        $restricted_query_params['caps'] = $context;
1582
-        $this->setDebugInfo('model query params', $restricted_query_params);
1583
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
-        if (! empty($model_rows)) {
1585
-            return $this->createEntityFromWpdbResult(
1586
-                $model,
1587
-                reset($model_rows),
1588
-                $request
1589
-            );
1590
-        } else {
1591
-            // ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1592
-            $lowercase_model_name = strtolower($model->get_this_model_name());
1593
-            if ($model->exists($query_params)) {
1594
-                // you got shafted- it existed but we didn't want to tell you!
1595
-                throw new RestException(
1596
-                    'rest_user_cannot_' . $context,
1597
-                    sprintf(
1598
-                        esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599
-                        $context,
1600
-                        $lowercase_model_name,
1601
-                        Capabilities::getMissingPermissionsString(
1602
-                            $model,
1603
-                            $context
1604
-                        )
1605
-                    ),
1606
-                    ['status' => 403]
1607
-                );
1608
-            } else {
1609
-                // it's not you. It just doesn't exist
1610
-                throw new RestException(
1611
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
1612
-                    sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1613
-                    ['status' => 404]
1614
-                );
1615
-            }
1616
-        }
1617
-    }
1618
-
1619
-
1620
-    /**
1621
-     * Checks that if this content requires a password to be read, that it's been provided and is correct.
1622
-     *
1623
-     * @param EEM_Base        $model
1624
-     * @param array           $model_row
1625
-     * @param array           $query_params Adds 'default_where_conditions' => 'minimum'
1626
-     *                                      to ensure we don't confuse trashed with password protected.
1627
-     * @param WP_REST_Request $request
1628
-     * @throws EE_Error
1629
-     * @throws InvalidArgumentException
1630
-     * @throws InvalidDataTypeException
1631
-     * @throws InvalidInterfaceException
1632
-     * @throws RestPasswordRequiredException
1633
-     * @throws RestPasswordIncorrectException
1634
-     * @throws ModelConfigurationException
1635
-     * @throws ReflectionException
1636
-     * @since 4.9.74.p
1637
-     */
1638
-    protected function checkPassword(EEM_Base $model, array $model_row, array $query_params, WP_REST_Request $request)
1639
-    {
1640
-        $query_params['default_where_conditions'] = 'minimum';
1641
-        // stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1642
-        // or you don't.
1643
-        $request_caps = $request->get_param('caps');
1644
-        if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1645
-            return;
1646
-        }
1647
-        // if this entity requires a password, they better give it and it better be right!
1648
-        if (
1649
-            $model->hasPassword()
1650
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1651
-        ) {
1652
-            if (empty($request['password'])) {
1653
-                throw new RestPasswordRequiredException();
1654
-            }
1655
-            if (
1656
-                ! hash_equals(
1657
-                    $model_row[ $model->getPasswordField()->get_qualified_column() ],
1658
-                    $request['password']
1659
-                )
1660
-            ) {
1661
-                throw new RestPasswordIncorrectException();
1662
-            }
1663
-        } elseif (
1664
-            // wait! maybe this content is password protected
1665
-            $model->restrictedByRelatedModelPassword()
1666
-            && $request->get_param('caps') === EEM_Base::caps_read
1667
-        ) {
1668
-            $password_supplied = $request->get_param('password');
1669
-            if (empty($password_supplied)) {
1670
-                $query_params['exclude_protected'] = true;
1671
-                if (! $model->exists($query_params)) {
1672
-                    throw new RestPasswordRequiredException();
1673
-                }
1674
-            } else {
1675
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
-                if (! $model->exists($query_params)) {
1677
-                    throw new RestPasswordIncorrectException();
1678
-                }
1679
-            }
1680
-        }
1681
-    }
1682
-
1683
-
1684
-    private static function endpointParsingError(string $model_name): WP_Error
1685
-    {
1686
-        return new WP_Error(
1687
-            'endpoint_parsing_error',
1688
-            sprintf(
1689
-                esc_html__(
1690
-                    'There is no model for endpoint %s. Please contact event espresso support',
1691
-                    'event_espresso'
1692
-                ),
1693
-                $model_name
1694
-            )
1695
-        );
1696
-    }
53
+	/**
54
+	 * @var CalculatedModelFields
55
+	 */
56
+	protected $fields_calculator;
57
+
58
+
59
+	/**
60
+	 * Read constructor.
61
+	 *
62
+	 * @param CalculatedModelFields $fields_calculator
63
+	 */
64
+	public function __construct(CalculatedModelFields $fields_calculator)
65
+	{
66
+		parent::__construct();
67
+		$this->fields_calculator = $fields_calculator;
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param string $version
73
+	 * @return Read
74
+	 * @since $VID:$
75
+	 */
76
+	public static function getModelReadController(string $version): Read
77
+	{
78
+		/** @var Read $controller */
79
+		$controller = LoaderFactory::getLoader()->getNew(Read::class);
80
+		$controller->setRequestedVersion($version);
81
+		return $controller;
82
+	}
83
+
84
+
85
+	/**
86
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
87
+	 *
88
+	 * @param WP_REST_Request $request
89
+	 * @param string          $version
90
+	 * @param string          $model_name
91
+	 * @return WP_REST_Response
92
+	 * @throws InvalidArgumentException
93
+	 * @throws InvalidDataTypeException
94
+	 * @throws InvalidInterfaceException
95
+	 */
96
+	public static function handleRequestGetAll(
97
+		WP_REST_Request $request,
98
+		string $version,
99
+		string $model_name
100
+	): WP_REST_Response {
101
+		$controller = Read::getModelReadController($version);
102
+		try {
103
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104
+				return $controller->sendResponse(Read::endpointParsingError($model_name));
105
+			}
106
+			return $controller->sendResponse(
107
+				$controller->getEntitiesFromModel(
108
+					$controller->getModelVersionInfo()->loadModel($model_name),
109
+					$request
110
+				)
111
+			);
112
+		} catch (Exception $e) {
113
+			return $controller->sendResponse($e);
114
+		}
115
+	}
116
+
117
+
118
+	/**
119
+	 * Prepares and returns schema for any OPTIONS request.
120
+	 *
121
+	 * @param string $version    The API endpoint version being used.
122
+	 * @param string $model_name Something like `Event` or `Registration`
123
+	 * @return array
124
+	 * @throws InvalidArgumentException
125
+	 * @throws InvalidDataTypeException
126
+	 * @throws InvalidInterfaceException
127
+	 */
128
+	public static function handleSchemaRequest(string $version, string $model_name): array
129
+	{
130
+		$controller = Read::getModelReadController($version);
131
+		try {
132
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133
+				return [];
134
+			}
135
+			// get the model for this version
136
+			$model        = $controller->getModelVersionInfo()->loadModel($model_name);
137
+			$model_schema = new JsonModelSchema(
138
+				$model,
139
+				LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields')
140
+			);
141
+			return $model_schema->getModelSchemaForRelations(
142
+				$controller->getModelVersionInfo()->relationSettings($model),
143
+				$controller->customizeSchemaForRestResponse(
144
+					$model,
145
+					$model_schema->getModelSchemaForFields(
146
+						$controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
147
+						$model_schema->getInitialSchemaStructure()
148
+					)
149
+				)
150
+			);
151
+		} catch (Exception $e) {
152
+			return [];
153
+		}
154
+	}
155
+
156
+
157
+	/**
158
+	 * This loops through each field in the given schema for the model and does the following:
159
+	 * - add any extra fields that are REST API specific and related to existing fields.
160
+	 * - transform default values into the correct format for a REST API response.
161
+	 *
162
+	 * @param EEM_Base $model
163
+	 * @param array    $schema
164
+	 * @return array  The final schema.
165
+	 * @throws EE_Error
166
+	 * @throws EE_Error
167
+	 */
168
+	protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema): array
169
+	{
170
+		foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
171
+			$schema = $this->translateDefaultsForRestResponse(
172
+				$field_name,
173
+				$field,
174
+				$this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
175
+			);
176
+		}
177
+		return $schema;
178
+	}
179
+
180
+
181
+	/**
182
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
183
+	 * response.
184
+	 *
185
+	 * @param                      $field_name
186
+	 * @param EE_Model_Field_Base  $field
187
+	 * @param array                $schema
188
+	 * @return array
189
+	 * @throws RestException  if a default value has a PHP object, which we should never do
190
+	 *                                  (but if we did, let's know about it ASAP, so let the exception bubble up)
191
+	 * @throws EE_Error
192
+	 *
193
+	 */
194
+	protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195
+	{
196
+		if (isset($schema['properties'][ $field_name ]['default'])) {
197
+			if (is_array($schema['properties'][ $field_name ]['default'])) {
198
+				foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
199
+					if ($default_key === 'raw') {
200
+						$schema['properties'][ $field_name ]['default'][ $default_key ] =
201
+							ModelDataTranslator::prepareFieldValueForJson(
202
+								$field,
203
+								$default_value,
204
+								$this->getModelVersionInfo()->requestedVersion()
205
+							);
206
+					}
207
+				}
208
+			} else {
209
+				$schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210
+					$field,
211
+					$schema['properties'][ $field_name ]['default'],
212
+					$this->getModelVersionInfo()->requestedVersion()
213
+				);
214
+			}
215
+		}
216
+		return $schema;
217
+	}
218
+
219
+
220
+	/**
221
+	 * Adds additional fields to the schema
222
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
223
+	 * needs to be added to the schema.
224
+	 *
225
+	 * @param                      $field_name
226
+	 * @param EE_Model_Field_Base  $field
227
+	 * @param array                $schema
228
+	 * @return array
229
+	 */
230
+	protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231
+	{
232
+		if ($field instanceof EE_Datetime_Field) {
233
+			$schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
234
+			// modify the description
235
+			$schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
236
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237
+				wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238
+			);
239
+		}
240
+		return $schema;
241
+	}
242
+
243
+
244
+	/**
245
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
246
+	 *
247
+	 * @return string
248
+	 */
249
+	protected function getRouteFromRequest(): string
250
+	{
251
+		if (
252
+			isset($GLOBALS['wp'])
253
+			&& $GLOBALS['wp'] instanceof WP
254
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
255
+		) {
256
+			return $GLOBALS['wp']->query_vars['rest_route'];
257
+		} else {
258
+			/** @var RequestInterface $request */
259
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
260
+			return $request->serverParamIsSet('PATH_INFO')
261
+				? $request->getServerParam('PATH_INFO')
262
+				: '/';
263
+		}
264
+	}
265
+
266
+
267
+	/**
268
+	 * Gets a single entity related to the model indicated in the path and its id
269
+	 *
270
+	 * @param WP_REST_Request $request
271
+	 * @param string          $version
272
+	 * @param string          $model_name
273
+	 * @return WP_REST_Response
274
+	 * @throws InvalidDataTypeException
275
+	 * @throws InvalidInterfaceException
276
+	 * @throws InvalidArgumentException
277
+	 */
278
+	public static function handleRequestGetOne(
279
+		WP_REST_Request $request,
280
+		string $version,
281
+		string $model_name
282
+	): WP_REST_Response {
283
+		$controller = Read::getModelReadController($version);
284
+		try {
285
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286
+				return $controller->sendResponse(Read::endpointParsingError($model_name));
287
+			}
288
+			return $controller->sendResponse(
289
+				$controller->getEntityFromModel(
290
+					$controller->getModelVersionInfo()->loadModel($model_name),
291
+					$request
292
+				)
293
+			);
294
+		} catch (Exception $e) {
295
+			return $controller->sendResponse($e);
296
+		}
297
+	}
298
+
299
+
300
+	/**
301
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
302
+	 * to the item with the given id
303
+	 *
304
+	 * @param WP_REST_Request $request
305
+	 * @param string          $version
306
+	 * @param string          $model_name
307
+	 * @param string          $related_model_name
308
+	 * @return WP_REST_Response
309
+	 * @throws InvalidDataTypeException
310
+	 * @throws InvalidInterfaceException
311
+	 * @throws InvalidArgumentException
312
+	 */
313
+	public static function handleRequestGetRelated(
314
+		WP_REST_Request $request,
315
+		string $version,
316
+		string $model_name,
317
+		string $related_model_name
318
+	): WP_REST_Response {
319
+		$controller = Read::getModelReadController($version);
320
+		try {
321
+			$main_model = $controller->validateModel($model_name);
322
+			$controller->validateModel($related_model_name);
323
+			return $controller->sendResponse(
324
+				$controller->getEntitiesFromRelation(
325
+					$request->get_param('id'),
326
+					$main_model->related_settings_for($related_model_name),
327
+					$request
328
+				)
329
+			);
330
+		} catch (Exception $e) {
331
+			return $controller->sendResponse($e);
332
+		}
333
+	}
334
+
335
+
336
+	/**
337
+	 * Gets a collection for the given model and filters
338
+	 *
339
+	 * @param EEM_Base        $model
340
+	 * @param WP_REST_Request $request
341
+	 * @return array
342
+	 * @throws EE_Error
343
+	 * @throws InvalidArgumentException
344
+	 * @throws InvalidDataTypeException
345
+	 * @throws InvalidInterfaceException
346
+	 * @throws ReflectionException
347
+	 * @throws RestException
348
+	 */
349
+	public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350
+	{
351
+		$query_params = $this->createModelQueryParams($model, $request->get_params());
352
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354
+			throw new RestException(
355
+				sprintf('rest_%s_cannot_list', $model_name_plural),
356
+				sprintf(
357
+					esc_html__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
358
+					$model_name_plural,
359
+					Capabilities::getMissingPermissionsString($model, $query_params['caps'])
360
+				),
361
+				['status' => 403]
362
+			);
363
+		}
364
+		if (! $request->get_header('no_rest_headers')) {
365
+			$this->setHeadersFromQueryParams($model, $query_params);
366
+		}
367
+		/** @type array $results */
368
+		$results      = $model->get_all_wpdb_results($query_params);
369
+		$nice_results = [];
370
+		foreach ($results as $result) {
371
+			$nice_results[] = $this->createEntityFromWpdbResult(
372
+				$model,
373
+				$result,
374
+				$request
375
+			);
376
+		}
377
+		return $nice_results;
378
+	}
379
+
380
+
381
+	/**
382
+	 * Gets the collection for given relation object
383
+	 * The same as Read::get_entities_from_model(), except if the relation
384
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
385
+	 * the join-model-object into the results
386
+	 *
387
+	 * @param array                  $primary_model_query_params  query params for finding the item from which
388
+	 *                                                            relations will be based
389
+	 * @param EE_Model_Relation_Base $relation
390
+	 * @param WP_REST_Request        $request
391
+	 * @return array
392
+	 * @throws EE_Error
393
+	 * @throws InvalidArgumentException
394
+	 * @throws InvalidDataTypeException
395
+	 * @throws InvalidInterfaceException
396
+	 * @throws ReflectionException
397
+	 * @throws RestException
398
+	 * @throws ModelConfigurationException
399
+	 */
400
+	protected function getEntitiesFromRelationUsingModelQueryParams(
401
+		array $primary_model_query_params,
402
+		EE_Model_Relation_Base $relation,
403
+		WP_REST_Request $request
404
+	): array {
405
+		$context       = $this->validateContext($request->get_param('caps'));
406
+		$model         = $relation->get_this_model();
407
+		$related_model = $relation->get_other_model();
408
+		if (! isset($primary_model_query_params[0])) {
409
+			$primary_model_query_params[0] = [];
410
+		}
411
+		// check if they can access the 1st model object
412
+		$primary_model_query_params = [
413
+			0       => $primary_model_query_params[0],
414
+			'limit' => 1,
415
+		];
416
+		if ($model instanceof EEM_Soft_Delete_Base) {
417
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
418
+				$primary_model_query_params
419
+			);
420
+		}
421
+		$restricted_query_params          = $primary_model_query_params;
422
+		$restricted_query_params['caps']  = $context;
423
+		$restricted_query_params['limit'] = 1;
424
+		$this->setDebugInfo('main model query params', $restricted_query_params);
425
+		$this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
426
+		$primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
427
+		$primary_model_row  = null;
428
+		if (is_array($primary_model_rows)) {
429
+			$primary_model_row = reset($primary_model_rows);
430
+		}
431
+		if (
432
+			! (
433
+				$primary_model_row
434
+				&& Capabilities::currentUserHasPartialAccessTo($related_model, $context)
435
+			)
436
+		) {
437
+			if ($relation instanceof EE_Belongs_To_Relation) {
438
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
439
+			} else {
440
+				$related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
441
+					$related_model->get_this_model_name()
442
+				);
443
+			}
444
+			throw new RestException(
445
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
446
+				sprintf(
447
+					esc_html__(
448
+						'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
449
+						'event_espresso'
450
+					),
451
+					$related_model_name_maybe_plural,
452
+					$relation->get_this_model()->get_this_model_name(),
453
+					implode(
454
+						',',
455
+						array_keys(
456
+							Capabilities::getMissingPermissions($related_model, $context)
457
+						)
458
+					)
459
+				),
460
+				['status' => 403]
461
+			);
462
+		}
463
+
464
+		$this->checkPassword(
465
+			$model,
466
+			$primary_model_row,
467
+			$restricted_query_params,
468
+			$request
469
+		);
470
+		$query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
+			$query_params[0][ $relation->get_this_model()->get_this_model_name()
473
+							  . '.'
474
+							  . $where_condition_key ] = $where_condition_value;
475
+		}
476
+		$query_params['default_where_conditions'] = 'none';
477
+		$query_params['caps']                     = $context;
478
+		if (! $request->get_header('no_rest_headers')) {
479
+			$this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480
+		}
481
+		/** @type array $results */
482
+		$results      = $relation->get_other_model()->get_all_wpdb_results($query_params);
483
+		$nice_results = [];
484
+		foreach ($results as $result) {
485
+			$nice_result = $this->createEntityFromWpdbResult(
486
+				$relation->get_other_model(),
487
+				$result,
488
+				$request
489
+			);
490
+			if ($relation instanceof EE_HABTM_Relation) {
491
+				// put the unusual stuff (properties from the HABTM relation) first, and make sure
492
+				// if there are conflicts we prefer the properties from the main model
493
+				$join_model_result = $this->createEntityFromWpdbResult(
494
+					$relation->get_join_model(),
495
+					$result,
496
+					$request
497
+				);
498
+				$joined_result     = array_merge($join_model_result, $nice_result);
499
+				// but keep the meta stuff from the main model
500
+				if (isset($nice_result['meta'])) {
501
+					$joined_result['meta'] = $nice_result['meta'];
502
+				}
503
+				$nice_result = $joined_result;
504
+			}
505
+			$nice_results[] = $nice_result;
506
+		}
507
+		if ($relation instanceof EE_Belongs_To_Relation) {
508
+			return array_shift($nice_results);
509
+		} else {
510
+			return $nice_results;
511
+		}
512
+	}
513
+
514
+
515
+	/**
516
+	 * Gets the collection for given relation object
517
+	 * The same as Read::get_entities_from_model(), except if the relation
518
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
519
+	 * the join-model-object into the results
520
+	 *
521
+	 * @param string                 $id the ID of the thing we are fetching related stuff from
522
+	 * @param EE_Model_Relation_Base $relation
523
+	 * @param WP_REST_Request        $request
524
+	 * @return array
525
+	 * @throws EE_Error
526
+	 * @throws ReflectionException
527
+	 */
528
+	public function getEntitiesFromRelation(
529
+		string $id,
530
+		EE_Model_Relation_Base $relation,
531
+		WP_REST_Request $request
532
+	): array {
533
+		if (! $relation->get_this_model()->has_primary_key_field()) {
534
+			throw new EE_Error(
535
+				sprintf(
536
+					esc_html__(
537
+					// @codingStandardsIgnoreStart
538
+						'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
539
+						// @codingStandardsIgnoreEnd
540
+						'event_espresso'
541
+					),
542
+					$relation->get_this_model()->get_this_model_name()
543
+				)
544
+			);
545
+		}
546
+		// can we edit that main item?
547
+		// if not, show nothing but an error
548
+		// otherwise, please proceed
549
+		return $this->getEntitiesFromRelationUsingModelQueryParams(
550
+			[
551
+				[
552
+					$relation->get_this_model()->primary_key_name() => $id,
553
+				],
554
+			],
555
+			$relation,
556
+			$request
557
+		);
558
+	}
559
+
560
+
561
+	/**
562
+	 * Sets the headers that are based on the model and query params,
563
+	 * like the total records. This should only be called on the original request
564
+	 * from the client, not on subsequent internal
565
+	 *
566
+	 * @param EEM_Base $model
567
+	 * @param array    $query_params
568
+	 * @return void
569
+	 * @throws EE_Error
570
+	 * @throws EE_Error
571
+	 */
572
+	protected function setHeadersFromQueryParams(EEM_Base $model, array $query_params)
573
+	{
574
+		$this->setDebugInfo('model query params', $query_params);
575
+		$this->setDebugInfo(
576
+			'missing caps',
577
+			Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578
+		);
579
+		// normally the limit to a 2-part array, where the 2nd item is the limit
580
+		if (! isset($query_params['limit'])) {
581
+			$query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582
+		}
583
+		if (is_array($query_params['limit'])) {
584
+			$limit_parts = $query_params['limit'];
585
+		} else {
586
+			$limit_parts = explode(',', $query_params['limit']);
587
+			if (count($limit_parts) == 1) {
588
+				$limit_parts = [0, $limit_parts[0]];
589
+			}
590
+		}
591
+		// remove the group by and having parts of the query, as those will
592
+		// make the sql query return an array of values, instead of just a single value
593
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
594
+		$count = $model->count($query_params, null, true);
595
+		$pages = $count / $limit_parts[1];
596
+		$this->setResponseHeader('Total', $count, false);
597
+		$this->setResponseHeader('PageSize', $limit_parts[1], false);
598
+		$this->setResponseHeader('TotalPages', ceil($pages), false);
599
+	}
600
+
601
+
602
+	/**
603
+	 * Changes database results into REST API entities
604
+	 *
605
+	 * @param EEM_Base             $model
606
+	 * @param array                $db_row     like results from $wpdb->get_results()
607
+	 * @param WP_REST_Request|null $rest_request
608
+	 * @param string|null          $deprecated no longer used
609
+	 * @return array ready for being converted into json for sending to client
610
+	 * @throws EE_Error
611
+	 * @throws ReflectionException
612
+	 * @throws RestException
613
+	 * @throws RestPasswordIncorrectException
614
+	 * @throws RestPasswordRequiredException
615
+	 */
616
+	public function createEntityFromWpdbResult(
617
+		EEM_Base $model,
618
+		array $db_row,
619
+		?WP_REST_Request $rest_request,
620
+		string $deprecated = null
621
+	): array {
622
+		if (! $rest_request instanceof WP_REST_Request) {
623
+			// ok so this was called in the old style, where the 3rd arg was
624
+			// $include, and the 4th arg was $context
625
+			// now setup the request just to avoid fatal errors, although we won't be able
626
+			// to truly make use of it because it's kinda devoid of info
627
+			$rest_request = new WP_REST_Request();
628
+			$rest_request->set_param('include', $rest_request);
629
+			$rest_request->set_param('caps', $deprecated);
630
+		}
631
+		if ($rest_request->get_param('caps') == null) {
632
+			$rest_request->set_param('caps', EEM_Base::caps_read);
633
+		}
634
+		$current_user_full_access_to_entity = $model->currentUserCan(
635
+			EEM_Base::caps_read_admin,
636
+			$model->deduce_fields_n_values_from_cols_n_values($db_row)
637
+		);
638
+		$entity_array                       = $this->createBareEntityFromWpdbResults($model, $db_row);
639
+		$entity_array                       = $this->addExtraFields($model, $db_row, $entity_array);
640
+		$entity_array['_links']             = $this->getEntityLinks($model, $db_row, $entity_array);
641
+		// when it's a regular read request for a model with a password and the password wasn't provided
642
+		// remove the password protected fields
643
+		$has_protected_fields = false;
644
+		try {
645
+			$this->checkPassword(
646
+				$model,
647
+				$db_row,
648
+				$model->alter_query_params_to_restrict_by_ID(
649
+					$model->get_index_primary_key_string(
650
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
651
+					)
652
+				),
653
+				$rest_request
654
+			);
655
+		} catch (RestPasswordRequiredException $e) {
656
+			if ($model->hasPassword()) {
657
+				// just remove protected fields
658
+				$has_protected_fields = true;
659
+				$entity_array         = Capabilities::filterOutPasswordProtectedFields(
660
+					$entity_array,
661
+					$model,
662
+					$this->getModelVersionInfo()
663
+				);
664
+			} else {
665
+				// that's a problem. None of this should be accessible if no password was provided
666
+				throw $e;
667
+			}
668
+		}
669
+
670
+		$entity_array['_calculated_fields'] =
671
+			$this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
672
+		$entity_array                       = apply_filters(
673
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
674
+			$entity_array,
675
+			$model,
676
+			$rest_request->get_param('caps'),
677
+			$rest_request,
678
+			$this
679
+		);
680
+		// add an empty protected property for now. If it's still around after we remove everything the request didn't
681
+		// want, we'll populate it then. k?
682
+		$entity_array['_protected'] = [];
683
+		// remove any properties the request didn't want. This way _protected won't bother mentioning them
684
+		$entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
685
+		$entity_array =
686
+			$this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
+		// if they still wanted the _protected property, add it.
688
+		if (isset($entity_array['_protected'])) {
689
+			$entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
+		}
691
+		$entity_array = apply_filters(
692
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
+			$entity_array,
694
+			$model,
695
+			$rest_request->get_param('caps'),
696
+			$rest_request,
697
+			$this
698
+		);
699
+		if (! $current_user_full_access_to_entity) {
700
+			$result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
+				$entity_array,
702
+				$model,
703
+				$rest_request->get_param('caps'),
704
+				$this->getModelVersionInfo()
705
+			);
706
+		} else {
707
+			$result_without_inaccessible_fields = $entity_array;
708
+		}
709
+		$this->setDebugInfo(
710
+			'inaccessible fields',
711
+			array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
+		);
713
+		return apply_filters(
714
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
+			$result_without_inaccessible_fields,
716
+			$model,
717
+			$rest_request->get_param('caps')
718
+		);
719
+	}
720
+
721
+
722
+	/**
723
+	 * Returns an array describing which fields can be protected, and which actually were removed this request
724
+	 *
725
+	 * @param EEM_Base $model
726
+	 * @param array    $results_so_far
727
+	 * @param bool     $protected
728
+	 * @return array results
729
+	 * @throws EE_Error
730
+	 * @since 4.9.74.p
731
+	 */
732
+	protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733
+	{
734
+		if (! $model->hasPassword() || ! $protected) {
735
+			return $results_so_far;
736
+		}
737
+		$password_field  = $model->getPasswordField();
738
+		$all_protected   = array_merge(
739
+			[$password_field->get_name()],
740
+			$password_field->protectedFields()
741
+		);
742
+		$fields_included = array_keys($results_so_far);
743
+		$fields_included = array_intersect(
744
+			$all_protected,
745
+			$fields_included
746
+		);
747
+		foreach ($fields_included as $field_name) {
748
+			$results_so_far['_protected'][] = $field_name;
749
+		}
750
+		return $results_so_far;
751
+	}
752
+
753
+
754
+	/**
755
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
756
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
757
+	 * from $wpdb->get_row( $sql, ARRAY_A)
758
+	 *
759
+	 * @param EEM_Base $model
760
+	 * @param array    $db_row
761
+	 * @return array entity mostly ready for converting to JSON and sending in the response
762
+	 * @throws EE_Error
763
+	 * @throws ReflectionException
764
+	 * @throws RestException
765
+	 */
766
+	protected function createBareEntityFromWpdbResults(EEM_Base $model, array $db_row): array
767
+	{
768
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
769
+		$result = array_intersect_key(
770
+			$result,
771
+			$this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
772
+		);
773
+		// if this is a CPT, we need to set the global $post to it,
774
+		// otherwise shortcodes etc won't work properly while rendering it
775
+		if ($model instanceof EEM_CPT_Base) {
776
+			$do_chevy_shuffle = true;
777
+		} else {
778
+			$do_chevy_shuffle = false;
779
+		}
780
+		if ($do_chevy_shuffle) {
781
+			global $post;
782
+			$old_post = $post;
783
+			$post     = get_post($result[ $model->primary_key_name() ]);
784
+			if (! $post instanceof WP_Post) {
785
+				// well that's weird, because $result is what we JUST fetched from the database
786
+				throw new RestException(
787
+					'error_fetching_post_from_database_results',
788
+					esc_html__(
789
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
790
+						'event_espresso'
791
+					)
792
+				);
793
+			}
794
+			$model_object_classname          = 'EE_' . $model->get_this_model_name();
795
+			$post->{$model_object_classname} = EE_Registry::instance()->load_class(
796
+				$model_object_classname,
797
+				$result,
798
+				false,
799
+				false
800
+			);
801
+		}
802
+		foreach ($result as $field_name => $field_value) {
803
+			$field_obj = $model->field_settings_for($field_name);
804
+			if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
+				unset($result[ $field_name ]);
806
+			} elseif (
807
+				$this->isSubclassOfOne(
808
+					$field_obj,
809
+					$this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810
+				)
811
+			) {
812
+				$result[ $field_name ] = [
813
+					'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814
+					'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815
+				];
816
+			} elseif (
817
+				$this->isSubclassOfOne(
818
+					$field_obj,
819
+					$this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820
+				)
821
+			) {
822
+				$result[ $field_name ] = [
823
+					'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824
+					'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825
+				];
826
+			} elseif ($field_obj instanceof EE_Datetime_Field) {
827
+				$field_value = $field_obj->prepare_for_set_from_db($field_value);
828
+				// if the value is null, but we're not supposed to permit null, then set to the field's default
829
+				if (is_null($field_value)) {
830
+					$field_value = $field_obj->getDefaultDateTimeObj();
831
+				}
832
+				if (is_null($field_value)) {
833
+					$gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
834
+						$field_obj,
835
+						$field_value,
836
+						$this->getModelVersionInfo()->requestedVersion()
837
+					);
838
+				} else {
839
+					$timezone = $field_value->getTimezone();
840
+					EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
841
+					$gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
842
+						$field_obj,
843
+						$field_value,
844
+						$this->getModelVersionInfo()->requestedVersion()
845
+					);
846
+					EEH_DTT_Helper::setTimezone($field_value, $timezone);
847
+					$local_date = ModelDataTranslator::prepareFieldValuesForJson(
848
+						$field_obj,
849
+						$field_value,
850
+						$this->getModelVersionInfo()->requestedVersion()
851
+					);
852
+				}
853
+				$result[ $field_name . '_gmt' ] = $gmt_date;
854
+				$result[ $field_name ]          = $local_date;
855
+			} else {
856
+				$result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857
+			}
858
+		}
859
+		if ($do_chevy_shuffle) {
860
+			$post = $old_post;
861
+		}
862
+		return $result;
863
+	}
864
+
865
+
866
+	/**
867
+	 * Takes a value all the way from the DB representation, to the model object's representation, to the
868
+	 * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
869
+	 * representation using $field_obj->prepare_for_set_from_db())
870
+	 *
871
+	 * @param EE_Model_Field_Base $field_obj
872
+	 * @param mixed               $value  as it's stored on a model object
873
+	 * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
874
+	 * @return mixed
875
+	 * @throws RestException if $value contains a PHP object
876
+	 * @throws EE_Error
877
+	 */
878
+	protected function prepareFieldObjValueForJson(
879
+		EE_Model_Field_Base $field_obj,
880
+		$value,
881
+		string $format = 'normal'
882
+	) {
883
+		$value = $field_obj->prepare_for_set_from_db($value);
884
+		switch ($format) {
885
+			case 'pretty':
886
+				$value = $field_obj->prepare_for_pretty_echoing($value);
887
+				break;
888
+			case 'normal':
889
+			default:
890
+				$value = $field_obj->prepare_for_get($value);
891
+				break;
892
+		}
893
+		return ModelDataTranslator::prepareFieldValuesForJson(
894
+			$field_obj,
895
+			$value,
896
+			$this->getModelVersionInfo()->requestedVersion()
897
+		);
898
+	}
899
+
900
+
901
+	/**
902
+	 * Adds a few extra fields to the entity response
903
+	 *
904
+	 * @param EEM_Base $model
905
+	 * @param array    $db_row
906
+	 * @param array    $entity_array
907
+	 * @return array modified entity
908
+	 * @throws EE_Error
909
+	 * @throws EE_Error
910
+	 */
911
+	protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912
+	{
913
+		if ($model instanceof EEM_CPT_Base) {
914
+			$entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
915
+		}
916
+		return $entity_array;
917
+	}
918
+
919
+
920
+	/**
921
+	 * Gets links we want to add to the response
922
+	 *
923
+	 * @param EEM_Base        $model
924
+	 * @param array           $db_row
925
+	 * @param array           $entity_array
926
+	 * @return array the _links item in the entity
927
+	 * @throws EE_Error
928
+	 * @throws EE_Error
929
+	 * @global WP_REST_Server $wp_rest_server
930
+	 */
931
+	protected function getEntityLinks(EEM_Base $model, array $db_row, array $entity_array): array
932
+	{
933
+		// add basic links
934
+		$links = [];
935
+		if ($model->has_primary_key_field()) {
936
+			$links['self'] = [
937
+				[
938
+					'href' => $this->getVersionedLinkTo(
939
+						EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940
+						. '/'
941
+						. $entity_array[ $model->primary_key_name() ]
942
+					),
943
+				],
944
+			];
945
+		}
946
+		$links['collection'] = [
947
+			[
948
+				'href' => $this->getVersionedLinkTo(
949
+					EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
950
+				),
951
+			],
952
+		];
953
+		// add links to related models
954
+		if ($model->has_primary_key_field()) {
955
+			foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956
+				$related_model_part                                                      =
957
+					Read::getRelatedEntityName($relation_name, $relation_obj);
958
+				$links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
959
+					[
960
+						'href'   => $this->getVersionedLinkTo(
961
+							EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962
+							. '/'
963
+							. $entity_array[ $model->primary_key_name() ]
964
+							. '/'
965
+							. $related_model_part
966
+						),
967
+						'single' => $relation_obj instanceof EE_Belongs_To_Relation,
968
+					],
969
+				];
970
+			}
971
+		}
972
+		return $links;
973
+	}
974
+
975
+
976
+	/**
977
+	 * Adds the included models indicated in the request to the entity provided
978
+	 *
979
+	 * @param EEM_Base        $model
980
+	 * @param WP_REST_Request $rest_request
981
+	 * @param array           $entity_array
982
+	 * @param array           $db_row
983
+	 * @param boolean         $included_items_protected if the original item is password protected, don't include any
984
+	 *                                                  related models.
985
+	 * @return array the modified entity
986
+	 * @throws EE_Error
987
+	 * @throws ReflectionException
988
+	 */
989
+	protected function includeRequestedModels(
990
+		EEM_Base $model,
991
+		WP_REST_Request $rest_request,
992
+		array $entity_array,
993
+		array $db_row = [],
994
+		bool $included_items_protected = false
995
+	): array {
996
+		// if $db_row not included, hope the entity array has what we need
997
+		if (! $db_row) {
998
+			$db_row = $entity_array;
999
+		}
1000
+		$relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001
+		foreach ($relation_settings as $relation_name => $relation_obj) {
1002
+			$related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1003
+				$rest_request->get_param('include'),
1004
+				$relation_name
1005
+			);
1006
+			$related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
1007
+				$rest_request->get_param('calculate'),
1008
+				$relation_name
1009
+			);
1010
+			// did they specify they wanted to include a related model, or
1011
+			// specific fields from a related model?
1012
+			// or did they specify to calculate a field from a related model?
1013
+			if ($related_fields_to_include || $related_fields_to_calculate) {
1014
+				// if so, we should include at least some part of the related model
1015
+				$pretend_related_request = new WP_REST_Request();
1016
+				$pretend_related_request->set_query_params(
1017
+					[
1018
+						'caps'      => $rest_request->get_param('caps'),
1019
+						'include'   => $related_fields_to_include,
1020
+						'calculate' => $related_fields_to_calculate,
1021
+						'password'  => $rest_request->get_param('password'),
1022
+					]
1023
+				);
1024
+				$pretend_related_request->add_header('no_rest_headers', true);
1025
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1026
+					$model->get_index_primary_key_string(
1027
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
1028
+					)
1029
+				);
1030
+				if (! $included_items_protected) {
1031
+					try {
1032
+						$related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033
+							$primary_model_query_params,
1034
+							$relation_obj,
1035
+							$pretend_related_request
1036
+						);
1037
+					} catch (RestException $e) {
1038
+						$related_results = new WP_Error('entity_relations_error', $e->getMessage());
1039
+					}
1040
+				} else {
1041
+					// they're protected, hide them.
1042
+					$related_results              = null;
1043
+					$entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1044
+				}
1045
+				if ($related_results instanceof WP_Error) {
1046
+					$related_results = $relation_obj instanceof EE_Belongs_To_Relation
1047
+						? null
1048
+						: [];
1049
+				}
1050
+				$entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1051
+			}
1052
+		}
1053
+		return $entity_array;
1054
+	}
1055
+
1056
+
1057
+	/**
1058
+	 * If the user has requested only specific properties (including meta properties like _links or _protected)
1059
+	 * remove everything else.
1060
+	 *
1061
+	 * @param EEM_Base        $model
1062
+	 * @param WP_REST_Request $rest_request
1063
+	 * @param                 $entity_array
1064
+	 * @return array
1065
+	 * @throws EE_Error
1066
+	 * @since 4.9.74.p
1067
+	 */
1068
+	protected function includeOnlyRequestedProperties(
1069
+		EEM_Base $model,
1070
+		WP_REST_Request $rest_request,
1071
+		$entity_array
1072
+	): array {
1073
+		$includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1074
+		$includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1075
+		// if they passed in * or didn't specify any includes, return everything
1076
+		if (
1077
+			! in_array('*', $includes_for_this_model)
1078
+			&& ! empty($includes_for_this_model)
1079
+		) {
1080
+			if ($model->has_primary_key_field()) {
1081
+				// always include the primary key. ya just gotta know that at least
1082
+				$includes_for_this_model[] = $model->primary_key_name();
1083
+			}
1084
+			if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1085
+				$includes_for_this_model[] = '_calculated_fields';
1086
+			}
1087
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1088
+		}
1089
+		return $entity_array;
1090
+	}
1091
+
1092
+
1093
+	/**
1094
+	 * Returns a new array with all the names of models removed. Eg
1095
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1096
+	 *
1097
+	 * @param array $model_names
1098
+	 * @return array
1099
+	 */
1100
+	private function removeModelNamesFromArray(array $model_names): array
1101
+	{
1102
+		return array_diff($model_names, array_keys(EE_Registry::instance()->non_abstract_db_models));
1103
+	}
1104
+
1105
+
1106
+	/**
1107
+	 * Gets the calculated fields for the response
1108
+	 *
1109
+	 * @param EEM_Base        $model
1110
+	 * @param array           $wpdb_row
1111
+	 * @param WP_REST_Request $rest_request
1112
+	 * @param boolean         $row_is_protected whether this row is password protected or not
1113
+	 * @return stdClass the _calculations item in the entity
1114
+	 * @throws RestException if a default value has a PHP object, which should never do (and if we
1115
+	 * @throws EE_Error
1116
+	 *                                          did, let's know about it ASAP, so let the exception bubble up)
1117
+	 */
1118
+	protected function getEntityCalculations(
1119
+		EEM_Base $model,
1120
+		array $wpdb_row,
1121
+		WP_REST_Request $rest_request,
1122
+		bool $row_is_protected = false
1123
+	): stdClass {
1124
+		$calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1125
+			$rest_request->get_param('calculate'),
1126
+			''
1127
+		);
1128
+		// note: setting calculate=* doesn't do anything
1129
+		$calculated_fields_to_return = new stdClass();
1130
+		$protected_fields            = [];
1131
+		foreach ($calculated_fields as $field_to_calculate) {
1132
+			try {
1133
+				// it's password protected, so they shouldn't be able to read this. Remove the value
1134
+				$schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135
+				if (
1136
+					$row_is_protected
1137
+					&& isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
+					&& $schema['properties'][ $field_to_calculate ]['protected']
1139
+				) {
1140
+					$calculated_value   = null;
1141
+					$protected_fields[] = $field_to_calculate;
1142
+					if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
+						switch ($schema['properties'][ $field_to_calculate ]['type']) {
1144
+							case 'boolean':
1145
+								$calculated_value = false;
1146
+								break;
1147
+							case 'integer':
1148
+								$calculated_value = 0;
1149
+								break;
1150
+							case 'string':
1151
+								$calculated_value = '';
1152
+								break;
1153
+							case 'array':
1154
+								$calculated_value = [];
1155
+								break;
1156
+							case 'object':
1157
+								$calculated_value = new stdClass();
1158
+								break;
1159
+						}
1160
+					}
1161
+				} else {
1162
+					$calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1163
+						null,
1164
+						$this->fields_calculator->retrieveCalculatedFieldValue(
1165
+							$model,
1166
+							$field_to_calculate,
1167
+							$wpdb_row,
1168
+							$rest_request,
1169
+							$this
1170
+						),
1171
+						$this->getModelVersionInfo()->requestedVersion()
1172
+					);
1173
+				}
1174
+				$calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1175
+			} catch (RestException $e) {
1176
+				// if we don't have permission to read it, just leave it out. but let devs know about the problem
1177
+				$this->setResponseHeader(
1178
+					'Notices-Field-Calculation-Errors['
1179
+					. $e->getStringCode()
1180
+					. ']['
1181
+					. $model->get_this_model_name()
1182
+					. ']['
1183
+					. $field_to_calculate
1184
+					. ']',
1185
+					$e->getMessage()
1186
+				);
1187
+			}
1188
+		}
1189
+		$calculated_fields_to_return->_protected = $protected_fields;
1190
+		return $calculated_fields_to_return;
1191
+	}
1192
+
1193
+
1194
+	/**
1195
+	 * Gets the full URL to the resource, taking the requested version into account
1196
+	 *
1197
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1198
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1199
+	 * @throws EE_Error
1200
+	 * @throws EE_Error
1201
+	 */
1202
+	public function getVersionedLinkTo(string $link_part_after_version_and_slash): string
1203
+	{
1204
+		return rest_url(
1205
+			EED_Core_Rest_Api::get_versioned_route_to(
1206
+				$link_part_after_version_and_slash,
1207
+				$this->getModelVersionInfo()->requestedVersion()
1208
+			)
1209
+		);
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * Gets the correct lowercase name for the relation in the API according
1215
+	 * to the relation's type
1216
+	 *
1217
+	 * @param string                 $relation_name
1218
+	 * @param EE_Model_Relation_Base $relation_obj
1219
+	 * @return string
1220
+	 */
1221
+	public static function getRelatedEntityName(string $relation_name, EE_Model_Relation_Base $relation_obj): string
1222
+	{
1223
+		if ($relation_obj instanceof EE_Belongs_To_Relation) {
1224
+			return strtolower($relation_name);
1225
+		} else {
1226
+			return EEH_Inflector::pluralize_and_lower($relation_name);
1227
+		}
1228
+	}
1229
+
1230
+
1231
+	/**
1232
+	 * Gets the one model object with the specified id for the specified model
1233
+	 *
1234
+	 * @param EEM_Base        $model
1235
+	 * @param WP_REST_Request $request
1236
+	 * @return array
1237
+	 * @throws EE_Error
1238
+	 * @throws EE_Error
1239
+	 * @throws ReflectionException
1240
+	 */
1241
+	public function getEntityFromModel(EEM_Base $model, WP_REST_Request $request): array
1242
+	{
1243
+		$context = $this->validateContext($request->get_param('caps'));
1244
+		return $this->getOneOrReportPermissionError($model, $request, $context);
1245
+	}
1246
+
1247
+
1248
+	/**
1249
+	 * If a context is provided which isn't valid, maybe it was added in a future
1250
+	 * version so just treat it as a default read
1251
+	 *
1252
+	 * @param string $context
1253
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1254
+	 */
1255
+	public function validateContext(string $context): string
1256
+	{
1257
+		if (! $context) {
1258
+			$context = EEM_Base::caps_read;
1259
+		}
1260
+		$valid_contexts = EEM_Base::valid_cap_contexts();
1261
+		if (in_array($context, $valid_contexts)) {
1262
+			return $context;
1263
+		} else {
1264
+			return EEM_Base::caps_read;
1265
+		}
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * Verifies the passed in value is an allowable default where conditions value.
1271
+	 *
1272
+	 * @param $default_query_params
1273
+	 * @return string
1274
+	 */
1275
+	public function validateDefaultQueryParams($default_query_params): string
1276
+	{
1277
+		$valid_default_where_conditions_for_api_calls = [
1278
+			EEM_Base::default_where_conditions_all,
1279
+			EEM_Base::default_where_conditions_minimum_all,
1280
+			EEM_Base::default_where_conditions_minimum_others,
1281
+		];
1282
+		if (! $default_query_params) {
1283
+			$default_query_params = EEM_Base::default_where_conditions_all;
1284
+		}
1285
+		if (
1286
+			in_array(
1287
+				$default_query_params,
1288
+				$valid_default_where_conditions_for_api_calls,
1289
+				true
1290
+			)
1291
+		) {
1292
+			return $default_query_params;
1293
+		}
1294
+		return EEM_Base::default_where_conditions_all;
1295
+	}
1296
+
1297
+
1298
+	/**
1299
+	 * Translates API filter get parameter into model query params @param EEM_Base $model
1300
+	 *
1301
+	 * @param array $query_params
1302
+	 * @return array model query params (@see
1303
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1304
+	 *               or FALSE to indicate that absolutely no results should be returned
1305
+	 * @throws EE_Error
1306
+	 * @throws RestException
1307
+	 * @see
1308
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1309
+	 *               Note: right now the query parameter keys for fields (and related fields) can be left as-is, but
1310
+	 *               it's quite possible this will change someday. Also, this method's contents might be candidate for
1311
+	 *               moving to Model_Data_Translator
1312
+	 *
1313
+	 */
1314
+	public function createModelQueryParams(EEM_Base $model, array $query_params): array
1315
+	{
1316
+		$model_query_params = [];
1317
+		if (isset($query_params['where'])) {
1318
+			$model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1319
+				$query_params['where'],
1320
+				$model,
1321
+				$this->getModelVersionInfo()->requestedVersion()
1322
+			);
1323
+		}
1324
+		if (isset($query_params['order_by'])) {
1325
+			$order_by = $query_params['order_by'];
1326
+		} elseif (isset($query_params['orderby'])) {
1327
+			$order_by = $query_params['orderby'];
1328
+		} else {
1329
+			$order_by = null;
1330
+		}
1331
+		if ($order_by !== null) {
1332
+			if (is_array($order_by)) {
1333
+				$order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1334
+			} else {
1335
+				// it's a single item
1336
+				$order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1337
+			}
1338
+			$model_query_params['order_by'] = $order_by;
1339
+		}
1340
+		if (isset($query_params['group_by'])) {
1341
+			$group_by = $query_params['group_by'];
1342
+		} elseif (isset($query_params['groupby'])) {
1343
+			$group_by = $query_params['groupby'];
1344
+		} else {
1345
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1346
+		}
1347
+		// make sure they're all real names
1348
+		if (is_array($group_by)) {
1349
+			$group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1350
+		}
1351
+		if ($group_by !== null) {
1352
+			$model_query_params['group_by'] = $group_by;
1353
+		}
1354
+		if (isset($query_params['having'])) {
1355
+			$model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1356
+				$query_params['having'],
1357
+				$model,
1358
+				$this->getModelVersionInfo()->requestedVersion()
1359
+			);
1360
+		}
1361
+		if (isset($query_params['order'])) {
1362
+			$model_query_params['order'] = $query_params['order'];
1363
+		}
1364
+		if (isset($query_params['mine'])) {
1365
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1366
+		}
1367
+		if (isset($query_params['limit'])) {
1368
+			// limit should be either a string like '23' or '23,43', or an array with two items in it
1369
+			if (! is_array($query_params['limit'])) {
1370
+				$limit_array = explode(',', (string) $query_params['limit']);
1371
+			} else {
1372
+				$limit_array = $query_params['limit'];
1373
+			}
1374
+			$sanitized_limit = [];
1375
+			foreach ($limit_array as $limit_part) {
1376
+				if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377
+					throw new EE_Error(
1378
+						sprintf(
1379
+							esc_html__(
1380
+							// @codingStandardsIgnoreStart
1381
+								'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1382
+								// @codingStandardsIgnoreEnd
1383
+								'event_espresso'
1384
+							),
1385
+							wp_json_encode($query_params['limit'])
1386
+						)
1387
+					);
1388
+				}
1389
+				$sanitized_limit[] = (int) $limit_part;
1390
+			}
1391
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1392
+		} else {
1393
+			$model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1394
+		}
1395
+		if (isset($query_params['caps'])) {
1396
+			$model_query_params['caps'] = $this->validateContext($query_params['caps']);
1397
+		} else {
1398
+			$model_query_params['caps'] = EEM_Base::caps_read;
1399
+		}
1400
+		if (isset($query_params['default_where_conditions'])) {
1401
+			$model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1402
+				$query_params['default_where_conditions']
1403
+			);
1404
+		}
1405
+		// if this is a model protected by a password on another model, exclude the password protected
1406
+		// entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1407
+		// though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1408
+		if (
1409
+			! $model->hasPassword()
1410
+			&& $model->restrictedByRelatedModelPassword()
1411
+			&& $model_query_params['caps'] === EEM_Base::caps_read
1412
+		) {
1413
+			if (empty($query_params['password'])) {
1414
+				$model_query_params['exclude_protected'] = true;
1415
+			}
1416
+		}
1417
+
1418
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1419
+	}
1420
+
1421
+
1422
+	/**
1423
+	 * Changes the REST-style query params for use in the models
1424
+	 *
1425
+	 * @param EEM_Base $model
1426
+	 * @param array    $query_params sub-array from @see EEM_Base::get_all()
1427
+	 * @return array
1428
+	 * @deprecated
1429
+	 */
1430
+	public function prepareRestQueryParamsKeyForModels(EEM_Base $model, array $query_params): array
1431
+	{
1432
+		$model_ready_query_params = [];
1433
+		foreach ($query_params as $key => $value) {
1434
+			$model_ready_query_params[ $key ] = is_array($value)
1435
+				? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436
+				: $value;
1437
+		}
1438
+		return $model_ready_query_params;
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * @param EEM_Base $model
1444
+	 * @param array    $query_params
1445
+	 * @return array
1446
+	 * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1447
+	 */
1448
+	public function prepareRestQueryParamsValuesForModels(EEM_Base $model, array $query_params): array
1449
+	{
1450
+		$model_ready_query_params = [];
1451
+		foreach ($query_params as $key => $value) {
1452
+			if (is_array($value)) {
1453
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454
+			} else {
1455
+				$model_ready_query_params[ $key ] = $value;
1456
+			}
1457
+		}
1458
+		return $model_ready_query_params;
1459
+	}
1460
+
1461
+
1462
+	/**
1463
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1464
+	 * If no prefix is specified, returns items with no period.
1465
+	 *
1466
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1467
+	 * @param string       $prefix            "Event" or "foobar"
1468
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1469
+	 *                                        we only return strings starting with that and a period; if no prefix was
1470
+	 *                                        specified we return all items containing NO periods
1471
+	 */
1472
+	public function explodeAndGetItemsPrefixedWith($string_to_explode, string $prefix): array
1473
+	{
1474
+		if (is_string($string_to_explode)) {
1475
+			$exploded_contents = explode(',', $string_to_explode);
1476
+		} elseif (is_array($string_to_explode)) {
1477
+			$exploded_contents = $string_to_explode;
1478
+		} else {
1479
+			$exploded_contents = [];
1480
+		}
1481
+		// if the string was empty, we want an empty array
1482
+		$exploded_contents    = array_filter($exploded_contents);
1483
+		$contents_with_prefix = [];
1484
+		foreach ($exploded_contents as $item) {
1485
+			$item = trim($item);
1486
+			// if no prefix was provided, so we look for items with no "." in them
1487
+			if (! $prefix) {
1488
+				// does this item have a period?
1489
+				if (strpos($item, '.') === false) {
1490
+					// if not, then its what we're looking for
1491
+					$contents_with_prefix[] = $item;
1492
+				}
1493
+			} elseif (strpos($item, $prefix . '.') === 0) {
1494
+				// this item has the prefix and a period, grab it
1495
+				$contents_with_prefix[] = substr(
1496
+					$item,
1497
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1498
+				);
1499
+			} elseif ($item === $prefix) {
1500
+				// this item is JUST the prefix
1501
+				// so let's grab everything after, which is a blank string
1502
+				$contents_with_prefix[] = '';
1503
+			}
1504
+		}
1505
+		return $contents_with_prefix;
1506
+	}
1507
+
1508
+
1509
+	/**
1510
+	 * @param array|string $include_string @see Read:handle_request_get_all
1511
+	 * @param string|null  $model_name
1512
+	 * @return array of fields for this model. If $model_name is provided, then
1513
+	 *                                     the fields for that model, with the model's name removed from each.
1514
+	 *                                     If $include_string was blank or '*' returns an empty array
1515
+	 * @throws EE_Error
1516
+	 * @throws EE_Error
1517
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1518
+	 *                                     Deprecated because its return values were really quite confusing- sometimes
1519
+	 *                                     it returned an empty array (when the include string was blank or '*') or
1520
+	 *                                     sometimes it returned array('*') (when you provided a model and a model of
1521
+	 *                                     that kind was found). Parses the $include_string so we fetch all the field
1522
+	 *                                     names relating to THIS model
1523
+	 *                                     (ie have NO period in them), or for the provided model (ie start with the
1524
+	 *                                     model name and then a period).
1525
+	 */
1526
+	public function extractIncludesForThisModel($include_string, string $model_name = ''): array
1527
+	{
1528
+		if (is_array($include_string)) {
1529
+			$include_string = implode(',', $include_string);
1530
+		}
1531
+		if ($include_string === '*' || $include_string === '') {
1532
+			return [];
1533
+		}
1534
+		$includes                    = explode(',', $include_string);
1535
+		$extracted_fields_to_include = [];
1536
+		if ($model_name) {
1537
+			foreach ($includes as $field_to_include) {
1538
+				$field_to_include = trim($field_to_include);
1539
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1540
+					// found the model name at the exact start
1541
+					$field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1542
+					$extracted_fields_to_include[] = $field_sans_model_name;
1543
+				} elseif ($field_to_include == $model_name) {
1544
+					$extracted_fields_to_include[] = '*';
1545
+				}
1546
+			}
1547
+		} else {
1548
+			// look for ones with no period
1549
+			foreach ($includes as $field_to_include) {
1550
+				$field_to_include = trim($field_to_include);
1551
+				if (
1552
+					strpos($field_to_include, '.') === false
1553
+					&& ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1554
+				) {
1555
+					$extracted_fields_to_include[] = $field_to_include;
1556
+				}
1557
+			}
1558
+		}
1559
+		return $extracted_fields_to_include;
1560
+	}
1561
+
1562
+
1563
+	/**
1564
+	 * Gets the single item using the model according to the request in the context given, otherwise
1565
+	 * returns that it's inaccessible to the current user
1566
+	 *
1567
+	 * @param EEM_Base        $model
1568
+	 * @param WP_REST_Request $request
1569
+	 * @param null            $context
1570
+	 * @return array
1571
+	 * @throws EE_Error
1572
+	 * @throws ReflectionException
1573
+	 */
1574
+	public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null): array
1575
+	{
1576
+		$query_params = [[$model->primary_key_name() => $request->get_param('id')], 'limit' => 1];
1577
+		if ($model instanceof EEM_Soft_Delete_Base) {
1578
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1579
+		}
1580
+		$restricted_query_params         = $query_params;
1581
+		$restricted_query_params['caps'] = $context;
1582
+		$this->setDebugInfo('model query params', $restricted_query_params);
1583
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
+		if (! empty($model_rows)) {
1585
+			return $this->createEntityFromWpdbResult(
1586
+				$model,
1587
+				reset($model_rows),
1588
+				$request
1589
+			);
1590
+		} else {
1591
+			// ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1592
+			$lowercase_model_name = strtolower($model->get_this_model_name());
1593
+			if ($model->exists($query_params)) {
1594
+				// you got shafted- it existed but we didn't want to tell you!
1595
+				throw new RestException(
1596
+					'rest_user_cannot_' . $context,
1597
+					sprintf(
1598
+						esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599
+						$context,
1600
+						$lowercase_model_name,
1601
+						Capabilities::getMissingPermissionsString(
1602
+							$model,
1603
+							$context
1604
+						)
1605
+					),
1606
+					['status' => 403]
1607
+				);
1608
+			} else {
1609
+				// it's not you. It just doesn't exist
1610
+				throw new RestException(
1611
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
1612
+					sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1613
+					['status' => 404]
1614
+				);
1615
+			}
1616
+		}
1617
+	}
1618
+
1619
+
1620
+	/**
1621
+	 * Checks that if this content requires a password to be read, that it's been provided and is correct.
1622
+	 *
1623
+	 * @param EEM_Base        $model
1624
+	 * @param array           $model_row
1625
+	 * @param array           $query_params Adds 'default_where_conditions' => 'minimum'
1626
+	 *                                      to ensure we don't confuse trashed with password protected.
1627
+	 * @param WP_REST_Request $request
1628
+	 * @throws EE_Error
1629
+	 * @throws InvalidArgumentException
1630
+	 * @throws InvalidDataTypeException
1631
+	 * @throws InvalidInterfaceException
1632
+	 * @throws RestPasswordRequiredException
1633
+	 * @throws RestPasswordIncorrectException
1634
+	 * @throws ModelConfigurationException
1635
+	 * @throws ReflectionException
1636
+	 * @since 4.9.74.p
1637
+	 */
1638
+	protected function checkPassword(EEM_Base $model, array $model_row, array $query_params, WP_REST_Request $request)
1639
+	{
1640
+		$query_params['default_where_conditions'] = 'minimum';
1641
+		// stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1642
+		// or you don't.
1643
+		$request_caps = $request->get_param('caps');
1644
+		if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1645
+			return;
1646
+		}
1647
+		// if this entity requires a password, they better give it and it better be right!
1648
+		if (
1649
+			$model->hasPassword()
1650
+			&& $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1651
+		) {
1652
+			if (empty($request['password'])) {
1653
+				throw new RestPasswordRequiredException();
1654
+			}
1655
+			if (
1656
+				! hash_equals(
1657
+					$model_row[ $model->getPasswordField()->get_qualified_column() ],
1658
+					$request['password']
1659
+				)
1660
+			) {
1661
+				throw new RestPasswordIncorrectException();
1662
+			}
1663
+		} elseif (
1664
+			// wait! maybe this content is password protected
1665
+			$model->restrictedByRelatedModelPassword()
1666
+			&& $request->get_param('caps') === EEM_Base::caps_read
1667
+		) {
1668
+			$password_supplied = $request->get_param('password');
1669
+			if (empty($password_supplied)) {
1670
+				$query_params['exclude_protected'] = true;
1671
+				if (! $model->exists($query_params)) {
1672
+					throw new RestPasswordRequiredException();
1673
+				}
1674
+			} else {
1675
+				$query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
+				if (! $model->exists($query_params)) {
1677
+					throw new RestPasswordIncorrectException();
1678
+				}
1679
+			}
1680
+		}
1681
+	}
1682
+
1683
+
1684
+	private static function endpointParsingError(string $model_name): WP_Error
1685
+	{
1686
+		return new WP_Error(
1687
+			'endpoint_parsing_error',
1688
+			sprintf(
1689
+				esc_html__(
1690
+					'There is no model for endpoint %s. Please contact event espresso support',
1691
+					'event_espresso'
1692
+				),
1693
+				$model_name
1694
+			)
1695
+		);
1696
+	}
1697 1697
 }
Please login to merge, or discard this patch.
Spacing   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -100,7 +100,7 @@  discard block
 block discarded – undo
100 100
     ): WP_REST_Response {
101 101
         $controller = Read::getModelReadController($version);
102 102
         try {
103
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
103
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104 104
                 return $controller->sendResponse(Read::endpointParsingError($model_name));
105 105
             }
106 106
             return $controller->sendResponse(
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
     {
130 130
         $controller = Read::getModelReadController($version);
131 131
         try {
132
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
132
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133 133
                 return [];
134 134
             }
135 135
             // get the model for this version
@@ -193,11 +193,11 @@  discard block
 block discarded – undo
193 193
      */
194 194
     protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195 195
     {
196
-        if (isset($schema['properties'][ $field_name ]['default'])) {
197
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
198
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
196
+        if (isset($schema['properties'][$field_name]['default'])) {
197
+            if (is_array($schema['properties'][$field_name]['default'])) {
198
+                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
199 199
                     if ($default_key === 'raw') {
200
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
200
+                        $schema['properties'][$field_name]['default'][$default_key] =
201 201
                             ModelDataTranslator::prepareFieldValueForJson(
202 202
                                 $field,
203 203
                                 $default_value,
@@ -206,9 +206,9 @@  discard block
 block discarded – undo
206 206
                     }
207 207
                 }
208 208
             } else {
209
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
209
+                $schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210 210
                     $field,
211
-                    $schema['properties'][ $field_name ]['default'],
211
+                    $schema['properties'][$field_name]['default'],
212 212
                     $this->getModelVersionInfo()->requestedVersion()
213 213
                 );
214 214
             }
@@ -230,9 +230,9 @@  discard block
 block discarded – undo
230 230
     protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231 231
     {
232 232
         if ($field instanceof EE_Datetime_Field) {
233
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
233
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
234 234
             // modify the description
235
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
235
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
236 236
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237 237
                 wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238 238
             );
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
     ): WP_REST_Response {
283 283
         $controller = Read::getModelReadController($version);
284 284
         try {
285
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
285
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286 286
                 return $controller->sendResponse(Read::endpointParsingError($model_name));
287 287
             }
288 288
             return $controller->sendResponse(
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
     public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350 350
     {
351 351
         $query_params = $this->createModelQueryParams($model, $request->get_params());
352
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
352
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353 353
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354 354
             throw new RestException(
355 355
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
                 ['status' => 403]
362 362
             );
363 363
         }
364
-        if (! $request->get_header('no_rest_headers')) {
364
+        if ( ! $request->get_header('no_rest_headers')) {
365 365
             $this->setHeadersFromQueryParams($model, $query_params);
366 366
         }
367 367
         /** @type array $results */
@@ -405,7 +405,7 @@  discard block
 block discarded – undo
405 405
         $context       = $this->validateContext($request->get_param('caps'));
406 406
         $model         = $relation->get_this_model();
407 407
         $related_model = $relation->get_other_model();
408
-        if (! isset($primary_model_query_params[0])) {
408
+        if ( ! isset($primary_model_query_params[0])) {
409 409
             $primary_model_query_params[0] = [];
410 410
         }
411 411
         // check if they can access the 1st model object
@@ -469,13 +469,13 @@  discard block
 block discarded – undo
469 469
         );
470 470
         $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471 471
         foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
472
+            $query_params[0][$relation->get_this_model()->get_this_model_name()
473 473
                               . '.'
474
-                              . $where_condition_key ] = $where_condition_value;
474
+                              . $where_condition_key] = $where_condition_value;
475 475
         }
476 476
         $query_params['default_where_conditions'] = 'none';
477 477
         $query_params['caps']                     = $context;
478
-        if (! $request->get_header('no_rest_headers')) {
478
+        if ( ! $request->get_header('no_rest_headers')) {
479 479
             $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480 480
         }
481 481
         /** @type array $results */
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
                     $result,
496 496
                     $request
497 497
                 );
498
-                $joined_result     = array_merge($join_model_result, $nice_result);
498
+                $joined_result = array_merge($join_model_result, $nice_result);
499 499
                 // but keep the meta stuff from the main model
500 500
                 if (isset($nice_result['meta'])) {
501 501
                     $joined_result['meta'] = $nice_result['meta'];
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
         EE_Model_Relation_Base $relation,
531 531
         WP_REST_Request $request
532 532
     ): array {
533
-        if (! $relation->get_this_model()->has_primary_key_field()) {
533
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
534 534
             throw new EE_Error(
535 535
                 sprintf(
536 536
                     esc_html__(
@@ -577,7 +577,7 @@  discard block
 block discarded – undo
577 577
             Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578 578
         );
579 579
         // normally the limit to a 2-part array, where the 2nd item is the limit
580
-        if (! isset($query_params['limit'])) {
580
+        if ( ! isset($query_params['limit'])) {
581 581
             $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582 582
         }
583 583
         if (is_array($query_params['limit'])) {
@@ -619,7 +619,7 @@  discard block
 block discarded – undo
619 619
         ?WP_REST_Request $rest_request,
620 620
         string $deprecated = null
621 621
     ): array {
622
-        if (! $rest_request instanceof WP_REST_Request) {
622
+        if ( ! $rest_request instanceof WP_REST_Request) {
623 623
             // ok so this was called in the old style, where the 3rd arg was
624 624
             // $include, and the 4th arg was $context
625 625
             // now setup the request just to avoid fatal errors, although we won't be able
@@ -696,7 +696,7 @@  discard block
 block discarded – undo
696 696
             $rest_request,
697 697
             $this
698 698
         );
699
-        if (! $current_user_full_access_to_entity) {
699
+        if ( ! $current_user_full_access_to_entity) {
700 700
             $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701 701
                 $entity_array,
702 702
                 $model,
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
      */
732 732
     protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733 733
     {
734
-        if (! $model->hasPassword() || ! $protected) {
734
+        if ( ! $model->hasPassword() || ! $protected) {
735 735
             return $results_so_far;
736 736
         }
737 737
         $password_field  = $model->getPasswordField();
@@ -780,8 +780,8 @@  discard block
 block discarded – undo
780 780
         if ($do_chevy_shuffle) {
781 781
             global $post;
782 782
             $old_post = $post;
783
-            $post     = get_post($result[ $model->primary_key_name() ]);
784
-            if (! $post instanceof WP_Post) {
783
+            $post     = get_post($result[$model->primary_key_name()]);
784
+            if ( ! $post instanceof WP_Post) {
785 785
                 // well that's weird, because $result is what we JUST fetched from the database
786 786
                 throw new RestException(
787 787
                     'error_fetching_post_from_database_results',
@@ -791,7 +791,7 @@  discard block
 block discarded – undo
791 791
                     )
792 792
                 );
793 793
             }
794
-            $model_object_classname          = 'EE_' . $model->get_this_model_name();
794
+            $model_object_classname          = 'EE_'.$model->get_this_model_name();
795 795
             $post->{$model_object_classname} = EE_Registry::instance()->load_class(
796 796
                 $model_object_classname,
797 797
                 $result,
@@ -802,14 +802,14 @@  discard block
 block discarded – undo
802 802
         foreach ($result as $field_name => $field_value) {
803 803
             $field_obj = $model->field_settings_for($field_name);
804 804
             if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
-                unset($result[ $field_name ]);
805
+                unset($result[$field_name]);
806 806
             } elseif (
807 807
                 $this->isSubclassOfOne(
808 808
                     $field_obj,
809 809
                     $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810 810
                 )
811 811
             ) {
812
-                $result[ $field_name ] = [
812
+                $result[$field_name] = [
813 813
                     'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814 814
                     'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815 815
                 ];
@@ -819,7 +819,7 @@  discard block
 block discarded – undo
819 819
                     $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820 820
                 )
821 821
             ) {
822
-                $result[ $field_name ] = [
822
+                $result[$field_name] = [
823 823
                     'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824 824
                     'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825 825
                 ];
@@ -850,10 +850,10 @@  discard block
 block discarded – undo
850 850
                         $this->getModelVersionInfo()->requestedVersion()
851 851
                     );
852 852
                 }
853
-                $result[ $field_name . '_gmt' ] = $gmt_date;
854
-                $result[ $field_name ]          = $local_date;
853
+                $result[$field_name.'_gmt'] = $gmt_date;
854
+                $result[$field_name]          = $local_date;
855 855
             } else {
856
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
856
+                $result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857 857
             }
858 858
         }
859 859
         if ($do_chevy_shuffle) {
@@ -911,7 +911,7 @@  discard block
 block discarded – undo
911 911
     protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912 912
     {
913 913
         if ($model instanceof EEM_CPT_Base) {
914
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
914
+            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
915 915
         }
916 916
         return $entity_array;
917 917
     }
@@ -938,7 +938,7 @@  discard block
 block discarded – undo
938 938
                     'href' => $this->getVersionedLinkTo(
939 939
                         EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940 940
                         . '/'
941
-                        . $entity_array[ $model->primary_key_name() ]
941
+                        . $entity_array[$model->primary_key_name()]
942 942
                     ),
943 943
                 ],
944 944
             ];
@@ -955,12 +955,12 @@  discard block
 block discarded – undo
955 955
             foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956 956
                 $related_model_part                                                      =
957 957
                     Read::getRelatedEntityName($relation_name, $relation_obj);
958
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
958
+                $links[EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = [
959 959
                     [
960 960
                         'href'   => $this->getVersionedLinkTo(
961 961
                             EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962 962
                             . '/'
963
-                            . $entity_array[ $model->primary_key_name() ]
963
+                            . $entity_array[$model->primary_key_name()]
964 964
                             . '/'
965 965
                             . $related_model_part
966 966
                         ),
@@ -994,12 +994,12 @@  discard block
 block discarded – undo
994 994
         bool $included_items_protected = false
995 995
     ): array {
996 996
         // if $db_row not included, hope the entity array has what we need
997
-        if (! $db_row) {
997
+        if ( ! $db_row) {
998 998
             $db_row = $entity_array;
999 999
         }
1000 1000
         $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001 1001
         foreach ($relation_settings as $relation_name => $relation_obj) {
1002
-            $related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1002
+            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
1003 1003
                 $rest_request->get_param('include'),
1004 1004
                 $relation_name
1005 1005
             );
@@ -1027,7 +1027,7 @@  discard block
 block discarded – undo
1027 1027
                         $model->deduce_fields_n_values_from_cols_n_values($db_row)
1028 1028
                     )
1029 1029
                 );
1030
-                if (! $included_items_protected) {
1030
+                if ( ! $included_items_protected) {
1031 1031
                     try {
1032 1032
                         $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033 1033
                             $primary_model_query_params,
@@ -1047,7 +1047,7 @@  discard block
 block discarded – undo
1047 1047
                         ? null
1048 1048
                         : [];
1049 1049
                 }
1050
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1050
+                $entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results;
1051 1051
             }
1052 1052
         }
1053 1053
         return $entity_array;
@@ -1134,13 +1134,13 @@  discard block
 block discarded – undo
1134 1134
                 $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135 1135
                 if (
1136 1136
                     $row_is_protected
1137
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
-                    && $schema['properties'][ $field_to_calculate ]['protected']
1137
+                    && isset($schema['properties'][$field_to_calculate]['protected'])
1138
+                    && $schema['properties'][$field_to_calculate]['protected']
1139 1139
                 ) {
1140 1140
                     $calculated_value   = null;
1141 1141
                     $protected_fields[] = $field_to_calculate;
1142
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1142
+                    if ($schema['properties'][$field_to_calculate]['type']) {
1143
+                        switch ($schema['properties'][$field_to_calculate]['type']) {
1144 1144
                             case 'boolean':
1145 1145
                                 $calculated_value = false;
1146 1146
                                 break;
@@ -1254,7 +1254,7 @@  discard block
 block discarded – undo
1254 1254
      */
1255 1255
     public function validateContext(string $context): string
1256 1256
     {
1257
-        if (! $context) {
1257
+        if ( ! $context) {
1258 1258
             $context = EEM_Base::caps_read;
1259 1259
         }
1260 1260
         $valid_contexts = EEM_Base::valid_cap_contexts();
@@ -1279,7 +1279,7 @@  discard block
 block discarded – undo
1279 1279
             EEM_Base::default_where_conditions_minimum_all,
1280 1280
             EEM_Base::default_where_conditions_minimum_others,
1281 1281
         ];
1282
-        if (! $default_query_params) {
1282
+        if ( ! $default_query_params) {
1283 1283
             $default_query_params = EEM_Base::default_where_conditions_all;
1284 1284
         }
1285 1285
         if (
@@ -1366,14 +1366,14 @@  discard block
 block discarded – undo
1366 1366
         }
1367 1367
         if (isset($query_params['limit'])) {
1368 1368
             // limit should be either a string like '23' or '23,43', or an array with two items in it
1369
-            if (! is_array($query_params['limit'])) {
1369
+            if ( ! is_array($query_params['limit'])) {
1370 1370
                 $limit_array = explode(',', (string) $query_params['limit']);
1371 1371
             } else {
1372 1372
                 $limit_array = $query_params['limit'];
1373 1373
             }
1374 1374
             $sanitized_limit = [];
1375 1375
             foreach ($limit_array as $limit_part) {
1376
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1376
+                if ($this->debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377 1377
                     throw new EE_Error(
1378 1378
                         sprintf(
1379 1379
                             esc_html__(
@@ -1431,7 +1431,7 @@  discard block
 block discarded – undo
1431 1431
     {
1432 1432
         $model_ready_query_params = [];
1433 1433
         foreach ($query_params as $key => $value) {
1434
-            $model_ready_query_params[ $key ] = is_array($value)
1434
+            $model_ready_query_params[$key] = is_array($value)
1435 1435
                 ? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436 1436
                 : $value;
1437 1437
         }
@@ -1450,9 +1450,9 @@  discard block
 block discarded – undo
1450 1450
         $model_ready_query_params = [];
1451 1451
         foreach ($query_params as $key => $value) {
1452 1452
             if (is_array($value)) {
1453
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1453
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454 1454
             } else {
1455
-                $model_ready_query_params[ $key ] = $value;
1455
+                $model_ready_query_params[$key] = $value;
1456 1456
             }
1457 1457
         }
1458 1458
         return $model_ready_query_params;
@@ -1484,17 +1484,17 @@  discard block
 block discarded – undo
1484 1484
         foreach ($exploded_contents as $item) {
1485 1485
             $item = trim($item);
1486 1486
             // if no prefix was provided, so we look for items with no "." in them
1487
-            if (! $prefix) {
1487
+            if ( ! $prefix) {
1488 1488
                 // does this item have a period?
1489 1489
                 if (strpos($item, '.') === false) {
1490 1490
                     // if not, then its what we're looking for
1491 1491
                     $contents_with_prefix[] = $item;
1492 1492
                 }
1493
-            } elseif (strpos($item, $prefix . '.') === 0) {
1493
+            } elseif (strpos($item, $prefix.'.') === 0) {
1494 1494
                 // this item has the prefix and a period, grab it
1495 1495
                 $contents_with_prefix[] = substr(
1496 1496
                     $item,
1497
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1497
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1498 1498
                 );
1499 1499
             } elseif ($item === $prefix) {
1500 1500
                 // this item is JUST the prefix
@@ -1536,9 +1536,9 @@  discard block
 block discarded – undo
1536 1536
         if ($model_name) {
1537 1537
             foreach ($includes as $field_to_include) {
1538 1538
                 $field_to_include = trim($field_to_include);
1539
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1539
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1540 1540
                     // found the model name at the exact start
1541
-                    $field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1541
+                    $field_sans_model_name         = str_replace($model_name.'.', '', $field_to_include);
1542 1542
                     $extracted_fields_to_include[] = $field_sans_model_name;
1543 1543
                 } elseif ($field_to_include == $model_name) {
1544 1544
                     $extracted_fields_to_include[] = '*';
@@ -1581,7 +1581,7 @@  discard block
 block discarded – undo
1581 1581
         $restricted_query_params['caps'] = $context;
1582 1582
         $this->setDebugInfo('model query params', $restricted_query_params);
1583 1583
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
-        if (! empty($model_rows)) {
1584
+        if ( ! empty($model_rows)) {
1585 1585
             return $this->createEntityFromWpdbResult(
1586 1586
                 $model,
1587 1587
                 reset($model_rows),
@@ -1593,7 +1593,7 @@  discard block
 block discarded – undo
1593 1593
             if ($model->exists($query_params)) {
1594 1594
                 // you got shafted- it existed but we didn't want to tell you!
1595 1595
                 throw new RestException(
1596
-                    'rest_user_cannot_' . $context,
1596
+                    'rest_user_cannot_'.$context,
1597 1597
                     sprintf(
1598 1598
                         esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599 1599
                         $context,
@@ -1647,14 +1647,14 @@  discard block
 block discarded – undo
1647 1647
         // if this entity requires a password, they better give it and it better be right!
1648 1648
         if (
1649 1649
             $model->hasPassword()
1650
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1650
+            && $model_row[$model->getPasswordField()->get_qualified_column()] !== ''
1651 1651
         ) {
1652 1652
             if (empty($request['password'])) {
1653 1653
                 throw new RestPasswordRequiredException();
1654 1654
             }
1655 1655
             if (
1656 1656
                 ! hash_equals(
1657
-                    $model_row[ $model->getPasswordField()->get_qualified_column() ],
1657
+                    $model_row[$model->getPasswordField()->get_qualified_column()],
1658 1658
                     $request['password']
1659 1659
                 )
1660 1660
             ) {
@@ -1668,12 +1668,12 @@  discard block
 block discarded – undo
1668 1668
             $password_supplied = $request->get_param('password');
1669 1669
             if (empty($password_supplied)) {
1670 1670
                 $query_params['exclude_protected'] = true;
1671
-                if (! $model->exists($query_params)) {
1671
+                if ( ! $model->exists($query_params)) {
1672 1672
                     throw new RestPasswordRequiredException();
1673 1673
                 }
1674 1674
             } else {
1675
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
-                if (! $model->exists($query_params)) {
1675
+                $query_params[0][$model->modelChainAndPassword()] = $password_supplied;
1676
+                if ( ! $model->exists($query_params)) {
1677 1677
                     throw new RestPasswordIncorrectException();
1678 1678
                 }
1679 1679
             }
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Base.php 2 patches
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -18,92 +18,92 @@
 block discarded – undo
18 18
  */
19 19
 class Base extends Controller_Base
20 20
 {
21
-    /**
22
-     * Holds reference to the model version info, which knows the requested version
23
-     *
24
-     * @var ModelVersionInfo
25
-     */
26
-    protected $model_version_info;
21
+	/**
22
+	 * Holds reference to the model version info, which knows the requested version
23
+	 *
24
+	 * @var ModelVersionInfo
25
+	 */
26
+	protected $model_version_info;
27 27
 
28 28
 
29
-    /**
30
-     * Sets the version the user requested
31
-     *
32
-     * @param string $version eg '4.8'
33
-     */
34
-    public function setRequestedVersion(string $version)
35
-    {
36
-        parent::setRequestedVersion($version);
37
-        $this->model_version_info = new ModelVersionInfo($version);
38
-    }
29
+	/**
30
+	 * Sets the version the user requested
31
+	 *
32
+	 * @param string $version eg '4.8'
33
+	 */
34
+	public function setRequestedVersion(string $version)
35
+	{
36
+		parent::setRequestedVersion($version);
37
+		$this->model_version_info = new ModelVersionInfo($version);
38
+	}
39 39
 
40 40
 
41
-    /**
42
-     * Gets the object that should be used for getting any info from the models,
43
-     * because it's takes the requested and current core version into account
44
-     *
45
-     * @return ModelVersionInfo
46
-     * @throws EE_Error
47
-     */
48
-    public function getModelVersionInfo(): ModelVersionInfo
49
-    {
50
-        if (! $this->model_version_info) {
51
-            throw new EE_Error(
52
-                esc_html__(
53
-                    'Cannot use model version info before setting the requested version in the controller',
54
-                    'event_espresso'
55
-                )
56
-            );
57
-        }
58
-        return $this->model_version_info;
59
-    }
41
+	/**
42
+	 * Gets the object that should be used for getting any info from the models,
43
+	 * because it's takes the requested and current core version into account
44
+	 *
45
+	 * @return ModelVersionInfo
46
+	 * @throws EE_Error
47
+	 */
48
+	public function getModelVersionInfo(): ModelVersionInfo
49
+	{
50
+		if (! $this->model_version_info) {
51
+			throw new EE_Error(
52
+				esc_html__(
53
+					'Cannot use model version info before setting the requested version in the controller',
54
+					'event_espresso'
55
+				)
56
+			);
57
+		}
58
+		return $this->model_version_info;
59
+	}
60 60
 
61 61
 
62
-    /**
63
-     * Determines if $object is of one of the classes of $classes. Similar to
64
-     * in_array(), except this checks if $object is a subclass of the classnames provided
65
-     * in $classnames
66
-     *
67
-     * @param object $object
68
-     * @param array  $classnames
69
-     * @return boolean
70
-     */
71
-    public function isSubclassOfOne($object, array $classnames): bool
72
-    {
73
-        foreach ($classnames as $classname) {
74
-            if (is_a($object, $classname)) {
75
-                return true;
76
-            }
77
-        }
78
-        return false;
79
-    }
62
+	/**
63
+	 * Determines if $object is of one of the classes of $classes. Similar to
64
+	 * in_array(), except this checks if $object is a subclass of the classnames provided
65
+	 * in $classnames
66
+	 *
67
+	 * @param object $object
68
+	 * @param array  $classnames
69
+	 * @return boolean
70
+	 */
71
+	public function isSubclassOfOne($object, array $classnames): bool
72
+	{
73
+		foreach ($classnames as $classname) {
74
+			if (is_a($object, $classname)) {
75
+				return true;
76
+			}
77
+		}
78
+		return false;
79
+	}
80 80
 
81 81
 
82
-    /**
83
-     * Verifies the model name provided was valid. If so, returns the model (as an object). Otherwise, throws an
84
-     * exception. Must be called after `setRequestedVersion()`.
85
-     *
86
-     * @param string $model_name
87
-     * @return EEM_Base
88
-     * @throws EE_Error
89
-     * @throws RestException
90
-     * @since 4.9.76.p
91
-     */
92
-    protected function validateModel(string $model_name): EEM_Base
93
-    {
94
-        if (! $this->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
95
-            throw new RestException(
96
-                'endpoint_parsing_error',
97
-                sprintf(
98
-                    esc_html__(
99
-                        'There is no model for endpoint %s. Please contact event espresso support',
100
-                        'event_espresso'
101
-                    ),
102
-                    $model_name
103
-                )
104
-            );
105
-        }
106
-        return $this->getModelVersionInfo()->loadModel($model_name);
107
-    }
82
+	/**
83
+	 * Verifies the model name provided was valid. If so, returns the model (as an object). Otherwise, throws an
84
+	 * exception. Must be called after `setRequestedVersion()`.
85
+	 *
86
+	 * @param string $model_name
87
+	 * @return EEM_Base
88
+	 * @throws EE_Error
89
+	 * @throws RestException
90
+	 * @since 4.9.76.p
91
+	 */
92
+	protected function validateModel(string $model_name): EEM_Base
93
+	{
94
+		if (! $this->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
95
+			throw new RestException(
96
+				'endpoint_parsing_error',
97
+				sprintf(
98
+					esc_html__(
99
+						'There is no model for endpoint %s. Please contact event espresso support',
100
+						'event_espresso'
101
+					),
102
+					$model_name
103
+				)
104
+			);
105
+		}
106
+		return $this->getModelVersionInfo()->loadModel($model_name);
107
+	}
108 108
 }
109 109
 // End of file Base.php
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -47,7 +47,7 @@  discard block
 block discarded – undo
47 47
      */
48 48
     public function getModelVersionInfo(): ModelVersionInfo
49 49
     {
50
-        if (! $this->model_version_info) {
50
+        if ( ! $this->model_version_info) {
51 51
             throw new EE_Error(
52 52
                 esc_html__(
53 53
                     'Cannot use model version info before setting the requested version in the controller',
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
      */
92 92
     protected function validateModel(string $model_name): EEM_Base
93 93
     {
94
-        if (! $this->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
94
+        if ( ! $this->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
95 95
             throw new RestException(
96 96
                 'endpoint_parsing_error',
97 97
                 sprintf(
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/Base.php 2 patches
Indentation   +329 added lines, -329 removed lines patch added patch discarded remove patch
@@ -21,333 +21,333 @@
 block discarded – undo
21 21
  */
22 22
 class Base
23 23
 {
24
-    /**
25
-     * @deprecated use all-caps version
26
-     */
27
-    // @codingStandardsIgnoreStart
28
-    const header_prefix_for_ee = 'X-EE-';
29
-
30
-    // @codingStandardsIgnoreEnd
31
-
32
-    const HEADER_PREFIX_FOR_EE = 'X-EE-';
33
-
34
-    /**
35
-     * @deprecated use all-caps version instead
36
-     */
37
-    // @codingStandardsIgnoreStart
38
-    const header_prefix_for_wp = 'X-WP-';
39
-
40
-    // @codingStandardsIgnoreEnd
41
-
42
-    const HEADER_PREFIX_FOR_WP = 'X-WP-';
43
-
44
-    /**
45
-     * Contains debug info we'll send back in the response headers
46
-     *
47
-     * @var array
48
-     */
49
-    protected $debug_info = [];
50
-
51
-    /**
52
-     * Indicates whether or not the API is in debug mode
53
-     *
54
-     * @var boolean
55
-     */
56
-    protected $debug_mode = false;
57
-
58
-    /**
59
-     * Indicates the version that was requested
60
-     *
61
-     * @var string
62
-     */
63
-    protected $requested_version;
64
-
65
-    /**
66
-     * flat array of headers to send in the response
67
-     *
68
-     * @var array
69
-     */
70
-    protected $response_headers = [];
71
-
72
-
73
-    public function __construct()
74
-    {
75
-        $this->debug_mode = EED_Core_Rest_Api::debugMode();
76
-        // we are handling a REST request. Don't show a fancy HTML error message is any error comes up
77
-        add_filter('FHEE__EE_Error__get_error__show_normal_exceptions', '__return_true');
78
-    }
79
-
80
-
81
-    /**
82
-     * Sets the version the user requested
83
-     *
84
-     * @param string $version eg '4.8'
85
-     */
86
-    public function setRequestedVersion(string $version)
87
-    {
88
-        $this->requested_version = $version;
89
-    }
90
-
91
-
92
-    /**
93
-     * Sets some debug info that we'll send back in headers
94
-     *
95
-     * @param string       $key
96
-     * @param string|array $info
97
-     */
98
-    protected function setDebugInfo(string $key, $info)
99
-    {
100
-        $this->debug_info[ $key ] = $info;
101
-    }
102
-
103
-
104
-    /**
105
-     * Sets headers for the response
106
-     *
107
-     * @param string       $header_key    , excluding the "X-EE-" part
108
-     * @param array|string $value         if an array, multiple headers will be added, one
109
-     *                                    for each key in the array
110
-     * @param boolean      $use_ee_prefix whether to use the EE prefix on the header, or fallback to
111
-     *                                    the standard WP one
112
-     */
113
-    protected function setResponseHeader(string $header_key, $value, bool $use_ee_prefix = true)
114
-    {
115
-        if (is_array($value)) {
116
-            foreach ($value as $value_key => $value_value) {
117
-                $this->setResponseHeader($header_key . '[' . $value_key . ']', $value_value);
118
-            }
119
-        } else {
120
-            $prefix                                          =
121
-                $use_ee_prefix ? Base::HEADER_PREFIX_FOR_EE : Base::HEADER_PREFIX_FOR_WP;
122
-            $this->response_headers[ $prefix . $header_key ] = $value;
123
-        }
124
-    }
125
-
126
-
127
-    /**
128
-     * Returns a flat array of headers to be added to the response
129
-     *
130
-     * @return array
131
-     */
132
-    protected function getResponseHeaders(): array
133
-    {
134
-        return apply_filters(
135
-            'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_response_headers',
136
-            $this->response_headers,
137
-            $this,
138
-            $this->requested_version
139
-        );
140
-    }
141
-
142
-
143
-    /**
144
-     * Adds error notices from EE_Error onto the provided \WP_Error
145
-     *
146
-     * @param WP_Error $wp_error_response
147
-     * @return WP_Error
148
-     */
149
-    protected function addEeErrorsToResponse(WP_Error $wp_error_response): WP_Error
150
-    {
151
-        $notices_during_checkin = EE_Error::get_raw_notices();
152
-        if (! empty($notices_during_checkin['errors'])) {
153
-            foreach ($notices_during_checkin['errors'] as $error_code => $error_message) {
154
-                $wp_error_response->add(
155
-                    sanitize_key($error_code),
156
-                    strip_tags($error_message)
157
-                );
158
-            }
159
-        }
160
-        return $wp_error_response;
161
-    }
162
-
163
-
164
-    /**
165
-     * Sends a response, but also makes sure to attach headers that
166
-     * are handy for debugging.
167
-     * Specifically, we assume folks will want to know what exactly was the DB query that got run,
168
-     * what exactly was the Models query that got run, what capabilities came into play, what fields were omitted from
169
-     * the response, others?
170
-     *
171
-     * @param array|WP_Error|Exception|RestException $response
172
-     * @return WP_REST_Response
173
-     */
174
-    public function sendResponse($response): WP_REST_Response
175
-    {
176
-        if ($response instanceof RestException) {
177
-            $response = new WP_Error($response->getStringCode(), $response->getMessage(), $response->getData());
178
-        }
179
-        if ($response instanceof Exception) {
180
-            $code     = $response->getCode() ?: 'error_occurred';
181
-            $response = new WP_Error($code, $response->getMessage());
182
-        }
183
-        if ($response instanceof WP_Error) {
184
-            $response      = $this->addEeErrorsToResponse($response);
185
-            $rest_response = $this->createRestResponseFromWpError($response);
186
-        } else {
187
-            $rest_response = new WP_REST_Response($response, 200);
188
-        }
189
-        $headers = [];
190
-        if ($this->debug_mode && is_array($this->debug_info)) {
191
-            foreach ($this->debug_info as $debug_key => $debug_info) {
192
-                if (is_array($debug_info)) {
193
-                    $debug_info = wp_json_encode($debug_info);
194
-                }
195
-                $headers[ 'X-EE4-Debug-' . ucwords($debug_key) ] = $debug_info;
196
-            }
197
-        }
198
-        $headers = array_merge(
199
-            $headers,
200
-            $this->getResponseHeaders(),
201
-            $this->getHeadersFromEeNotices()
202
-        );
203
-        $rest_response->set_headers($headers);
204
-        return $rest_response;
205
-    }
206
-
207
-
208
-    /**
209
-     * Converts the \WP_Error into `WP_REST_Response.
210
-     * Mostly this is just a copy-and-paste from \WP_REST_Server::error_to_response
211
-     * (which is protected)
212
-     *
213
-     * @param WP_Error $wp_error
214
-     * @return WP_REST_Response
215
-     */
216
-    protected function createRestResponseFromWpError(WP_Error $wp_error): WP_REST_Response
217
-    {
218
-        $error_data = $wp_error->get_error_data();
219
-        if (is_array($error_data) && isset($error_data['status'])) {
220
-            $status = $error_data['status'];
221
-        } else {
222
-            $status = 500;
223
-        }
224
-        $errors = [];
225
-        foreach ($wp_error->errors as $code => $messages) {
226
-            foreach ((array) $messages as $message) {
227
-                $errors[] = [
228
-                    'code'    => $code,
229
-                    'message' => $message,
230
-                    'data'    => $wp_error->get_error_data($code),
231
-                ];
232
-            }
233
-        }
234
-        $data = $errors[0] ?? [];
235
-        if (count($errors) > 1) {
236
-            // Remove the primary error.
237
-            array_shift($errors);
238
-            $data['additional_errors'] = $errors;
239
-        }
240
-        return new WP_REST_Response($data, $status);
241
-    }
242
-
243
-
244
-    /**
245
-     * Array of headers derived from EE success, attention, and error messages
246
-     *
247
-     * @return array
248
-     */
249
-    protected function getHeadersFromEeNotices(): array
250
-    {
251
-        $headers = [];
252
-        $notices = EE_Error::get_raw_notices();
253
-        foreach ($notices as $notice_type => $sub_notices) {
254
-            if (! is_array($sub_notices)) {
255
-                continue;
256
-            }
257
-            foreach ($sub_notices as $notice_code => $sub_notice) {
258
-                $headers[ 'X-EE4-Notices-'
259
-                          . EEH_Inflector::humanize($notice_type)
260
-                          . '['
261
-                          . $notice_code
262
-                          . ']' ] = strip_tags((string) $sub_notice);
263
-            }
264
-        }
265
-        return apply_filters(
266
-            'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_headers_from_ee_notices__return',
267
-            $headers,
268
-            $this->requested_version,
269
-            $notices
270
-        );
271
-    }
272
-
273
-
274
-    /**
275
-     * Finds which version of the API was requested given the route, and returns it.
276
-     * eg in a request to "my-site.com/wp-json/ee/v4.8.29/events/123" this would return
277
-     * "4.8.29".
278
-     * We should know hte requested version in this model though, so if no route is
279
-     * provided just use what we set earlier
280
-     *
281
-     * @param string|null $route
282
-     * @return string
283
-     * @throws EE_Error
284
-     */
285
-    public function getRequestedVersion(?string $route = null): string
286
-    {
287
-        if ($route === null) {
288
-            return $this->requested_version;
289
-        }
290
-        $matches = $this->parseRoute(
291
-            $route,
292
-            '~' . EED_Core_Rest_Api::ee_api_namespace_for_regex . '~',
293
-            ['version']
294
-        );
295
-        return $matches['version'] ?? EED_Core_Rest_Api::latest_rest_api_version();
296
-    }
297
-
298
-
299
-    /**
300
-     * Applies the regex to the route, then creates an array using the values of
301
-     * $match_keys as keys (but ignores the full pattern match). Returns the array of matches.
302
-     * For example, if you call
303
-     * parse_route( '/ee/v4.8/events', '~\/ee\/v([^/]*)\/(.*)~', array( 'version', 'model' ) )
304
-     * it will return array( 'version' => '4.8', 'model' => 'events' )
305
-     *
306
-     * @param string $route
307
-     * @param string $regex
308
-     * @param array  $match_keys EXCLUDING matching the entire regex
309
-     * @return array where  $match_keys are the keys (the first value of $match_keys
310
-     *                           becomes the first key of the return value, etc. Eg passing in $match_keys of
311
-     *                           array( 'model', 'id' ), will, if the regex is successful, will return
312
-     *                           array( 'model' => 'foo', 'id' => 'bar' )
313
-     * @throws EE_Error if it couldn't be parsed
314
-     */
315
-    public function parseRoute(string $route, string $regex, array $match_keys): array
316
-    {
317
-        $indexed_matches = [];
318
-        $success         = preg_match($regex, $route, $matches);
319
-        if (is_array($matches)) {
320
-            // skip the overall regex match. Who cares
321
-            for ($i = 1; $i <= count($match_keys); $i++) {
322
-                if (! isset($matches[ $i ])) {
323
-                    $success = false;
324
-                } else {
325
-                    $indexed_matches[ $match_keys[ $i - 1 ] ] = $matches[ $i ];
326
-                }
327
-            }
328
-        }
329
-        if (! $success) {
330
-            throw new EE_Error(
331
-                esc_html__('We could not parse the URL. Please contact Event Espresso Support', 'event_espresso'),
332
-                'endpoint_parsing_error'
333
-            );
334
-        }
335
-        return $indexed_matches;
336
-    }
337
-
338
-
339
-    /**
340
-     * Gets the body's params (either from JSON or parsed body), which EXCLUDES the GET params and URL params
341
-     *
342
-     * @param WP_REST_Request $request
343
-     * @return array
344
-     */
345
-    protected function getBodyParams(WP_REST_Request $request): array
346
-    {
347
-        // $request->get_params();
348
-        return array_merge(
349
-            (array) $request->get_body_params(),
350
-            (array) $request->get_json_params()
351
-        );
352
-    }
24
+	/**
25
+	 * @deprecated use all-caps version
26
+	 */
27
+	// @codingStandardsIgnoreStart
28
+	const header_prefix_for_ee = 'X-EE-';
29
+
30
+	// @codingStandardsIgnoreEnd
31
+
32
+	const HEADER_PREFIX_FOR_EE = 'X-EE-';
33
+
34
+	/**
35
+	 * @deprecated use all-caps version instead
36
+	 */
37
+	// @codingStandardsIgnoreStart
38
+	const header_prefix_for_wp = 'X-WP-';
39
+
40
+	// @codingStandardsIgnoreEnd
41
+
42
+	const HEADER_PREFIX_FOR_WP = 'X-WP-';
43
+
44
+	/**
45
+	 * Contains debug info we'll send back in the response headers
46
+	 *
47
+	 * @var array
48
+	 */
49
+	protected $debug_info = [];
50
+
51
+	/**
52
+	 * Indicates whether or not the API is in debug mode
53
+	 *
54
+	 * @var boolean
55
+	 */
56
+	protected $debug_mode = false;
57
+
58
+	/**
59
+	 * Indicates the version that was requested
60
+	 *
61
+	 * @var string
62
+	 */
63
+	protected $requested_version;
64
+
65
+	/**
66
+	 * flat array of headers to send in the response
67
+	 *
68
+	 * @var array
69
+	 */
70
+	protected $response_headers = [];
71
+
72
+
73
+	public function __construct()
74
+	{
75
+		$this->debug_mode = EED_Core_Rest_Api::debugMode();
76
+		// we are handling a REST request. Don't show a fancy HTML error message is any error comes up
77
+		add_filter('FHEE__EE_Error__get_error__show_normal_exceptions', '__return_true');
78
+	}
79
+
80
+
81
+	/**
82
+	 * Sets the version the user requested
83
+	 *
84
+	 * @param string $version eg '4.8'
85
+	 */
86
+	public function setRequestedVersion(string $version)
87
+	{
88
+		$this->requested_version = $version;
89
+	}
90
+
91
+
92
+	/**
93
+	 * Sets some debug info that we'll send back in headers
94
+	 *
95
+	 * @param string       $key
96
+	 * @param string|array $info
97
+	 */
98
+	protected function setDebugInfo(string $key, $info)
99
+	{
100
+		$this->debug_info[ $key ] = $info;
101
+	}
102
+
103
+
104
+	/**
105
+	 * Sets headers for the response
106
+	 *
107
+	 * @param string       $header_key    , excluding the "X-EE-" part
108
+	 * @param array|string $value         if an array, multiple headers will be added, one
109
+	 *                                    for each key in the array
110
+	 * @param boolean      $use_ee_prefix whether to use the EE prefix on the header, or fallback to
111
+	 *                                    the standard WP one
112
+	 */
113
+	protected function setResponseHeader(string $header_key, $value, bool $use_ee_prefix = true)
114
+	{
115
+		if (is_array($value)) {
116
+			foreach ($value as $value_key => $value_value) {
117
+				$this->setResponseHeader($header_key . '[' . $value_key . ']', $value_value);
118
+			}
119
+		} else {
120
+			$prefix                                          =
121
+				$use_ee_prefix ? Base::HEADER_PREFIX_FOR_EE : Base::HEADER_PREFIX_FOR_WP;
122
+			$this->response_headers[ $prefix . $header_key ] = $value;
123
+		}
124
+	}
125
+
126
+
127
+	/**
128
+	 * Returns a flat array of headers to be added to the response
129
+	 *
130
+	 * @return array
131
+	 */
132
+	protected function getResponseHeaders(): array
133
+	{
134
+		return apply_filters(
135
+			'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_response_headers',
136
+			$this->response_headers,
137
+			$this,
138
+			$this->requested_version
139
+		);
140
+	}
141
+
142
+
143
+	/**
144
+	 * Adds error notices from EE_Error onto the provided \WP_Error
145
+	 *
146
+	 * @param WP_Error $wp_error_response
147
+	 * @return WP_Error
148
+	 */
149
+	protected function addEeErrorsToResponse(WP_Error $wp_error_response): WP_Error
150
+	{
151
+		$notices_during_checkin = EE_Error::get_raw_notices();
152
+		if (! empty($notices_during_checkin['errors'])) {
153
+			foreach ($notices_during_checkin['errors'] as $error_code => $error_message) {
154
+				$wp_error_response->add(
155
+					sanitize_key($error_code),
156
+					strip_tags($error_message)
157
+				);
158
+			}
159
+		}
160
+		return $wp_error_response;
161
+	}
162
+
163
+
164
+	/**
165
+	 * Sends a response, but also makes sure to attach headers that
166
+	 * are handy for debugging.
167
+	 * Specifically, we assume folks will want to know what exactly was the DB query that got run,
168
+	 * what exactly was the Models query that got run, what capabilities came into play, what fields were omitted from
169
+	 * the response, others?
170
+	 *
171
+	 * @param array|WP_Error|Exception|RestException $response
172
+	 * @return WP_REST_Response
173
+	 */
174
+	public function sendResponse($response): WP_REST_Response
175
+	{
176
+		if ($response instanceof RestException) {
177
+			$response = new WP_Error($response->getStringCode(), $response->getMessage(), $response->getData());
178
+		}
179
+		if ($response instanceof Exception) {
180
+			$code     = $response->getCode() ?: 'error_occurred';
181
+			$response = new WP_Error($code, $response->getMessage());
182
+		}
183
+		if ($response instanceof WP_Error) {
184
+			$response      = $this->addEeErrorsToResponse($response);
185
+			$rest_response = $this->createRestResponseFromWpError($response);
186
+		} else {
187
+			$rest_response = new WP_REST_Response($response, 200);
188
+		}
189
+		$headers = [];
190
+		if ($this->debug_mode && is_array($this->debug_info)) {
191
+			foreach ($this->debug_info as $debug_key => $debug_info) {
192
+				if (is_array($debug_info)) {
193
+					$debug_info = wp_json_encode($debug_info);
194
+				}
195
+				$headers[ 'X-EE4-Debug-' . ucwords($debug_key) ] = $debug_info;
196
+			}
197
+		}
198
+		$headers = array_merge(
199
+			$headers,
200
+			$this->getResponseHeaders(),
201
+			$this->getHeadersFromEeNotices()
202
+		);
203
+		$rest_response->set_headers($headers);
204
+		return $rest_response;
205
+	}
206
+
207
+
208
+	/**
209
+	 * Converts the \WP_Error into `WP_REST_Response.
210
+	 * Mostly this is just a copy-and-paste from \WP_REST_Server::error_to_response
211
+	 * (which is protected)
212
+	 *
213
+	 * @param WP_Error $wp_error
214
+	 * @return WP_REST_Response
215
+	 */
216
+	protected function createRestResponseFromWpError(WP_Error $wp_error): WP_REST_Response
217
+	{
218
+		$error_data = $wp_error->get_error_data();
219
+		if (is_array($error_data) && isset($error_data['status'])) {
220
+			$status = $error_data['status'];
221
+		} else {
222
+			$status = 500;
223
+		}
224
+		$errors = [];
225
+		foreach ($wp_error->errors as $code => $messages) {
226
+			foreach ((array) $messages as $message) {
227
+				$errors[] = [
228
+					'code'    => $code,
229
+					'message' => $message,
230
+					'data'    => $wp_error->get_error_data($code),
231
+				];
232
+			}
233
+		}
234
+		$data = $errors[0] ?? [];
235
+		if (count($errors) > 1) {
236
+			// Remove the primary error.
237
+			array_shift($errors);
238
+			$data['additional_errors'] = $errors;
239
+		}
240
+		return new WP_REST_Response($data, $status);
241
+	}
242
+
243
+
244
+	/**
245
+	 * Array of headers derived from EE success, attention, and error messages
246
+	 *
247
+	 * @return array
248
+	 */
249
+	protected function getHeadersFromEeNotices(): array
250
+	{
251
+		$headers = [];
252
+		$notices = EE_Error::get_raw_notices();
253
+		foreach ($notices as $notice_type => $sub_notices) {
254
+			if (! is_array($sub_notices)) {
255
+				continue;
256
+			}
257
+			foreach ($sub_notices as $notice_code => $sub_notice) {
258
+				$headers[ 'X-EE4-Notices-'
259
+						  . EEH_Inflector::humanize($notice_type)
260
+						  . '['
261
+						  . $notice_code
262
+						  . ']' ] = strip_tags((string) $sub_notice);
263
+			}
264
+		}
265
+		return apply_filters(
266
+			'FHEE__EventEspresso\core\libraries\rest_api\controllers\Base___get_headers_from_ee_notices__return',
267
+			$headers,
268
+			$this->requested_version,
269
+			$notices
270
+		);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Finds which version of the API was requested given the route, and returns it.
276
+	 * eg in a request to "my-site.com/wp-json/ee/v4.8.29/events/123" this would return
277
+	 * "4.8.29".
278
+	 * We should know hte requested version in this model though, so if no route is
279
+	 * provided just use what we set earlier
280
+	 *
281
+	 * @param string|null $route
282
+	 * @return string
283
+	 * @throws EE_Error
284
+	 */
285
+	public function getRequestedVersion(?string $route = null): string
286
+	{
287
+		if ($route === null) {
288
+			return $this->requested_version;
289
+		}
290
+		$matches = $this->parseRoute(
291
+			$route,
292
+			'~' . EED_Core_Rest_Api::ee_api_namespace_for_regex . '~',
293
+			['version']
294
+		);
295
+		return $matches['version'] ?? EED_Core_Rest_Api::latest_rest_api_version();
296
+	}
297
+
298
+
299
+	/**
300
+	 * Applies the regex to the route, then creates an array using the values of
301
+	 * $match_keys as keys (but ignores the full pattern match). Returns the array of matches.
302
+	 * For example, if you call
303
+	 * parse_route( '/ee/v4.8/events', '~\/ee\/v([^/]*)\/(.*)~', array( 'version', 'model' ) )
304
+	 * it will return array( 'version' => '4.8', 'model' => 'events' )
305
+	 *
306
+	 * @param string $route
307
+	 * @param string $regex
308
+	 * @param array  $match_keys EXCLUDING matching the entire regex
309
+	 * @return array where  $match_keys are the keys (the first value of $match_keys
310
+	 *                           becomes the first key of the return value, etc. Eg passing in $match_keys of
311
+	 *                           array( 'model', 'id' ), will, if the regex is successful, will return
312
+	 *                           array( 'model' => 'foo', 'id' => 'bar' )
313
+	 * @throws EE_Error if it couldn't be parsed
314
+	 */
315
+	public function parseRoute(string $route, string $regex, array $match_keys): array
316
+	{
317
+		$indexed_matches = [];
318
+		$success         = preg_match($regex, $route, $matches);
319
+		if (is_array($matches)) {
320
+			// skip the overall regex match. Who cares
321
+			for ($i = 1; $i <= count($match_keys); $i++) {
322
+				if (! isset($matches[ $i ])) {
323
+					$success = false;
324
+				} else {
325
+					$indexed_matches[ $match_keys[ $i - 1 ] ] = $matches[ $i ];
326
+				}
327
+			}
328
+		}
329
+		if (! $success) {
330
+			throw new EE_Error(
331
+				esc_html__('We could not parse the URL. Please contact Event Espresso Support', 'event_espresso'),
332
+				'endpoint_parsing_error'
333
+			);
334
+		}
335
+		return $indexed_matches;
336
+	}
337
+
338
+
339
+	/**
340
+	 * Gets the body's params (either from JSON or parsed body), which EXCLUDES the GET params and URL params
341
+	 *
342
+	 * @param WP_REST_Request $request
343
+	 * @return array
344
+	 */
345
+	protected function getBodyParams(WP_REST_Request $request): array
346
+	{
347
+		// $request->get_params();
348
+		return array_merge(
349
+			(array) $request->get_body_params(),
350
+			(array) $request->get_json_params()
351
+		);
352
+	}
353 353
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -97,7 +97,7 @@  discard block
 block discarded – undo
97 97
      */
98 98
     protected function setDebugInfo(string $key, $info)
99 99
     {
100
-        $this->debug_info[ $key ] = $info;
100
+        $this->debug_info[$key] = $info;
101 101
     }
102 102
 
103 103
 
@@ -114,12 +114,12 @@  discard block
 block discarded – undo
114 114
     {
115 115
         if (is_array($value)) {
116 116
             foreach ($value as $value_key => $value_value) {
117
-                $this->setResponseHeader($header_key . '[' . $value_key . ']', $value_value);
117
+                $this->setResponseHeader($header_key.'['.$value_key.']', $value_value);
118 118
             }
119 119
         } else {
120 120
             $prefix                                          =
121 121
                 $use_ee_prefix ? Base::HEADER_PREFIX_FOR_EE : Base::HEADER_PREFIX_FOR_WP;
122
-            $this->response_headers[ $prefix . $header_key ] = $value;
122
+            $this->response_headers[$prefix.$header_key] = $value;
123 123
         }
124 124
     }
125 125
 
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
     protected function addEeErrorsToResponse(WP_Error $wp_error_response): WP_Error
150 150
     {
151 151
         $notices_during_checkin = EE_Error::get_raw_notices();
152
-        if (! empty($notices_during_checkin['errors'])) {
152
+        if ( ! empty($notices_during_checkin['errors'])) {
153 153
             foreach ($notices_during_checkin['errors'] as $error_code => $error_message) {
154 154
                 $wp_error_response->add(
155 155
                     sanitize_key($error_code),
@@ -192,7 +192,7 @@  discard block
 block discarded – undo
192 192
                 if (is_array($debug_info)) {
193 193
                     $debug_info = wp_json_encode($debug_info);
194 194
                 }
195
-                $headers[ 'X-EE4-Debug-' . ucwords($debug_key) ] = $debug_info;
195
+                $headers['X-EE4-Debug-'.ucwords($debug_key)] = $debug_info;
196 196
             }
197 197
         }
198 198
         $headers = array_merge(
@@ -251,15 +251,15 @@  discard block
 block discarded – undo
251 251
         $headers = [];
252 252
         $notices = EE_Error::get_raw_notices();
253 253
         foreach ($notices as $notice_type => $sub_notices) {
254
-            if (! is_array($sub_notices)) {
254
+            if ( ! is_array($sub_notices)) {
255 255
                 continue;
256 256
             }
257 257
             foreach ($sub_notices as $notice_code => $sub_notice) {
258
-                $headers[ 'X-EE4-Notices-'
258
+                $headers['X-EE4-Notices-'
259 259
                           . EEH_Inflector::humanize($notice_type)
260 260
                           . '['
261 261
                           . $notice_code
262
-                          . ']' ] = strip_tags((string) $sub_notice);
262
+                          . ']'] = strip_tags((string) $sub_notice);
263 263
             }
264 264
         }
265 265
         return apply_filters(
@@ -289,7 +289,7 @@  discard block
 block discarded – undo
289 289
         }
290 290
         $matches = $this->parseRoute(
291 291
             $route,
292
-            '~' . EED_Core_Rest_Api::ee_api_namespace_for_regex . '~',
292
+            '~'.EED_Core_Rest_Api::ee_api_namespace_for_regex.'~',
293 293
             ['version']
294 294
         );
295 295
         return $matches['version'] ?? EED_Core_Rest_Api::latest_rest_api_version();
@@ -319,14 +319,14 @@  discard block
 block discarded – undo
319 319
         if (is_array($matches)) {
320 320
             // skip the overall regex match. Who cares
321 321
             for ($i = 1; $i <= count($match_keys); $i++) {
322
-                if (! isset($matches[ $i ])) {
322
+                if ( ! isset($matches[$i])) {
323 323
                     $success = false;
324 324
                 } else {
325
-                    $indexed_matches[ $match_keys[ $i - 1 ] ] = $matches[ $i ];
325
+                    $indexed_matches[$match_keys[$i - 1]] = $matches[$i];
326 326
                 }
327 327
             }
328 328
         }
329
-        if (! $success) {
329
+        if ( ! $success) {
330 330
             throw new EE_Error(
331 331
                 esc_html__('We could not parse the URL. Please contact Event Espresso Support', 'event_espresso'),
332 332
                 'endpoint_parsing_error'
Please login to merge, or discard this patch.
core/libraries/rest_api/Capabilities.php 2 patches
Indentation   +186 added lines, -186 removed lines patch added patch discarded remove patch
@@ -16,200 +16,200 @@
 block discarded – undo
16 16
  */
17 17
 class Capabilities
18 18
 {
19
-    /**
20
-     * The current user can see at least SOME of these entities.
21
-     *
22
-     * @param EEM_Base $model
23
-     * @param string   $model_context one of the return values from EEM_Base::valid_cap_contexts()
24
-     * @return boolean
25
-     * @throws EE_Error
26
-     */
27
-    public static function currentUserHasPartialAccessTo(
28
-        EEM_Base $model,
29
-        string $model_context = EEM_Base::caps_read
30
-    ): bool {
31
-        if (
32
-            apply_filters(
33
-                'FHEE__Capabilities__current_user_has_partial_access_to__override_begin',
34
-                false,
35
-                $model,
36
-                $model
37
-            )
38
-        ) {
39
-            return true;
40
-        }
41
-        foreach ($model->caps_missing($model_context) as $restriction_obj) {
42
-            if ($restriction_obj instanceof EE_Return_None_Where_Conditions) {
43
-                return false;
44
-            }
45
-        }
46
-        if (
47
-            apply_filters(
48
-                'FHEE__Capabilities__current_user_has_partial_access_to__override_end',
49
-                false,
50
-                $model,
51
-                $model
52
-            )
53
-        ) {
54
-            return false;
55
-        }
56
-        return true;
57
-    }
19
+	/**
20
+	 * The current user can see at least SOME of these entities.
21
+	 *
22
+	 * @param EEM_Base $model
23
+	 * @param string   $model_context one of the return values from EEM_Base::valid_cap_contexts()
24
+	 * @return boolean
25
+	 * @throws EE_Error
26
+	 */
27
+	public static function currentUserHasPartialAccessTo(
28
+		EEM_Base $model,
29
+		string $model_context = EEM_Base::caps_read
30
+	): bool {
31
+		if (
32
+			apply_filters(
33
+				'FHEE__Capabilities__current_user_has_partial_access_to__override_begin',
34
+				false,
35
+				$model,
36
+				$model
37
+			)
38
+		) {
39
+			return true;
40
+		}
41
+		foreach ($model->caps_missing($model_context) as $restriction_obj) {
42
+			if ($restriction_obj instanceof EE_Return_None_Where_Conditions) {
43
+				return false;
44
+			}
45
+		}
46
+		if (
47
+			apply_filters(
48
+				'FHEE__Capabilities__current_user_has_partial_access_to__override_end',
49
+				false,
50
+				$model,
51
+				$model
52
+			)
53
+		) {
54
+			return false;
55
+		}
56
+		return true;
57
+	}
58 58
 
59 59
 
60
-    /**
61
-     * Gets an array of all the capabilities the current user is missing that affected
62
-     * the query
63
-     *
64
-     * @param EEM_Base $model
65
-     * @param string   $request_type one of the constants on WP_JSON_Server
66
-     * @return array
67
-     * @throws EE_Error
68
-     */
69
-    public static function getMissingPermissions(EEM_Base $model, string $request_type = EEM_Base::caps_read): array
70
-    {
71
-        return $model->caps_missing($request_type);
72
-    }
60
+	/**
61
+	 * Gets an array of all the capabilities the current user is missing that affected
62
+	 * the query
63
+	 *
64
+	 * @param EEM_Base $model
65
+	 * @param string   $request_type one of the constants on WP_JSON_Server
66
+	 * @return array
67
+	 * @throws EE_Error
68
+	 */
69
+	public static function getMissingPermissions(EEM_Base $model, string $request_type = EEM_Base::caps_read): array
70
+	{
71
+		return $model->caps_missing($request_type);
72
+	}
73 73
 
74 74
 
75
-    /**
76
-     * Gets a string of all the capabilities the current user is missing that affected
77
-     * the query
78
-     *
79
-     * @param EEM_Base $model
80
-     * @param string   $model_context one of the return values from EEM_Base::valid_cap_contexts()
81
-     * @return string
82
-     * @throws EE_Error
83
-     */
84
-    public static function getMissingPermissionsString(
85
-        EEM_Base $model,
86
-        string $model_context = EEM_Base::caps_read
87
-    ): string {
88
-        return implode(',', array_keys(self::getMissingPermissions($model, $model_context)));
89
-    }
75
+	/**
76
+	 * Gets a string of all the capabilities the current user is missing that affected
77
+	 * the query
78
+	 *
79
+	 * @param EEM_Base $model
80
+	 * @param string   $model_context one of the return values from EEM_Base::valid_cap_contexts()
81
+	 * @return string
82
+	 * @throws EE_Error
83
+	 */
84
+	public static function getMissingPermissionsString(
85
+		EEM_Base $model,
86
+		string $model_context = EEM_Base::caps_read
87
+	): string {
88
+		return implode(',', array_keys(self::getMissingPermissions($model, $model_context)));
89
+	}
90 90
 
91 91
 
92
-    /**
93
-     * "Removes" password-protected fields. Currently that means setting their values to their default.
94
-     *
95
-     * @param array            $entity
96
-     * @param EEM_Base         $model
97
-     * @param ModelVersionInfo $model_version_info
98
-     * @return array
99
-     * @throws EE_Error
100
-     * @since 4.9.74.p
101
-     */
102
-    public static function filterOutPasswordProtectedFields(
103
-        array $entity,
104
-        EEM_Base $model,
105
-        ModelVersionInfo $model_version_info
106
-    ): array {
107
-        $has_password = $model->hasPassword();
108
-        if ($has_password) {
109
-            $entity[ $model->getPasswordField()->get_name() ] = ModelDataTranslator::prepareFieldValueForJson(
110
-                $model->getPasswordField(),
111
-                $model->getPasswordField()->get_default_value(),
112
-                $model_version_info->requestedVersion()
113
-            );
114
-        }
115
-        foreach ($model->field_settings() as $field_name => $field_obj) {
116
-            if (
117
-                $has_password
118
-                && $model->getPasswordField()->fieldIsProtected($field_name)
119
-                && $entity[ $field_name ]
120
-            ) {
121
-                $replacement_value = ModelDataTranslator::prepareFieldValueForJson(
122
-                    $field_obj,
123
-                    $field_obj->get_default_value(),
124
-                    $model_version_info->requestedVersion()
125
-                );
126
-                if ($model_version_info->fieldHasRenderedFormat($field_obj)) {
127
-                    $entity[ $field_name ]['rendered'] = $replacement_value;
128
-                } elseif ($model_version_info->fieldHasPrettyFormat($field_obj)) {
129
-                    $entity[ $field_name ]['raw']    = $replacement_value;
130
-                    $entity[ $field_name ]['pretty'] = ModelDataTranslator::prepareFieldValueForJson(
131
-                        $field_obj,
132
-                        $field_obj->prepare_for_pretty_echoing($field_obj->get_default_value()),
133
-                        $model_version_info->requestedVersion()
134
-                    );
135
-                } else {
136
-                    // this is most likely an excerpt field. (These should have also had "rendered" and "raw"
137
-                    // versions, but we missed that, and can't change it without breaking backward compatibility)
138
-                    // so just remove it (or rather, set its default)
139
-                    // API clients will just need to look to fields with rendered formats to know if these have
140
-                    // been redacted. Sorry.
141
-                    $entity[ $field_name ] = $replacement_value;
142
-                }
143
-            }
144
-        }
145
-        return $entity;
146
-    }
92
+	/**
93
+	 * "Removes" password-protected fields. Currently that means setting their values to their default.
94
+	 *
95
+	 * @param array            $entity
96
+	 * @param EEM_Base         $model
97
+	 * @param ModelVersionInfo $model_version_info
98
+	 * @return array
99
+	 * @throws EE_Error
100
+	 * @since 4.9.74.p
101
+	 */
102
+	public static function filterOutPasswordProtectedFields(
103
+		array $entity,
104
+		EEM_Base $model,
105
+		ModelVersionInfo $model_version_info
106
+	): array {
107
+		$has_password = $model->hasPassword();
108
+		if ($has_password) {
109
+			$entity[ $model->getPasswordField()->get_name() ] = ModelDataTranslator::prepareFieldValueForJson(
110
+				$model->getPasswordField(),
111
+				$model->getPasswordField()->get_default_value(),
112
+				$model_version_info->requestedVersion()
113
+			);
114
+		}
115
+		foreach ($model->field_settings() as $field_name => $field_obj) {
116
+			if (
117
+				$has_password
118
+				&& $model->getPasswordField()->fieldIsProtected($field_name)
119
+				&& $entity[ $field_name ]
120
+			) {
121
+				$replacement_value = ModelDataTranslator::prepareFieldValueForJson(
122
+					$field_obj,
123
+					$field_obj->get_default_value(),
124
+					$model_version_info->requestedVersion()
125
+				);
126
+				if ($model_version_info->fieldHasRenderedFormat($field_obj)) {
127
+					$entity[ $field_name ]['rendered'] = $replacement_value;
128
+				} elseif ($model_version_info->fieldHasPrettyFormat($field_obj)) {
129
+					$entity[ $field_name ]['raw']    = $replacement_value;
130
+					$entity[ $field_name ]['pretty'] = ModelDataTranslator::prepareFieldValueForJson(
131
+						$field_obj,
132
+						$field_obj->prepare_for_pretty_echoing($field_obj->get_default_value()),
133
+						$model_version_info->requestedVersion()
134
+					);
135
+				} else {
136
+					// this is most likely an excerpt field. (These should have also had "rendered" and "raw"
137
+					// versions, but we missed that, and can't change it without breaking backward compatibility)
138
+					// so just remove it (or rather, set its default)
139
+					// API clients will just need to look to fields with rendered formats to know if these have
140
+					// been redacted. Sorry.
141
+					$entity[ $field_name ] = $replacement_value;
142
+				}
143
+			}
144
+		}
145
+		return $entity;
146
+	}
147 147
 
148 148
 
149
-    /**
150
-     * Takes a entity that's ready to be returned and removes fields which the user shouldn't be able to access.
151
-     *
152
-     * @param array            $entity
153
-     * @param EEM_Base         $model
154
-     * @param string           $request_type one of the return values from EEM_Base::valid_cap_contexts()
155
-     * @param ModelVersionInfo $model_version_info
156
-     * @return array ready for converting into json
157
-     */
158
-    public static function filterOutInaccessibleEntityFields(
159
-        array $entity,
160
-        EEM_Base $model,
161
-        string $request_type,
162
-        ModelVersionInfo $model_version_info
163
-    ): array {
164
-        foreach ($model->field_settings() as $field_name => $field_obj) {
165
-            if (
166
-                $model_version_info->fieldHasRenderedFormat($field_obj)
167
-                && isset($entity[ $field_name ])
168
-                && is_array($entity[ $field_name ])
169
-                && isset($entity[ $field_name ]['raw'])
170
-            ) {
171
-                unset($entity[ $field_name ]['raw']);
172
-            }
173
-        }
174
-        // theoretically we may want to filter out specific fields for specific models
175
-        return apply_filters(
176
-            'FHEE__Capabilities__filter_out_inaccessible_entity_fields',
177
-            $entity,
178
-            $model,
179
-            $request_type
180
-        );
181
-    }
149
+	/**
150
+	 * Takes a entity that's ready to be returned and removes fields which the user shouldn't be able to access.
151
+	 *
152
+	 * @param array            $entity
153
+	 * @param EEM_Base         $model
154
+	 * @param string           $request_type one of the return values from EEM_Base::valid_cap_contexts()
155
+	 * @param ModelVersionInfo $model_version_info
156
+	 * @return array ready for converting into json
157
+	 */
158
+	public static function filterOutInaccessibleEntityFields(
159
+		array $entity,
160
+		EEM_Base $model,
161
+		string $request_type,
162
+		ModelVersionInfo $model_version_info
163
+	): array {
164
+		foreach ($model->field_settings() as $field_name => $field_obj) {
165
+			if (
166
+				$model_version_info->fieldHasRenderedFormat($field_obj)
167
+				&& isset($entity[ $field_name ])
168
+				&& is_array($entity[ $field_name ])
169
+				&& isset($entity[ $field_name ]['raw'])
170
+			) {
171
+				unset($entity[ $field_name ]['raw']);
172
+			}
173
+		}
174
+		// theoretically we may want to filter out specific fields for specific models
175
+		return apply_filters(
176
+			'FHEE__Capabilities__filter_out_inaccessible_entity_fields',
177
+			$entity,
178
+			$model,
179
+			$request_type
180
+		);
181
+	}
182 182
 
183 183
 
184
-    /**
185
-     * Verifies the current user has at least partial access to do this action on this model.
186
-     * If not, throws an exception (so we can define the code that sets up this error object
187
-     * once)
188
-     *
189
-     * @param EEM_Base $model
190
-     * @param string   $model_action_context
191
-     * @param string   $action_name
192
-     * @return void
193
-     * @throws RestException
194
-     * @throws EE_Error
195
-     */
196
-    public static function verifyAtLeastPartialAccessTo(
197
-        EEM_Base $model,
198
-        string $model_action_context,
199
-        string $action_name = 'list'
200
-    ) {
201
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $model_action_context)) {
202
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
203
-            throw new RestException(
204
-                sprintf('rest_cannot_%s_%s', strtolower($action_name), $model_name_plural),
205
-                sprintf(
206
-                    esc_html__('Sorry, you are not allowed to %1$s %2$s. Missing permissions: %3$s', 'event_espresso'),
207
-                    $action_name,
208
-                    $model_name_plural,
209
-                    Capabilities::getMissingPermissionsString($model, $model_action_context)
210
-                ),
211
-                ['status' => 403]
212
-            );
213
-        }
214
-    }
184
+	/**
185
+	 * Verifies the current user has at least partial access to do this action on this model.
186
+	 * If not, throws an exception (so we can define the code that sets up this error object
187
+	 * once)
188
+	 *
189
+	 * @param EEM_Base $model
190
+	 * @param string   $model_action_context
191
+	 * @param string   $action_name
192
+	 * @return void
193
+	 * @throws RestException
194
+	 * @throws EE_Error
195
+	 */
196
+	public static function verifyAtLeastPartialAccessTo(
197
+		EEM_Base $model,
198
+		string $model_action_context,
199
+		string $action_name = 'list'
200
+	) {
201
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $model_action_context)) {
202
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
203
+			throw new RestException(
204
+				sprintf('rest_cannot_%s_%s', strtolower($action_name), $model_name_plural),
205
+				sprintf(
206
+					esc_html__('Sorry, you are not allowed to %1$s %2$s. Missing permissions: %3$s', 'event_espresso'),
207
+					$action_name,
208
+					$model_name_plural,
209
+					Capabilities::getMissingPermissionsString($model, $model_action_context)
210
+				),
211
+				['status' => 403]
212
+			);
213
+		}
214
+	}
215 215
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -106,7 +106,7 @@  discard block
 block discarded – undo
106 106
     ): array {
107 107
         $has_password = $model->hasPassword();
108 108
         if ($has_password) {
109
-            $entity[ $model->getPasswordField()->get_name() ] = ModelDataTranslator::prepareFieldValueForJson(
109
+            $entity[$model->getPasswordField()->get_name()] = ModelDataTranslator::prepareFieldValueForJson(
110 110
                 $model->getPasswordField(),
111 111
                 $model->getPasswordField()->get_default_value(),
112 112
                 $model_version_info->requestedVersion()
@@ -116,7 +116,7 @@  discard block
 block discarded – undo
116 116
             if (
117 117
                 $has_password
118 118
                 && $model->getPasswordField()->fieldIsProtected($field_name)
119
-                && $entity[ $field_name ]
119
+                && $entity[$field_name]
120 120
             ) {
121 121
                 $replacement_value = ModelDataTranslator::prepareFieldValueForJson(
122 122
                     $field_obj,
@@ -124,10 +124,10 @@  discard block
 block discarded – undo
124 124
                     $model_version_info->requestedVersion()
125 125
                 );
126 126
                 if ($model_version_info->fieldHasRenderedFormat($field_obj)) {
127
-                    $entity[ $field_name ]['rendered'] = $replacement_value;
127
+                    $entity[$field_name]['rendered'] = $replacement_value;
128 128
                 } elseif ($model_version_info->fieldHasPrettyFormat($field_obj)) {
129
-                    $entity[ $field_name ]['raw']    = $replacement_value;
130
-                    $entity[ $field_name ]['pretty'] = ModelDataTranslator::prepareFieldValueForJson(
129
+                    $entity[$field_name]['raw']    = $replacement_value;
130
+                    $entity[$field_name]['pretty'] = ModelDataTranslator::prepareFieldValueForJson(
131 131
                         $field_obj,
132 132
                         $field_obj->prepare_for_pretty_echoing($field_obj->get_default_value()),
133 133
                         $model_version_info->requestedVersion()
@@ -138,7 +138,7 @@  discard block
 block discarded – undo
138 138
                     // so just remove it (or rather, set its default)
139 139
                     // API clients will just need to look to fields with rendered formats to know if these have
140 140
                     // been redacted. Sorry.
141
-                    $entity[ $field_name ] = $replacement_value;
141
+                    $entity[$field_name] = $replacement_value;
142 142
                 }
143 143
             }
144 144
         }
@@ -164,11 +164,11 @@  discard block
 block discarded – undo
164 164
         foreach ($model->field_settings() as $field_name => $field_obj) {
165 165
             if (
166 166
                 $model_version_info->fieldHasRenderedFormat($field_obj)
167
-                && isset($entity[ $field_name ])
168
-                && is_array($entity[ $field_name ])
169
-                && isset($entity[ $field_name ]['raw'])
167
+                && isset($entity[$field_name])
168
+                && is_array($entity[$field_name])
169
+                && isset($entity[$field_name]['raw'])
170 170
             ) {
171
-                unset($entity[ $field_name ]['raw']);
171
+                unset($entity[$field_name]['raw']);
172 172
             }
173 173
         }
174 174
         // theoretically we may want to filter out specific fields for specific models
@@ -198,7 +198,7 @@  discard block
 block discarded – undo
198 198
         string $model_action_context,
199 199
         string $action_name = 'list'
200 200
     ) {
201
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $model_action_context)) {
201
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $model_action_context)) {
202 202
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
203 203
             throw new RestException(
204 204
                 sprintf('rest_cannot_%s_%s', strtolower($action_name), $model_name_plural),
Please login to merge, or discard this patch.
core/libraries/rest_api/RestIncomingQueryParamContext.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -18,71 +18,71 @@
 block discarded – undo
18 18
  */
19 19
 class RestIncomingQueryParamContext
20 20
 {
21
-    /**
22
-     * @var EEM_Base
23
-     */
24
-    private $model;
21
+	/**
22
+	 * @var EEM_Base
23
+	 */
24
+	private $model;
25 25
 
26
-    /**
27
-     * @var string
28
-     */
29
-    private $requested_version;
26
+	/**
27
+	 * @var string
28
+	 */
29
+	private $requested_version;
30 30
 
31
-    /**
32
-     * @var boolean
33
-     */
34
-    private $writing;
31
+	/**
32
+	 * @var boolean
33
+	 */
34
+	private $writing;
35 35
 
36 36
 
37
-    /**
38
-     * RestIncomingQueryParamContext constructor.
39
-     *
40
-     * @param EEM_Base        $model
41
-     * @param string          $requested_version
42
-     * @param bool|int|string $writing
43
-     */
44
-    public function __construct(EEM_Base $model, string $requested_version, $writing)
45
-    {
46
-        $this->model             = $model;
47
-        $this->requested_version = $requested_version;
48
-        $this->writing           = filter_var($writing, FILTER_VALIDATE_BOOLEAN);
49
-    }
37
+	/**
38
+	 * RestIncomingQueryParamContext constructor.
39
+	 *
40
+	 * @param EEM_Base        $model
41
+	 * @param string          $requested_version
42
+	 * @param bool|int|string $writing
43
+	 */
44
+	public function __construct(EEM_Base $model, string $requested_version, $writing)
45
+	{
46
+		$this->model             = $model;
47
+		$this->requested_version = $requested_version;
48
+		$this->writing           = filter_var($writing, FILTER_VALIDATE_BOOLEAN);
49
+	}
50 50
 
51 51
 
52
-    /**
53
-     * Gets the model currently being requested, eg class EEM_Event
54
-     *
55
-     * @return EEM_Base
56
-     * @since 4.9.72.p
57
-     */
58
-    public function getModel(): EEM_Base
59
-    {
60
-        return $this->model;
61
-    }
52
+	/**
53
+	 * Gets the model currently being requested, eg class EEM_Event
54
+	 *
55
+	 * @return EEM_Base
56
+	 * @since 4.9.72.p
57
+	 */
58
+	public function getModel(): EEM_Base
59
+	{
60
+		return $this->model;
61
+	}
62 62
 
63 63
 
64
-    /**
65
-     * Gets the version being requested, eg 4.8.36
66
-     *
67
-     * @return string
68
-     * @since 4.9.72.p
69
-     */
70
-    public function getRequestedVersion(): string
71
-    {
72
-        return $this->requested_version;
73
-    }
64
+	/**
65
+	 * Gets the version being requested, eg 4.8.36
66
+	 *
67
+	 * @return string
68
+	 * @since 4.9.72.p
69
+	 */
70
+	public function getRequestedVersion(): string
71
+	{
72
+		return $this->requested_version;
73
+	}
74 74
 
75 75
 
76
-    /**
77
-     * Gets if the current request is for a writing request or just simple read
78
-     *
79
-     * @return bool
80
-     * @since 4.9.72.p
81
-     */
82
-    public function isWriting(): bool
83
-    {
84
-        return $this->writing;
85
-    }
76
+	/**
77
+	 * Gets if the current request is for a writing request or just simple read
78
+	 *
79
+	 * @return bool
80
+	 * @since 4.9.72.p
81
+	 */
82
+	public function isWriting(): bool
83
+	{
84
+		return $this->writing;
85
+	}
86 86
 }
87 87
 // End of file RestIncomingQueryParamContext.php
88 88
 // Location: EventEspresso\core\libraries\rest_api/RestIncomingQueryParamContext.php
Please login to merge, or discard this patch.
core/EE_System.core.php 2 patches
Indentation   +1362 added lines, -1362 removed lines patch added patch discarded remove patch
@@ -26,1366 +26,1366 @@
 block discarded – undo
26 26
  */
27 27
 final class EE_System implements ResettableInterface
28 28
 {
29
-    /**
30
-     * indicates this is a 'normal' request. Ie, not activation, nor upgrade, nor activation.
31
-     * So examples of this would be a normal GET request on the frontend or backend, or a POST, etc
32
-     */
33
-    const req_type_normal = 0;
34
-
35
-    /**
36
-     * Indicates this is a brand new installation of EE so we should install
37
-     * tables and default data etc
38
-     */
39
-    const req_type_new_activation = 1;
40
-
41
-    /**
42
-     * we've detected that EE has been reactivated (or EE was activated during maintenance mode,
43
-     * and we just exited maintenance mode). We MUST check the database is setup properly
44
-     * and that default data is setup too
45
-     */
46
-    const req_type_reactivation = 2;
47
-
48
-    /**
49
-     * indicates that EE has been upgraded since its previous request.
50
-     * We may have data migration scripts to call and will want to trigger maintenance mode
51
-     */
52
-    const req_type_upgrade = 3;
53
-
54
-    /**
55
-     * TODO  will detect that EE has been DOWNGRADED. We probably don't want to run in this case...
56
-     */
57
-    const req_type_downgrade = 4;
58
-
59
-    /**
60
-     * @deprecated since version 4.6.0.dev.006
61
-     * Now whenever a new_activation is detected the request type is still just
62
-     * new_activation (same for reactivation, upgrade, downgrade etc), but if we'r ein maintenance mode
63
-     * EE_System::initialize_db_if_no_migrations_required and EE_Addon::initialize_db_if_no_migrations_required
64
-     * will instead enqueue that EE plugin's db initialization for when we're taken out of maintenance mode.
65
-     * (Specifically, when the migration manager indicates migrations are finished
66
-     * EE_Data_Migration_Manager::initialize_db_for_enqueued_ee_plugins() will be called)
67
-     */
68
-    const req_type_activation_but_not_installed = 5;
69
-
70
-    /**
71
-     * option prefix for recording the activation history (like core's "espresso_db_update") of addons
72
-     */
73
-    const addon_activation_history_option_prefix = 'ee_addon_activation_history_';
74
-
75
-    /**
76
-     * @var EE_System $_instance
77
-     */
78
-    private static $_instance;
79
-
80
-    /**
81
-     * @var EE_Registry $registry
82
-     */
83
-    private $registry;
84
-
85
-    /**
86
-     * @var LoaderInterface $loader
87
-     */
88
-    private $loader;
89
-
90
-    /**
91
-     * @var EE_Capabilities $capabilities
92
-     */
93
-    private $capabilities;
94
-
95
-    /**
96
-     * @var RequestInterface $request
97
-     */
98
-    private $request;
99
-
100
-    /**
101
-     * @var EE_Maintenance_Mode $maintenance_mode
102
-     */
103
-    private $maintenance_mode;
104
-
105
-    /**
106
-     * Stores which type of request this is, options being one of the constants on EE_System starting with req_type_*.
107
-     * It can be a brand-new activation, a reactivation, an upgrade, a downgrade, or a normal request.
108
-     *
109
-     * @var int $_req_type
110
-     */
111
-    private $_req_type;
112
-
113
-    /**
114
-     * Whether or not there was a non-micro version change in EE core version during this request
115
-     *
116
-     * @var boolean $_major_version_change
117
-     */
118
-    private $_major_version_change = false;
119
-
120
-    /**
121
-     * A Context DTO dedicated solely to identifying the current request type.
122
-     *
123
-     * @var RequestTypeContextCheckerInterface $request_type
124
-     */
125
-    private $request_type;
126
-
127
-    /**
128
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes
129
-     */
130
-    private $register_custom_post_types;
131
-
132
-    /**
133
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies
134
-     */
135
-    private $register_custom_taxonomies;
136
-
137
-    /**
138
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms
139
-     */
140
-    private $register_custom_taxonomy_terms;
141
-
142
-    /**
143
-     * @singleton method used to instantiate class object
144
-     * @param EE_Registry|null         $registry
145
-     * @param LoaderInterface|null     $loader
146
-     * @param RequestInterface|null    $request
147
-     * @param EE_Maintenance_Mode|null $maintenance_mode
148
-     * @return EE_System
149
-     */
150
-    public static function instance(
151
-        EE_Registry $registry = null,
152
-        LoaderInterface $loader = null,
153
-        RequestInterface $request = null,
154
-        EE_Maintenance_Mode $maintenance_mode = null
155
-    ) {
156
-        // check if class object is instantiated
157
-        if (! self::$_instance instanceof EE_System) {
158
-            self::$_instance = new self($registry, $loader, $request, $maintenance_mode);
159
-        }
160
-        return self::$_instance;
161
-    }
162
-
163
-
164
-    /**
165
-     * resets the instance and returns it
166
-     *
167
-     * @return EE_System
168
-     */
169
-    public static function reset()
170
-    {
171
-        self::$_instance->_req_type = null;
172
-        // make sure none of the old hooks are left hanging around
173
-        remove_all_actions('AHEE__EE_System__perform_activations_upgrades_and_migrations');
174
-        // we need to reset the migration manager in order for it to detect DMSs properly
175
-        EE_Data_Migration_Manager::reset();
176
-        self::instance()->detect_activations_or_upgrades();
177
-        self::instance()->perform_activations_upgrades_and_migrations();
178
-        return self::instance();
179
-    }
180
-
181
-
182
-    /**
183
-     * sets hooks for running rest of system
184
-     * provides "AHEE__EE_System__construct__complete" hook for EE Addons to use as their starting point
185
-     * starting EE Addons from any other point may lead to problems
186
-     *
187
-     * @param EE_Registry         $registry
188
-     * @param LoaderInterface     $loader
189
-     * @param RequestInterface    $request
190
-     * @param EE_Maintenance_Mode $maintenance_mode
191
-     */
192
-    private function __construct(
193
-        EE_Registry $registry,
194
-        LoaderInterface $loader,
195
-        RequestInterface $request,
196
-        EE_Maintenance_Mode $maintenance_mode
197
-    ) {
198
-        $this->registry = $registry;
199
-        $this->loader = $loader;
200
-        $this->request = $request;
201
-        $this->maintenance_mode = $maintenance_mode;
202
-        do_action('AHEE__EE_System__construct__begin', $this);
203
-        add_action(
204
-            'AHEE__EE_Bootstrap__load_espresso_addons',
205
-            array($this, 'loadCapabilities'),
206
-            5
207
-        );
208
-        add_action(
209
-            'AHEE__EE_Bootstrap__load_espresso_addons',
210
-            array($this, 'loadCommandBus'),
211
-            7
212
-        );
213
-        add_action(
214
-            'AHEE__EE_Bootstrap__load_espresso_addons',
215
-            array($this, 'loadPluginApi'),
216
-            9
217
-        );
218
-        // allow addons to load first so that they can register autoloaders, set hooks for running DMS's, etc
219
-        add_action(
220
-            'AHEE__EE_Bootstrap__load_espresso_addons',
221
-            array($this, 'load_espresso_addons')
222
-        );
223
-        // when an ee addon is activated, we want to call the core hook(s) again
224
-        // because the newly-activated addon didn't get a chance to run at all
225
-        add_action('activate_plugin', array($this, 'load_espresso_addons'), 1);
226
-        // detect whether install or upgrade
227
-        add_action(
228
-            'AHEE__EE_Bootstrap__detect_activations_or_upgrades',
229
-            array($this, 'detect_activations_or_upgrades'),
230
-            3
231
-        );
232
-        // load EE_Config, EE_Textdomain, etc
233
-        add_action(
234
-            'AHEE__EE_Bootstrap__load_core_configuration',
235
-            array($this, 'load_core_configuration'),
236
-            5
237
-        );
238
-        // load specifications for matching routes to current request
239
-        add_action(
240
-            'AHEE__EE_Bootstrap__load_core_configuration',
241
-            array($this, 'loadRouteMatchSpecifications')
242
-        );
243
-        // load specifications for custom post types
244
-        add_action(
245
-            'AHEE__EE_Bootstrap__load_core_configuration',
246
-            array($this, 'loadCustomPostTypes')
247
-        );
248
-        // load EE_Config, EE_Textdomain, etc
249
-        add_action(
250
-            'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets',
251
-            array($this, 'register_shortcodes_modules_and_widgets'),
252
-            7
253
-        );
254
-        // you wanna get going? I wanna get going... let's get going!
255
-        add_action(
256
-            'AHEE__EE_Bootstrap__brew_espresso',
257
-            array($this, 'brew_espresso'),
258
-            9
259
-        );
260
-        // other housekeeping
261
-        // exclude EE critical pages from wp_list_pages
262
-        add_filter(
263
-            'wp_list_pages_excludes',
264
-            array($this, 'remove_pages_from_wp_list_pages'),
265
-            10
266
-        );
267
-        // ALL EE Addons should use the following hook point to attach their initial setup too
268
-        // it's extremely important for EE Addons to register any class autoloaders so that they can be available when the EE_Config loads
269
-        do_action('AHEE__EE_System__construct__complete', $this);
270
-    }
271
-
272
-
273
-    /**
274
-     * load and setup EE_Capabilities
275
-     *
276
-     * @return void
277
-     * @throws EE_Error
278
-     */
279
-    public function loadCapabilities()
280
-    {
281
-        $this->capabilities = $this->loader->getShared('EE_Capabilities');
282
-        add_action(
283
-            'AHEE__EE_Capabilities__init_caps__before_initialization',
284
-            function () {
285
-                LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
286
-            }
287
-        );
288
-    }
289
-
290
-
291
-    /**
292
-     * create and cache the CommandBus, and also add middleware
293
-     * The CapChecker middleware requires the use of EE_Capabilities
294
-     * which is why we need to load the CommandBus after Caps are set up
295
-     * CommandBus middleware operate FIFO - First In First Out
296
-     * so LocateMovedCommands will run first in order to return any new commands
297
-     *
298
-     * @return void
299
-     * @throws EE_Error
300
-     */
301
-    public function loadCommandBus()
302
-    {
303
-        $this->loader->getShared(
304
-            'CommandBusInterface',
305
-            array(
306
-                null,
307
-                apply_filters(
308
-                    'FHEE__EE_Load_Espresso_Core__handle_request__CommandBus_middleware',
309
-                    array(
310
-                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\LocateMovedCommands'),
311
-                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\CapChecker'),
312
-                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\AddActionHook'),
313
-                    )
314
-                ),
315
-            )
316
-        );
317
-    }
318
-
319
-
320
-    /**
321
-     * @return void
322
-     * @throws EE_Error
323
-     */
324
-    public function loadPluginApi()
325
-    {
326
-        // set autoloaders for all of the classes implementing EEI_Plugin_API
327
-        // which provide helpers for EE plugin authors to more easily register certain components with EE.
328
-        EEH_Autoloader::instance()->register_autoloaders_for_each_file_in_folder(EE_LIBRARIES . 'plugin_api');
329
-    }
330
-
331
-
332
-    /**
333
-     * @param string $addon_name
334
-     * @param string $version_constant
335
-     * @param string $min_version_required
336
-     * @param string $load_callback
337
-     * @param string $plugin_file_constant
338
-     * @return void
339
-     */
340
-    private function deactivateIncompatibleAddon(
341
-        $addon_name,
342
-        $version_constant,
343
-        $min_version_required,
344
-        $load_callback,
345
-        $plugin_file_constant
346
-    ) {
347
-        if (! defined($version_constant)) {
348
-            return;
349
-        }
350
-        $addon_version = constant($version_constant);
351
-        if ($addon_version && version_compare($addon_version, $min_version_required, '<')) {
352
-            remove_action('AHEE__EE_System__load_espresso_addons', $load_callback);
353
-            if (! function_exists('deactivate_plugins')) {
354
-                require_once ABSPATH . 'wp-admin/includes/plugin.php';
355
-            }
356
-            deactivate_plugins(plugin_basename(constant($plugin_file_constant)));
357
-            $this->request->unSetRequestParams(['activate', 'activate-multi'], true);
358
-            EE_Error::add_error(
359
-                sprintf(
360
-                    esc_html__(
361
-                        'We\'re sorry, but the Event Espresso %1$s addon was deactivated because version %2$s or higher is required with this version of Event Espresso core.',
362
-                        'event_espresso'
363
-                    ),
364
-                    $addon_name,
365
-                    $min_version_required
366
-                ),
367
-                __FILE__,
368
-                __FUNCTION__ . "({$addon_name})",
369
-                __LINE__
370
-            );
371
-            EE_Error::get_notices(false, true);
372
-        }
373
-    }
374
-
375
-
376
-    /**
377
-     * load_espresso_addons
378
-     * allow addons to load first so that they can set hooks for running DMS's, etc
379
-     * this is hooked into both:
380
-     *    'AHEE__EE_Bootstrap__load_core_configuration'
381
-     *        which runs during the WP 'plugins_loaded' action at priority 5
382
-     *    and the WP 'activate_plugin' hook point
383
-     *
384
-     * @access public
385
-     * @return void
386
-     */
387
-    public function load_espresso_addons()
388
-    {
389
-        $this->deactivateIncompatibleAddon(
390
-            'Wait Lists',
391
-            'EE_WAIT_LISTS_VERSION',
392
-            '1.0.0.beta.074',
393
-            'load_espresso_wait_lists',
394
-            'EE_WAIT_LISTS_PLUGIN_FILE'
395
-        );
396
-        $this->deactivateIncompatibleAddon(
397
-            'Automated Upcoming Event Notifications',
398
-            'EE_AUTOMATED_UPCOMING_EVENT_NOTIFICATION_VERSION',
399
-            '1.0.0.beta.091',
400
-            'load_espresso_automated_upcoming_event_notification',
401
-            'EE_AUTOMATED_UPCOMING_EVENT_NOTIFICATION_PLUGIN_FILE'
402
-        );
403
-        do_action('AHEE__EE_System__load_espresso_addons');
404
-        // if the WP API basic auth plugin isn't already loaded, load it now.
405
-        // We want it for mobile apps. Just include the entire plugin
406
-        // also, don't load the basic auth when a plugin is getting activated, because
407
-        // it could be the basic auth plugin, and it doesn't check if its methods are already defined
408
-        // and causes a fatal error
409
-        if (
410
-            ($this->request->isWordPressApi() || $this->request->isApi())
411
-            && $this->request->getRequestParam('activate') !== 'true'
412
-            && ! function_exists('json_basic_auth_handler')
413
-            && ! function_exists('json_basic_auth_error')
414
-            && ! in_array(
415
-                $this->request->getRequestParam('action'),
416
-                array('activate', 'activate-selected'),
417
-                true
418
-            )
419
-            && is_readable(EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php')
420
-        ) {
421
-            include_once EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php';
422
-        }
423
-        do_action('AHEE__EE_System__load_espresso_addons__complete');
424
-    }
425
-
426
-
427
-    /**
428
-     * detect_activations_or_upgrades
429
-     * Checks for activation or upgrade of core first;
430
-     * then also checks if any registered addons have been activated or upgraded
431
-     * This is hooked into 'AHEE__EE_Bootstrap__detect_activations_or_upgrades'
432
-     * which runs during the WP 'plugins_loaded' action at priority 3
433
-     *
434
-     * @access public
435
-     * @return void
436
-     */
437
-    public function detect_activations_or_upgrades()
438
-    {
439
-        // first off: let's make sure to handle core
440
-        $this->detect_if_activation_or_upgrade();
441
-        foreach ($this->registry->addons as $addon) {
442
-            if ($addon instanceof EE_Addon) {
443
-                // detect teh request type for that addon
444
-                $addon->detect_req_type();
445
-            }
446
-        }
447
-    }
448
-
449
-
450
-    /**
451
-     * detect_if_activation_or_upgrade
452
-     * Takes care of detecting whether this is a brand new install or code upgrade,
453
-     * and either setting up the DB or setting up maintenance mode etc.
454
-     *
455
-     * @access public
456
-     * @return void
457
-     */
458
-    public function detect_if_activation_or_upgrade()
459
-    {
460
-        do_action('AHEE__EE_System___detect_if_activation_or_upgrade__begin');
461
-        // check if db has been updated, or if its a brand-new installation
462
-        $espresso_db_update = $this->fix_espresso_db_upgrade_option();
463
-        $request_type = $this->detect_req_type($espresso_db_update);
464
-        // EEH_Debug_Tools::printr( $request_type, '$request_type', __FILE__, __LINE__ );
465
-        switch ($request_type) {
466
-            case EE_System::req_type_new_activation:
467
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__new_activation');
468
-                $this->_handle_core_version_change($espresso_db_update);
469
-                break;
470
-            case EE_System::req_type_reactivation:
471
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__reactivation');
472
-                $this->_handle_core_version_change($espresso_db_update);
473
-                break;
474
-            case EE_System::req_type_upgrade:
475
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__upgrade');
476
-                // migrations may be required now that we've upgraded
477
-                $this->maintenance_mode->set_maintenance_mode_if_db_old();
478
-                $this->_handle_core_version_change($espresso_db_update);
479
-                break;
480
-            case EE_System::req_type_downgrade:
481
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__downgrade');
482
-                // its possible migrations are no longer required
483
-                $this->maintenance_mode->set_maintenance_mode_if_db_old();
484
-                $this->_handle_core_version_change($espresso_db_update);
485
-                break;
486
-            case EE_System::req_type_normal:
487
-            default:
488
-                break;
489
-        }
490
-        do_action('AHEE__EE_System__detect_if_activation_or_upgrade__complete');
491
-    }
492
-
493
-
494
-    /**
495
-     * Updates the list of installed versions and sets hooks for
496
-     * initializing the database later during the request
497
-     *
498
-     * @param array $espresso_db_update
499
-     */
500
-    private function _handle_core_version_change($espresso_db_update)
501
-    {
502
-        $this->update_list_of_installed_versions($espresso_db_update);
503
-        // get ready to verify the DB is ok (provided we aren't in maintenance mode, of course)
504
-        add_action(
505
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
506
-            array($this, 'initialize_db_if_no_migrations_required')
507
-        );
508
-    }
509
-
510
-
511
-    /**
512
-     * standardizes the wp option 'espresso_db_upgrade' which actually stores
513
-     * information about what versions of EE have been installed and activated,
514
-     * NOT necessarily the state of the database
515
-     *
516
-     * @param mixed $espresso_db_update           the value of the WordPress option.
517
-     *                                            If not supplied, fetches it from the options table
518
-     * @return array the correct value of 'espresso_db_upgrade', after saving it, if it needed correction
519
-     */
520
-    private function fix_espresso_db_upgrade_option($espresso_db_update = null)
521
-    {
522
-        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
523
-        if (! $espresso_db_update) {
524
-            $espresso_db_update = get_option('espresso_db_update');
525
-        }
526
-        // check that option is an array
527
-        if (! is_array($espresso_db_update)) {
528
-            // if option is FALSE, then it never existed
529
-            if ($espresso_db_update === false) {
530
-                // make $espresso_db_update an array and save option with autoload OFF
531
-                $espresso_db_update = array();
532
-                add_option('espresso_db_update', $espresso_db_update, '', 'no');
533
-            } else {
534
-                // option is NOT FALSE but also is NOT an array, so make it an array and save it
535
-                $espresso_db_update = array($espresso_db_update => array());
536
-                update_option('espresso_db_update', $espresso_db_update);
537
-            }
538
-        } else {
539
-            $corrected_db_update = array();
540
-            // if IS an array, but is it an array where KEYS are version numbers, and values are arrays?
541
-            foreach ($espresso_db_update as $should_be_version_string => $should_be_array) {
542
-                if (is_int($should_be_version_string) && ! is_array($should_be_array)) {
543
-                    // the key is an int, and the value IS NOT an array
544
-                    // so it must be numerically-indexed, where values are versions installed...
545
-                    // fix it!
546
-                    $version_string = $should_be_array;
547
-                    $corrected_db_update[ $version_string ] = array('unknown-date');
548
-                } else {
549
-                    // ok it checks out
550
-                    $corrected_db_update[ $should_be_version_string ] = $should_be_array;
551
-                }
552
-            }
553
-            $espresso_db_update = $corrected_db_update;
554
-            update_option('espresso_db_update', $espresso_db_update);
555
-        }
556
-        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__complete', $espresso_db_update);
557
-        return $espresso_db_update;
558
-    }
559
-
560
-
561
-    /**
562
-     * Does the traditional work of setting up the plugin's database and adding default data.
563
-     * If migration script/process did not exist, this is what would happen on every activation/reactivation/upgrade.
564
-     * NOTE: if we're in maintenance mode (which would be the case if we detect there are data
565
-     * migration scripts that need to be run and a version change happens), enqueues core for database initialization,
566
-     * so that it will be done when migrations are finished
567
-     *
568
-     * @param boolean $initialize_addons_too if true, we double-check addons' database tables etc too;
569
-     * @param boolean $verify_schema         if true will re-check the database tables have the correct schema.
570
-     *                                       This is a resource-intensive job
571
-     *                                       so we prefer to only do it when necessary
572
-     * @return void
573
-     * @throws EE_Error
574
-     */
575
-    public function initialize_db_if_no_migrations_required($initialize_addons_too = false, $verify_schema = true)
576
-    {
577
-        $request_type = $this->detect_req_type();
578
-        // only initialize system if we're not in maintenance mode.
579
-        if ($this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
580
-            /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
581
-            $rewrite_rules = $this->loader->getShared(
582
-                'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
583
-            );
584
-            $rewrite_rules->flush();
585
-            if ($verify_schema) {
586
-                EEH_Activation::initialize_db_and_folders();
587
-            }
588
-            EEH_Activation::initialize_db_content();
589
-            EEH_Activation::system_initialization();
590
-            if ($initialize_addons_too) {
591
-                $this->initialize_addons();
592
-            }
593
-        } else {
594
-            EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for('Core');
595
-        }
596
-        if (
597
-            $request_type === EE_System::req_type_new_activation
598
-            || $request_type === EE_System::req_type_reactivation
599
-            || (
600
-                $request_type === EE_System::req_type_upgrade
601
-                && $this->is_major_version_change()
602
-            )
603
-        ) {
604
-            add_action('AHEE__EE_System__initialize_last', array($this, 'redirect_to_about_ee'), 9);
605
-        }
606
-    }
607
-
608
-
609
-    /**
610
-     * Initializes the db for all registered addons
611
-     *
612
-     * @throws EE_Error
613
-     */
614
-    public function initialize_addons()
615
-    {
616
-        // foreach registered addon, make sure its db is up-to-date too
617
-        foreach ($this->registry->addons as $addon) {
618
-            if ($addon instanceof EE_Addon) {
619
-                $addon->initialize_db_if_no_migrations_required();
620
-            }
621
-        }
622
-    }
623
-
624
-
625
-    /**
626
-     * Adds the current code version to the saved wp option which stores a list of all ee versions ever installed.
627
-     *
628
-     * @param    array  $version_history
629
-     * @param    string $current_version_to_add version to be added to the version history
630
-     * @return    boolean success as to whether or not this option was changed
631
-     */
632
-    public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null)
633
-    {
634
-        if (! $version_history) {
635
-            $version_history = $this->fix_espresso_db_upgrade_option($version_history);
636
-        }
637
-        if ($current_version_to_add === null) {
638
-            $current_version_to_add = espresso_version();
639
-        }
640
-        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
641
-        // re-save
642
-        return update_option('espresso_db_update', $version_history);
643
-    }
644
-
645
-
646
-    /**
647
-     * Detects if the current version indicated in the has existed in the list of
648
-     * previously-installed versions of EE (espresso_db_update). Does NOT modify it (ie, no side-effect)
649
-     *
650
-     * @param array $espresso_db_update array from the wp option stored under the name 'espresso_db_update'.
651
-     *                                  If not supplied, fetches it from the options table.
652
-     *                                  Also, caches its result so later parts of the code can also know whether
653
-     *                                  there's been an update or not. This way we can add the current version to
654
-     *                                  espresso_db_update, but still know if this is a new install or not
655
-     * @return int one of the constants on EE_System::req_type_
656
-     */
657
-    public function detect_req_type($espresso_db_update = null)
658
-    {
659
-        if ($this->_req_type === null) {
660
-            $espresso_db_update = ! empty($espresso_db_update)
661
-                ? $espresso_db_update
662
-                : $this->fix_espresso_db_upgrade_option();
663
-            $this->_req_type = EE_System::detect_req_type_given_activation_history(
664
-                $espresso_db_update,
665
-                'ee_espresso_activation',
666
-                espresso_version()
667
-            );
668
-            $this->_major_version_change = $this->_detect_major_version_change($espresso_db_update);
669
-            $this->request->setIsActivation($this->_req_type !== EE_System::req_type_normal);
670
-        }
671
-        return $this->_req_type;
672
-    }
673
-
674
-
675
-    /**
676
-     * Returns whether or not there was a non-micro version change (ie, change in either
677
-     * the first or second number in the version. Eg 4.9.0.rc.001 to 4.10.0.rc.000,
678
-     * but not 4.9.0.rc.0001 to 4.9.1.rc.0001
679
-     *
680
-     * @param $activation_history
681
-     * @return bool
682
-     */
683
-    private function _detect_major_version_change($activation_history)
684
-    {
685
-        $previous_version = EE_System::_get_most_recently_active_version_from_activation_history($activation_history);
686
-        $previous_version_parts = explode('.', $previous_version);
687
-        $current_version_parts = explode('.', espresso_version());
688
-        return isset($previous_version_parts[0], $previous_version_parts[1], $current_version_parts[0], $current_version_parts[1])
689
-               && ($previous_version_parts[0] !== $current_version_parts[0]
690
-                   || $previous_version_parts[1] !== $current_version_parts[1]
691
-               );
692
-    }
693
-
694
-
695
-    /**
696
-     * Returns true if either the major or minor version of EE changed during this request.
697
-     * Eg 4.9.0.rc.001 to 4.10.0.rc.000, but not 4.9.0.rc.0001 to 4.9.1.rc.0001
698
-     *
699
-     * @return bool
700
-     */
701
-    public function is_major_version_change()
702
-    {
703
-        return $this->_major_version_change;
704
-    }
705
-
706
-
707
-    /**
708
-     * Determines the request type for any ee addon, given three piece of info: the current array of activation
709
-     * histories (for core that' 'espresso_db_update' wp option); the name of the WordPress option which is temporarily
710
-     * set upon activation of the plugin (for core it's 'ee_espresso_activation'); and the version that this plugin was
711
-     * just activated to (for core that will always be espresso_version())
712
-     *
713
-     * @param array  $activation_history_for_addon     the option's value which stores the activation history for this
714
-     *                                                 ee plugin. for core that's 'espresso_db_update'
715
-     * @param string $activation_indicator_option_name the name of the WordPress option that is temporarily set to
716
-     *                                                 indicate that this plugin was just activated
717
-     * @param string $version_to_upgrade_to            the version that was just upgraded to (for core that will be
718
-     *                                                 espresso_version())
719
-     * @return int one of the constants on EE_System::req_type_*
720
-     */
721
-    public static function detect_req_type_given_activation_history(
722
-        $activation_history_for_addon,
723
-        $activation_indicator_option_name,
724
-        $version_to_upgrade_to
725
-    ) {
726
-        $version_is_higher = self::_new_version_is_higher($activation_history_for_addon, $version_to_upgrade_to);
727
-        if ($activation_history_for_addon) {
728
-            // it exists, so this isn't a completely new install
729
-            // check if this version already in that list of previously installed versions
730
-            if (! isset($activation_history_for_addon[ $version_to_upgrade_to ])) {
731
-                // it a version we haven't seen before
732
-                if ($version_is_higher === 1) {
733
-                    $req_type = EE_System::req_type_upgrade;
734
-                } else {
735
-                    $req_type = EE_System::req_type_downgrade;
736
-                }
737
-                delete_option($activation_indicator_option_name);
738
-            } else {
739
-                // its not an update. maybe a reactivation?
740
-                if (get_option($activation_indicator_option_name, false)) {
741
-                    if ($version_is_higher === -1) {
742
-                        $req_type = EE_System::req_type_downgrade;
743
-                    } elseif ($version_is_higher === 0) {
744
-                        // we've seen this version before, but it's an activation. must be a reactivation
745
-                        $req_type = EE_System::req_type_reactivation;
746
-                    } else {// $version_is_higher === 1
747
-                        $req_type = EE_System::req_type_upgrade;
748
-                    }
749
-                    delete_option($activation_indicator_option_name);
750
-                } else {
751
-                    // we've seen this version before and the activation indicate doesn't show it was just activated
752
-                    if ($version_is_higher === -1) {
753
-                        $req_type = EE_System::req_type_downgrade;
754
-                    } elseif ($version_is_higher === 0) {
755
-                        // we've seen this version before and it's not an activation. its normal request
756
-                        $req_type = EE_System::req_type_normal;
757
-                    } else {// $version_is_higher === 1
758
-                        $req_type = EE_System::req_type_upgrade;
759
-                    }
760
-                }
761
-            }
762
-        } else {
763
-            // brand new install
764
-            $req_type = EE_System::req_type_new_activation;
765
-            delete_option($activation_indicator_option_name);
766
-        }
767
-        return $req_type;
768
-    }
769
-
770
-
771
-    /**
772
-     * Detects if the $version_to_upgrade_to is higher than the most recent version in
773
-     * the $activation_history_for_addon
774
-     *
775
-     * @param array  $activation_history_for_addon (keys are versions, values are arrays of times activated,
776
-     *                                             sometimes containing 'unknown-date'
777
-     * @param string $version_to_upgrade_to        (current version)
778
-     * @return int results of version_compare( $version_to_upgrade_to, $most_recently_active_version ).
779
-     *                                             ie, -1 if $version_to_upgrade_to is LOWER (downgrade);
780
-     *                                             0 if $version_to_upgrade_to MATCHES (reactivation or normal request);
781
-     *                                             1 if $version_to_upgrade_to is HIGHER (upgrade) ;
782
-     */
783
-    private static function _new_version_is_higher($activation_history_for_addon, $version_to_upgrade_to)
784
-    {
785
-        // find the most recently-activated version
786
-        $most_recently_active_version =
787
-            EE_System::_get_most_recently_active_version_from_activation_history($activation_history_for_addon);
788
-        return version_compare($version_to_upgrade_to, $most_recently_active_version);
789
-    }
790
-
791
-
792
-    /**
793
-     * Gets the most recently active version listed in the activation history,
794
-     * and if none are found (ie, it's a brand new install) returns '0.0.0.dev.000'.
795
-     *
796
-     * @param array $activation_history  (keys are versions, values are arrays of times activated,
797
-     *                                   sometimes containing 'unknown-date'
798
-     * @return string
799
-     */
800
-    private static function _get_most_recently_active_version_from_activation_history($activation_history)
801
-    {
802
-        $most_recently_active_version_activation = '1970-01-01 00:00:00';
803
-        $most_recently_active_version = '0.0.0.dev.000';
804
-        if (is_array($activation_history)) {
805
-            foreach ($activation_history as $version => $times_activated) {
806
-                // check there is a record of when this version was activated. Otherwise,
807
-                // mark it as unknown
808
-                if (! $times_activated) {
809
-                    $times_activated = array('unknown-date');
810
-                }
811
-                if (is_string($times_activated)) {
812
-                    $times_activated = array($times_activated);
813
-                }
814
-                foreach ($times_activated as $an_activation) {
815
-                    if (
816
-                        $an_activation !== 'unknown-date'
817
-                        && $an_activation
818
-                           > $most_recently_active_version_activation
819
-                    ) {
820
-                        $most_recently_active_version = $version;
821
-                        $most_recently_active_version_activation = $an_activation === 'unknown-date'
822
-                            ? '1970-01-01 00:00:00'
823
-                            : $an_activation;
824
-                    }
825
-                }
826
-            }
827
-        }
828
-        return $most_recently_active_version;
829
-    }
830
-
831
-
832
-    /**
833
-     * This redirects to the about EE page after activation
834
-     *
835
-     * @return void
836
-     */
837
-    public function redirect_to_about_ee()
838
-    {
839
-        $notices = EE_Error::get_notices(false);
840
-        // if current user is an admin and it's not an ajax or rest request
841
-        if (
842
-            ! isset($notices['errors'])
843
-            && $this->request->isAdmin()
844
-            && apply_filters(
845
-                'FHEE__EE_System__redirect_to_about_ee__do_redirect',
846
-                $this->capabilities->current_user_can('manage_options', 'espresso_about_default')
847
-            )
848
-        ) {
849
-            $query_params = array('page' => 'espresso_about');
850
-            if (EE_System::instance()->detect_req_type() === EE_System::req_type_new_activation) {
851
-                $query_params['new_activation'] = true;
852
-            }
853
-            if (EE_System::instance()->detect_req_type() === EE_System::req_type_reactivation) {
854
-                $query_params['reactivation'] = true;
855
-            }
856
-            $url = add_query_arg($query_params, admin_url('admin.php'));
857
-            wp_safe_redirect($url);
858
-            exit();
859
-        }
860
-    }
861
-
862
-
863
-    /**
864
-     * load_core_configuration
865
-     * this is hooked into 'AHEE__EE_Bootstrap__load_core_configuration'
866
-     * which runs during the WP 'plugins_loaded' action at priority 5
867
-     *
868
-     * @return void
869
-     * @throws ReflectionException
870
-     * @throws Exception
871
-     */
872
-    public function load_core_configuration()
873
-    {
874
-        do_action('AHEE__EE_System__load_core_configuration__begin', $this);
875
-        $this->loader->getShared('EE_Load_Textdomain');
876
-        // load textdomain
877
-        EE_Load_Textdomain::load_textdomain();
878
-        // load caf stuff a chance to play during the activation process too.
879
-        $this->_maybe_brew_regular();
880
-        // load and setup EE_Config and EE_Network_Config
881
-        $config = $this->loader->getShared('EE_Config');
882
-        $this->loader->getShared('EE_Network_Config');
883
-        // setup autoloaders
884
-        // enable logging?
885
-        $this->loader->getShared('EventEspresso\core\services\orm\TrashLogger');
886
-        if ($config->admin->use_remote_logging) {
887
-            $this->loader->getShared('EE_Log');
888
-        }
889
-        // check for activation errors
890
-        $activation_errors = get_option('ee_plugin_activation_errors', false);
891
-        if ($activation_errors) {
892
-            EE_Error::add_error($activation_errors, __FILE__, __FUNCTION__, __LINE__);
893
-            update_option('ee_plugin_activation_errors', false);
894
-        }
895
-        // get model names
896
-        $this->_parse_model_names();
897
-        // configure custom post type definitions
898
-        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions');
899
-        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions');
900
-        do_action('AHEE__EE_System__load_core_configuration__complete', $this);
901
-    }
902
-
903
-
904
-    /**
905
-     * cycles through all of the models/*.model.php files, and assembles an array of model names
906
-     *
907
-     * @return void
908
-     * @throws ReflectionException
909
-     */
910
-    private function _parse_model_names()
911
-    {
912
-        // get all the files in the EE_MODELS folder that end in .model.php
913
-        $models = glob(EE_MODELS . '*.model.php');
914
-        $model_names = array();
915
-        $non_abstract_db_models = array();
916
-        foreach ($models as $model) {
917
-            // get model classname
918
-            $classname = EEH_File::get_classname_from_filepath_with_standard_filename($model);
919
-            $short_name = str_replace('EEM_', '', $classname);
920
-            $reflectionClass = new ReflectionClass($classname);
921
-            if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
922
-                $non_abstract_db_models[ $short_name ] = $classname;
923
-            }
924
-            $model_names[ $short_name ] = $classname;
925
-        }
926
-        $this->registry->models = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
927
-        $this->registry->non_abstract_db_models = apply_filters(
928
-            'FHEE__EE_System__parse_implemented_model_names',
929
-            $non_abstract_db_models
930
-        );
931
-    }
932
-
933
-
934
-    /**
935
-     * The purpose of this method is to simply check for a file named "caffeinated/brewing_regular.php" for any hooks
936
-     * that need to be setup before our EE_System launches.
937
-     *
938
-     * @return void
939
-     * @throws DomainException
940
-     * @throws InvalidArgumentException
941
-     * @throws InvalidDataTypeException
942
-     * @throws InvalidInterfaceException
943
-     * @throws InvalidClassException
944
-     * @throws InvalidFilePathException
945
-     */
946
-    private function _maybe_brew_regular()
947
-    {
948
-        /** @var Domain $domain */
949
-        $domain = DomainFactory::getShared(
950
-            new FullyQualifiedName(
951
-                'EventEspresso\core\domain\Domain'
952
-            ),
953
-            array(
954
-                new FilePath(EVENT_ESPRESSO_MAIN_FILE),
955
-                Version::fromString(espresso_version()),
956
-            )
957
-        );
958
-        if ($domain->isCaffeinated()) {
959
-            require_once EE_CAFF_PATH . 'brewing_regular.php';
960
-        }
961
-    }
962
-
963
-
964
-    /**
965
-     * @since 4.9.71.p
966
-     * @throws Exception
967
-     */
968
-    public function loadRouteMatchSpecifications()
969
-    {
970
-        try {
971
-            $this->loader->getShared(
972
-                'EventEspresso\core\services\route_match\RouteMatchSpecificationManager'
973
-            );
974
-        } catch (Exception $exception) {
975
-            new ExceptionStackTraceDisplay($exception);
976
-        }
977
-        do_action('AHEE__EE_System__loadRouteMatchSpecifications');
978
-    }
979
-
980
-
981
-    /**
982
-     * loading CPT related classes earlier so that their definitions are available
983
-     * but not performing any actual registration with WP core until load_CPTs_and_session() is called
984
-     *
985
-     * @since   4.10.21.p
986
-     */
987
-    public function loadCustomPostTypes()
988
-    {
989
-        $this->register_custom_taxonomies = $this->loader->getShared(
990
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'
991
-        );
992
-        $this->register_custom_post_types = $this->loader->getShared(
993
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'
994
-        );
995
-        $this->register_custom_taxonomy_terms = $this->loader->getShared(
996
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms'
997
-        );
998
-        // integrate WP_Query with the EE models
999
-        $this->loader->getShared('EE_CPT_Strategy');
1000
-        // load legacy EE_Request_Handler in case add-ons still need it
1001
-        $this->loader->getShared('EE_Request_Handler');
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * register_shortcodes_modules_and_widgets
1007
-     * generate lists of shortcodes and modules, then verify paths and classes
1008
-     * This is hooked into 'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets'
1009
-     * which runs during the WP 'plugins_loaded' action at priority 7
1010
-     *
1011
-     * @access public
1012
-     * @return void
1013
-     * @throws Exception
1014
-     */
1015
-    public function register_shortcodes_modules_and_widgets()
1016
-    {
1017
-        if ($this->request->isFrontend() || $this->request->isIframe() || $this->request->isAjax()) {
1018
-            // load, register, and add shortcodes the new way
1019
-            $this->loader->getShared('EventEspresso\core\services\shortcodes\ShortcodesManager');
1020
-        }
1021
-        do_action('AHEE__EE_System__register_shortcodes_modules_and_widgets');
1022
-        // check for addons using old hook point
1023
-        if (has_action('AHEE__EE_System__register_shortcodes_modules_and_addons')) {
1024
-            $this->_incompatible_addon_error();
1025
-        }
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * _incompatible_addon_error
1031
-     *
1032
-     * @access public
1033
-     * @return void
1034
-     */
1035
-    private function _incompatible_addon_error()
1036
-    {
1037
-        // get array of classes hooking into here
1038
-        $class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
1039
-            'AHEE__EE_System__register_shortcodes_modules_and_addons'
1040
-        );
1041
-        if (! empty($class_names)) {
1042
-            $msg = esc_html__(
1043
-                'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
1044
-                'event_espresso'
1045
-            );
1046
-            $msg .= '<ul>';
1047
-            foreach ($class_names as $class_name) {
1048
-                $msg .= '<li><b>Event Espresso - '
1049
-                        . str_replace(
1050
-                            array('EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'),
1051
-                            '',
1052
-                            $class_name
1053
-                        ) . '</b></li>';
1054
-            }
1055
-            $msg .= '</ul>';
1056
-            $msg .= esc_html__(
1057
-                'Compatibility issues can be avoided and/or resolved by keeping addons and plugins updated to the latest version.',
1058
-                'event_espresso'
1059
-            );
1060
-            // save list of incompatible addons to wp-options for later use
1061
-            add_option('ee_incompatible_addons', $class_names, '', 'no');
1062
-            if (is_admin()) {
1063
-                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1064
-            }
1065
-        }
1066
-    }
1067
-
1068
-
1069
-    /**
1070
-     * brew_espresso
1071
-     * begins the process of setting hooks for initializing EE in the correct order
1072
-     * This is happening on the 'AHEE__EE_Bootstrap__brew_espresso' hook point
1073
-     * which runs during the WP 'plugins_loaded' action at priority 9
1074
-     *
1075
-     * @return void
1076
-     */
1077
-    public function brew_espresso()
1078
-    {
1079
-        do_action('AHEE__EE_System__brew_espresso__begin', $this);
1080
-        // load some final core systems
1081
-        add_action('init', array($this, 'set_hooks_for_core'), 1);
1082
-        add_action('init', array($this, 'perform_activations_upgrades_and_migrations'), 3);
1083
-        add_action('init', array($this, 'load_CPTs_and_session'), 5);
1084
-        add_action('init', array($this, 'load_controllers'), 7);
1085
-        add_action('init', array($this, 'core_loaded_and_ready'), 9);
1086
-        add_action('init', array($this, 'initialize'), 10);
1087
-        add_action('init', array($this, 'initialize_last'), 100);
1088
-        if (is_admin() && apply_filters('FHEE__EE_System__brew_espresso__load_pue', true)) {
1089
-            // pew pew pew
1090
-            $this->loader->getShared('EventEspresso\core\services\licensing\LicenseService');
1091
-            do_action('AHEE__EE_System__brew_espresso__after_pue_init');
1092
-        }
1093
-        do_action('AHEE__EE_System__brew_espresso__complete', $this);
1094
-    }
1095
-
1096
-
1097
-    /**
1098
-     *    set_hooks_for_core
1099
-     *
1100
-     * @access public
1101
-     * @return    void
1102
-     * @throws EE_Error
1103
-     */
1104
-    public function set_hooks_for_core()
1105
-    {
1106
-        $this->_deactivate_incompatible_addons();
1107
-        do_action('AHEE__EE_System__set_hooks_for_core');
1108
-        $this->loader->getShared('EventEspresso\core\domain\values\session\SessionLifespan');
1109
-        // caps need to be initialized on every request so that capability maps are set.
1110
-        // @see https://events.codebasehq.com/projects/event-espresso/tickets/8674
1111
-        $this->registry->CAP->init_caps();
1112
-    }
1113
-
1114
-
1115
-    /**
1116
-     * Using the information gathered in EE_System::_incompatible_addon_error,
1117
-     * deactivates any addons considered incompatible with the current version of EE
1118
-     */
1119
-    private function _deactivate_incompatible_addons()
1120
-    {
1121
-        $incompatible_addons = get_option('ee_incompatible_addons', array());
1122
-        if (! empty($incompatible_addons)) {
1123
-            $active_plugins = get_option('active_plugins', array());
1124
-            foreach ($active_plugins as $active_plugin) {
1125
-                foreach ($incompatible_addons as $incompatible_addon) {
1126
-                    if (strpos($active_plugin, $incompatible_addon) !== false) {
1127
-                        $this->request->unSetRequestParams(['activate'], true);
1128
-                        espresso_deactivate_plugin($active_plugin);
1129
-                    }
1130
-                }
1131
-            }
1132
-        }
1133
-    }
1134
-
1135
-
1136
-    /**
1137
-     *    perform_activations_upgrades_and_migrations
1138
-     *
1139
-     * @access public
1140
-     * @return    void
1141
-     */
1142
-    public function perform_activations_upgrades_and_migrations()
1143
-    {
1144
-        do_action('AHEE__EE_System__perform_activations_upgrades_and_migrations');
1145
-    }
1146
-
1147
-
1148
-    /**
1149
-     * @return void
1150
-     * @throws DomainException
1151
-     */
1152
-    public function load_CPTs_and_session()
1153
-    {
1154
-        do_action('AHEE__EE_System__load_CPTs_and_session__start');
1155
-        $this->register_custom_taxonomies->registerCustomTaxonomies();
1156
-        $this->register_custom_post_types->registerCustomPostTypes();
1157
-        $this->register_custom_taxonomy_terms->registerCustomTaxonomyTerms();
1158
-        // load legacy Custom Post Types and Taxonomies
1159
-        $this->loader->getShared('EE_Register_CPTs');
1160
-        do_action('AHEE__EE_System__load_CPTs_and_session__complete');
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * load_controllers
1166
-     * this is the best place to load any additional controllers that needs access to EE core.
1167
-     * it is expected that all basic core EE systems, that are not dependant on the current request are loaded at this
1168
-     * time
1169
-     *
1170
-     * @access public
1171
-     * @return void
1172
-     */
1173
-    public function load_controllers()
1174
-    {
1175
-        do_action('AHEE__EE_System__load_controllers__start');
1176
-        // let's get it started
1177
-        if (
1178
-            ! $this->maintenance_mode->level()
1179
-            && ($this->request->isFrontend() || $this->request->isFrontAjax())
1180
-        ) {
1181
-            do_action('AHEE__EE_System__load_controllers__load_front_controllers');
1182
-            $this->loader->getShared('EE_Front_Controller');
1183
-        } elseif ($this->request->isAdmin() || $this->request->isAdminAjax()) {
1184
-            do_action('AHEE__EE_System__load_controllers__load_admin_controllers');
1185
-            $this->loader->getShared('EE_Admin');
1186
-            $this->loader->getShared(
1187
-                'EventEspresso\core\services\activation\plugin_prompt\DownloadPluginPromptManager'
1188
-            );
1189
-        } elseif ($this->request->isWordPressHeartbeat()) {
1190
-            $this->loader->getShared('EventEspresso\core\domain\services\admin\ajax\WordpressHeartbeat');
1191
-        }
1192
-        do_action('AHEE__EE_System__load_controllers__complete');
1193
-    }
1194
-
1195
-
1196
-    /**
1197
-     * core_loaded_and_ready
1198
-     * all of the basic EE core should be loaded at this point and available regardless of M-Mode
1199
-     *
1200
-     * @access public
1201
-     * @return void
1202
-     * @throws Exception
1203
-     */
1204
-    public function core_loaded_and_ready()
1205
-    {
1206
-        if (
1207
-            $this->request->isAdmin()
1208
-            || $this->request->isFrontend()
1209
-            || $this->request->isIframe()
1210
-            || $this->request->isWordPressApi()
1211
-        ) {
1212
-            try {
1213
-                $this->loader->getShared('EventEspresso\core\services\assets\I18nRegistry', [[]]);
1214
-                $this->loader->getShared('EventEspresso\core\services\assets\Registry');
1215
-                $this->loader->getShared('EventEspresso\core\domain\services\assets\CoreAssetManager');
1216
-                if ($this->canLoadBlocks()) {
1217
-                    $this->loader->getShared(
1218
-                        'EventEspresso\core\services\editor\BlockRegistrationManager'
1219
-                    );
1220
-                }
1221
-            } catch (Exception $exception) {
1222
-                new ExceptionStackTraceDisplay($exception);
1223
-            }
1224
-        }
1225
-        if (
1226
-            $this->request->isAdmin()
1227
-            || $this->request->isEeAjax()
1228
-            || $this->request->isFrontend()
1229
-        ) {
1230
-            $this->loader->getShared('EE_Session');
1231
-        }
1232
-        do_action('AHEE__EE_System__core_loaded_and_ready');
1233
-        // always load template tags, because it's faster than checking if it's a front-end request, and many page
1234
-        // builders require these even on the front-end
1235
-        require_once EE_PUBLIC . 'template_tags.php';
1236
-        do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * initialize
1242
-     * this is the best place to begin initializing client code
1243
-     *
1244
-     * @access public
1245
-     * @return void
1246
-     */
1247
-    public function initialize()
1248
-    {
1249
-        do_action('AHEE__EE_System__initialize');
1250
-        add_filter(
1251
-            'safe_style_css',
1252
-            function ($styles) {
1253
-                $styles[] = 'display';
1254
-                $styles[] = 'visibility';
1255
-                $styles[] = 'position';
1256
-                $styles[] = 'top';
1257
-                $styles[] = 'right';
1258
-                $styles[] = 'bottom';
1259
-                $styles[] = 'left';
1260
-                $styles[] = 'resize';
1261
-                $styles[] = 'max-width';
1262
-                $styles[] = 'max-height';
1263
-                return $styles;
1264
-            }
1265
-        );
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * initialize_last
1271
-     * this is run really late during the WP init hook point, and ensures that mostly everything else that needs to
1272
-     * initialize has done so
1273
-     *
1274
-     * @access public
1275
-     * @return void
1276
-     */
1277
-    public function initialize_last()
1278
-    {
1279
-        do_action('AHEE__EE_System__initialize_last');
1280
-        /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
1281
-        $rewrite_rules = $this->loader->getShared(
1282
-            'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
1283
-        );
1284
-        $rewrite_rules->flushRewriteRules();
1285
-        add_action('admin_bar_init', array($this, 'addEspressoToolbar'));
1286
-        if (
1287
-            ($this->request->isAjax() || $this->request->isAdmin())
1288
-            && $this->maintenance_mode->models_can_query()
1289
-        ) {
1290
-            $this->loader->getShared('EventEspresso\core\services\privacy\export\PersonalDataExporterManager');
1291
-            $this->loader->getShared('EventEspresso\core\services\privacy\erasure\PersonalDataEraserManager');
1292
-        }
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * @return void
1298
-     * @throws EE_Error
1299
-     */
1300
-    public function addEspressoToolbar()
1301
-    {
1302
-        $this->loader->getShared(
1303
-            'EventEspresso\core\domain\services\admin\AdminToolBar',
1304
-            array($this->registry->CAP)
1305
-        );
1306
-    }
1307
-
1308
-
1309
-    /**
1310
-     * do_not_cache
1311
-     * sets no cache headers and defines no cache constants for WP plugins
1312
-     *
1313
-     * @access public
1314
-     * @return void
1315
-     */
1316
-    public static function do_not_cache()
1317
-    {
1318
-        // set no cache constants
1319
-        if (! defined('DONOTCACHEPAGE')) {
1320
-            define('DONOTCACHEPAGE', true);
1321
-        }
1322
-        if (! defined('DONOTCACHCEOBJECT')) {
1323
-            define('DONOTCACHCEOBJECT', true);
1324
-        }
1325
-        if (! defined('DONOTCACHEDB')) {
1326
-            define('DONOTCACHEDB', true);
1327
-        }
1328
-        // add no cache headers
1329
-        add_action('send_headers', array('EE_System', 'nocache_headers'), 10);
1330
-        // plus a little extra for nginx and Google Chrome
1331
-        add_filter('nocache_headers', array('EE_System', 'extra_nocache_headers'), 10, 1);
1332
-        // prevent browsers from prefetching of the rel='next' link, because it may contain content that interferes with the registration process
1333
-        remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     *    extra_nocache_headers
1339
-     *
1340
-     * @access    public
1341
-     * @param $headers
1342
-     * @return    array
1343
-     */
1344
-    public static function extra_nocache_headers($headers)
1345
-    {
1346
-        // for NGINX
1347
-        $headers['X-Accel-Expires'] = 0;
1348
-        // plus extra for Google Chrome since it doesn't seem to respect "no-cache", but WILL respect "no-store"
1349
-        $headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
1350
-        return $headers;
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     *    nocache_headers
1356
-     *
1357
-     * @access    public
1358
-     * @return    void
1359
-     */
1360
-    public static function nocache_headers()
1361
-    {
1362
-        nocache_headers();
1363
-    }
1364
-
1365
-
1366
-    /**
1367
-     * simply hooks into "wp_list_pages_exclude" filter (for wp_list_pages method) and makes sure EE critical pages are
1368
-     * never returned with the function.
1369
-     *
1370
-     * @param  array $exclude_array any existing pages being excluded are in this array.
1371
-     * @return array
1372
-     */
1373
-    public function remove_pages_from_wp_list_pages($exclude_array)
1374
-    {
1375
-        return array_merge($exclude_array, $this->registry->CFG->core->get_critical_pages_array());
1376
-    }
1377
-
1378
-
1379
-    /**
1380
-     * Return whether blocks can be registered/loaded or not.
1381
-     * @return bool
1382
-     */
1383
-    private function canLoadBlocks()
1384
-    {
1385
-        return apply_filters('FHEE__EE_System__canLoadBlocks', true)
1386
-               && function_exists('register_block_type')
1387
-               // don't load blocks if in the Divi page builder editor context
1388
-               // @see https://github.com/eventespresso/event-espresso-core/issues/814
1389
-               && ! $this->request->getRequestParam('et_fb', false);
1390
-    }
29
+	/**
30
+	 * indicates this is a 'normal' request. Ie, not activation, nor upgrade, nor activation.
31
+	 * So examples of this would be a normal GET request on the frontend or backend, or a POST, etc
32
+	 */
33
+	const req_type_normal = 0;
34
+
35
+	/**
36
+	 * Indicates this is a brand new installation of EE so we should install
37
+	 * tables and default data etc
38
+	 */
39
+	const req_type_new_activation = 1;
40
+
41
+	/**
42
+	 * we've detected that EE has been reactivated (or EE was activated during maintenance mode,
43
+	 * and we just exited maintenance mode). We MUST check the database is setup properly
44
+	 * and that default data is setup too
45
+	 */
46
+	const req_type_reactivation = 2;
47
+
48
+	/**
49
+	 * indicates that EE has been upgraded since its previous request.
50
+	 * We may have data migration scripts to call and will want to trigger maintenance mode
51
+	 */
52
+	const req_type_upgrade = 3;
53
+
54
+	/**
55
+	 * TODO  will detect that EE has been DOWNGRADED. We probably don't want to run in this case...
56
+	 */
57
+	const req_type_downgrade = 4;
58
+
59
+	/**
60
+	 * @deprecated since version 4.6.0.dev.006
61
+	 * Now whenever a new_activation is detected the request type is still just
62
+	 * new_activation (same for reactivation, upgrade, downgrade etc), but if we'r ein maintenance mode
63
+	 * EE_System::initialize_db_if_no_migrations_required and EE_Addon::initialize_db_if_no_migrations_required
64
+	 * will instead enqueue that EE plugin's db initialization for when we're taken out of maintenance mode.
65
+	 * (Specifically, when the migration manager indicates migrations are finished
66
+	 * EE_Data_Migration_Manager::initialize_db_for_enqueued_ee_plugins() will be called)
67
+	 */
68
+	const req_type_activation_but_not_installed = 5;
69
+
70
+	/**
71
+	 * option prefix for recording the activation history (like core's "espresso_db_update") of addons
72
+	 */
73
+	const addon_activation_history_option_prefix = 'ee_addon_activation_history_';
74
+
75
+	/**
76
+	 * @var EE_System $_instance
77
+	 */
78
+	private static $_instance;
79
+
80
+	/**
81
+	 * @var EE_Registry $registry
82
+	 */
83
+	private $registry;
84
+
85
+	/**
86
+	 * @var LoaderInterface $loader
87
+	 */
88
+	private $loader;
89
+
90
+	/**
91
+	 * @var EE_Capabilities $capabilities
92
+	 */
93
+	private $capabilities;
94
+
95
+	/**
96
+	 * @var RequestInterface $request
97
+	 */
98
+	private $request;
99
+
100
+	/**
101
+	 * @var EE_Maintenance_Mode $maintenance_mode
102
+	 */
103
+	private $maintenance_mode;
104
+
105
+	/**
106
+	 * Stores which type of request this is, options being one of the constants on EE_System starting with req_type_*.
107
+	 * It can be a brand-new activation, a reactivation, an upgrade, a downgrade, or a normal request.
108
+	 *
109
+	 * @var int $_req_type
110
+	 */
111
+	private $_req_type;
112
+
113
+	/**
114
+	 * Whether or not there was a non-micro version change in EE core version during this request
115
+	 *
116
+	 * @var boolean $_major_version_change
117
+	 */
118
+	private $_major_version_change = false;
119
+
120
+	/**
121
+	 * A Context DTO dedicated solely to identifying the current request type.
122
+	 *
123
+	 * @var RequestTypeContextCheckerInterface $request_type
124
+	 */
125
+	private $request_type;
126
+
127
+	/**
128
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes
129
+	 */
130
+	private $register_custom_post_types;
131
+
132
+	/**
133
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies
134
+	 */
135
+	private $register_custom_taxonomies;
136
+
137
+	/**
138
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms
139
+	 */
140
+	private $register_custom_taxonomy_terms;
141
+
142
+	/**
143
+	 * @singleton method used to instantiate class object
144
+	 * @param EE_Registry|null         $registry
145
+	 * @param LoaderInterface|null     $loader
146
+	 * @param RequestInterface|null    $request
147
+	 * @param EE_Maintenance_Mode|null $maintenance_mode
148
+	 * @return EE_System
149
+	 */
150
+	public static function instance(
151
+		EE_Registry $registry = null,
152
+		LoaderInterface $loader = null,
153
+		RequestInterface $request = null,
154
+		EE_Maintenance_Mode $maintenance_mode = null
155
+	) {
156
+		// check if class object is instantiated
157
+		if (! self::$_instance instanceof EE_System) {
158
+			self::$_instance = new self($registry, $loader, $request, $maintenance_mode);
159
+		}
160
+		return self::$_instance;
161
+	}
162
+
163
+
164
+	/**
165
+	 * resets the instance and returns it
166
+	 *
167
+	 * @return EE_System
168
+	 */
169
+	public static function reset()
170
+	{
171
+		self::$_instance->_req_type = null;
172
+		// make sure none of the old hooks are left hanging around
173
+		remove_all_actions('AHEE__EE_System__perform_activations_upgrades_and_migrations');
174
+		// we need to reset the migration manager in order for it to detect DMSs properly
175
+		EE_Data_Migration_Manager::reset();
176
+		self::instance()->detect_activations_or_upgrades();
177
+		self::instance()->perform_activations_upgrades_and_migrations();
178
+		return self::instance();
179
+	}
180
+
181
+
182
+	/**
183
+	 * sets hooks for running rest of system
184
+	 * provides "AHEE__EE_System__construct__complete" hook for EE Addons to use as their starting point
185
+	 * starting EE Addons from any other point may lead to problems
186
+	 *
187
+	 * @param EE_Registry         $registry
188
+	 * @param LoaderInterface     $loader
189
+	 * @param RequestInterface    $request
190
+	 * @param EE_Maintenance_Mode $maintenance_mode
191
+	 */
192
+	private function __construct(
193
+		EE_Registry $registry,
194
+		LoaderInterface $loader,
195
+		RequestInterface $request,
196
+		EE_Maintenance_Mode $maintenance_mode
197
+	) {
198
+		$this->registry = $registry;
199
+		$this->loader = $loader;
200
+		$this->request = $request;
201
+		$this->maintenance_mode = $maintenance_mode;
202
+		do_action('AHEE__EE_System__construct__begin', $this);
203
+		add_action(
204
+			'AHEE__EE_Bootstrap__load_espresso_addons',
205
+			array($this, 'loadCapabilities'),
206
+			5
207
+		);
208
+		add_action(
209
+			'AHEE__EE_Bootstrap__load_espresso_addons',
210
+			array($this, 'loadCommandBus'),
211
+			7
212
+		);
213
+		add_action(
214
+			'AHEE__EE_Bootstrap__load_espresso_addons',
215
+			array($this, 'loadPluginApi'),
216
+			9
217
+		);
218
+		// allow addons to load first so that they can register autoloaders, set hooks for running DMS's, etc
219
+		add_action(
220
+			'AHEE__EE_Bootstrap__load_espresso_addons',
221
+			array($this, 'load_espresso_addons')
222
+		);
223
+		// when an ee addon is activated, we want to call the core hook(s) again
224
+		// because the newly-activated addon didn't get a chance to run at all
225
+		add_action('activate_plugin', array($this, 'load_espresso_addons'), 1);
226
+		// detect whether install or upgrade
227
+		add_action(
228
+			'AHEE__EE_Bootstrap__detect_activations_or_upgrades',
229
+			array($this, 'detect_activations_or_upgrades'),
230
+			3
231
+		);
232
+		// load EE_Config, EE_Textdomain, etc
233
+		add_action(
234
+			'AHEE__EE_Bootstrap__load_core_configuration',
235
+			array($this, 'load_core_configuration'),
236
+			5
237
+		);
238
+		// load specifications for matching routes to current request
239
+		add_action(
240
+			'AHEE__EE_Bootstrap__load_core_configuration',
241
+			array($this, 'loadRouteMatchSpecifications')
242
+		);
243
+		// load specifications for custom post types
244
+		add_action(
245
+			'AHEE__EE_Bootstrap__load_core_configuration',
246
+			array($this, 'loadCustomPostTypes')
247
+		);
248
+		// load EE_Config, EE_Textdomain, etc
249
+		add_action(
250
+			'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets',
251
+			array($this, 'register_shortcodes_modules_and_widgets'),
252
+			7
253
+		);
254
+		// you wanna get going? I wanna get going... let's get going!
255
+		add_action(
256
+			'AHEE__EE_Bootstrap__brew_espresso',
257
+			array($this, 'brew_espresso'),
258
+			9
259
+		);
260
+		// other housekeeping
261
+		// exclude EE critical pages from wp_list_pages
262
+		add_filter(
263
+			'wp_list_pages_excludes',
264
+			array($this, 'remove_pages_from_wp_list_pages'),
265
+			10
266
+		);
267
+		// ALL EE Addons should use the following hook point to attach their initial setup too
268
+		// it's extremely important for EE Addons to register any class autoloaders so that they can be available when the EE_Config loads
269
+		do_action('AHEE__EE_System__construct__complete', $this);
270
+	}
271
+
272
+
273
+	/**
274
+	 * load and setup EE_Capabilities
275
+	 *
276
+	 * @return void
277
+	 * @throws EE_Error
278
+	 */
279
+	public function loadCapabilities()
280
+	{
281
+		$this->capabilities = $this->loader->getShared('EE_Capabilities');
282
+		add_action(
283
+			'AHEE__EE_Capabilities__init_caps__before_initialization',
284
+			function () {
285
+				LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
286
+			}
287
+		);
288
+	}
289
+
290
+
291
+	/**
292
+	 * create and cache the CommandBus, and also add middleware
293
+	 * The CapChecker middleware requires the use of EE_Capabilities
294
+	 * which is why we need to load the CommandBus after Caps are set up
295
+	 * CommandBus middleware operate FIFO - First In First Out
296
+	 * so LocateMovedCommands will run first in order to return any new commands
297
+	 *
298
+	 * @return void
299
+	 * @throws EE_Error
300
+	 */
301
+	public function loadCommandBus()
302
+	{
303
+		$this->loader->getShared(
304
+			'CommandBusInterface',
305
+			array(
306
+				null,
307
+				apply_filters(
308
+					'FHEE__EE_Load_Espresso_Core__handle_request__CommandBus_middleware',
309
+					array(
310
+						$this->loader->getShared('EventEspresso\core\services\commands\middleware\LocateMovedCommands'),
311
+						$this->loader->getShared('EventEspresso\core\services\commands\middleware\CapChecker'),
312
+						$this->loader->getShared('EventEspresso\core\services\commands\middleware\AddActionHook'),
313
+					)
314
+				),
315
+			)
316
+		);
317
+	}
318
+
319
+
320
+	/**
321
+	 * @return void
322
+	 * @throws EE_Error
323
+	 */
324
+	public function loadPluginApi()
325
+	{
326
+		// set autoloaders for all of the classes implementing EEI_Plugin_API
327
+		// which provide helpers for EE plugin authors to more easily register certain components with EE.
328
+		EEH_Autoloader::instance()->register_autoloaders_for_each_file_in_folder(EE_LIBRARIES . 'plugin_api');
329
+	}
330
+
331
+
332
+	/**
333
+	 * @param string $addon_name
334
+	 * @param string $version_constant
335
+	 * @param string $min_version_required
336
+	 * @param string $load_callback
337
+	 * @param string $plugin_file_constant
338
+	 * @return void
339
+	 */
340
+	private function deactivateIncompatibleAddon(
341
+		$addon_name,
342
+		$version_constant,
343
+		$min_version_required,
344
+		$load_callback,
345
+		$plugin_file_constant
346
+	) {
347
+		if (! defined($version_constant)) {
348
+			return;
349
+		}
350
+		$addon_version = constant($version_constant);
351
+		if ($addon_version && version_compare($addon_version, $min_version_required, '<')) {
352
+			remove_action('AHEE__EE_System__load_espresso_addons', $load_callback);
353
+			if (! function_exists('deactivate_plugins')) {
354
+				require_once ABSPATH . 'wp-admin/includes/plugin.php';
355
+			}
356
+			deactivate_plugins(plugin_basename(constant($plugin_file_constant)));
357
+			$this->request->unSetRequestParams(['activate', 'activate-multi'], true);
358
+			EE_Error::add_error(
359
+				sprintf(
360
+					esc_html__(
361
+						'We\'re sorry, but the Event Espresso %1$s addon was deactivated because version %2$s or higher is required with this version of Event Espresso core.',
362
+						'event_espresso'
363
+					),
364
+					$addon_name,
365
+					$min_version_required
366
+				),
367
+				__FILE__,
368
+				__FUNCTION__ . "({$addon_name})",
369
+				__LINE__
370
+			);
371
+			EE_Error::get_notices(false, true);
372
+		}
373
+	}
374
+
375
+
376
+	/**
377
+	 * load_espresso_addons
378
+	 * allow addons to load first so that they can set hooks for running DMS's, etc
379
+	 * this is hooked into both:
380
+	 *    'AHEE__EE_Bootstrap__load_core_configuration'
381
+	 *        which runs during the WP 'plugins_loaded' action at priority 5
382
+	 *    and the WP 'activate_plugin' hook point
383
+	 *
384
+	 * @access public
385
+	 * @return void
386
+	 */
387
+	public function load_espresso_addons()
388
+	{
389
+		$this->deactivateIncompatibleAddon(
390
+			'Wait Lists',
391
+			'EE_WAIT_LISTS_VERSION',
392
+			'1.0.0.beta.074',
393
+			'load_espresso_wait_lists',
394
+			'EE_WAIT_LISTS_PLUGIN_FILE'
395
+		);
396
+		$this->deactivateIncompatibleAddon(
397
+			'Automated Upcoming Event Notifications',
398
+			'EE_AUTOMATED_UPCOMING_EVENT_NOTIFICATION_VERSION',
399
+			'1.0.0.beta.091',
400
+			'load_espresso_automated_upcoming_event_notification',
401
+			'EE_AUTOMATED_UPCOMING_EVENT_NOTIFICATION_PLUGIN_FILE'
402
+		);
403
+		do_action('AHEE__EE_System__load_espresso_addons');
404
+		// if the WP API basic auth plugin isn't already loaded, load it now.
405
+		// We want it for mobile apps. Just include the entire plugin
406
+		// also, don't load the basic auth when a plugin is getting activated, because
407
+		// it could be the basic auth plugin, and it doesn't check if its methods are already defined
408
+		// and causes a fatal error
409
+		if (
410
+			($this->request->isWordPressApi() || $this->request->isApi())
411
+			&& $this->request->getRequestParam('activate') !== 'true'
412
+			&& ! function_exists('json_basic_auth_handler')
413
+			&& ! function_exists('json_basic_auth_error')
414
+			&& ! in_array(
415
+				$this->request->getRequestParam('action'),
416
+				array('activate', 'activate-selected'),
417
+				true
418
+			)
419
+			&& is_readable(EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php')
420
+		) {
421
+			include_once EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php';
422
+		}
423
+		do_action('AHEE__EE_System__load_espresso_addons__complete');
424
+	}
425
+
426
+
427
+	/**
428
+	 * detect_activations_or_upgrades
429
+	 * Checks for activation or upgrade of core first;
430
+	 * then also checks if any registered addons have been activated or upgraded
431
+	 * This is hooked into 'AHEE__EE_Bootstrap__detect_activations_or_upgrades'
432
+	 * which runs during the WP 'plugins_loaded' action at priority 3
433
+	 *
434
+	 * @access public
435
+	 * @return void
436
+	 */
437
+	public function detect_activations_or_upgrades()
438
+	{
439
+		// first off: let's make sure to handle core
440
+		$this->detect_if_activation_or_upgrade();
441
+		foreach ($this->registry->addons as $addon) {
442
+			if ($addon instanceof EE_Addon) {
443
+				// detect teh request type for that addon
444
+				$addon->detect_req_type();
445
+			}
446
+		}
447
+	}
448
+
449
+
450
+	/**
451
+	 * detect_if_activation_or_upgrade
452
+	 * Takes care of detecting whether this is a brand new install or code upgrade,
453
+	 * and either setting up the DB or setting up maintenance mode etc.
454
+	 *
455
+	 * @access public
456
+	 * @return void
457
+	 */
458
+	public function detect_if_activation_or_upgrade()
459
+	{
460
+		do_action('AHEE__EE_System___detect_if_activation_or_upgrade__begin');
461
+		// check if db has been updated, or if its a brand-new installation
462
+		$espresso_db_update = $this->fix_espresso_db_upgrade_option();
463
+		$request_type = $this->detect_req_type($espresso_db_update);
464
+		// EEH_Debug_Tools::printr( $request_type, '$request_type', __FILE__, __LINE__ );
465
+		switch ($request_type) {
466
+			case EE_System::req_type_new_activation:
467
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__new_activation');
468
+				$this->_handle_core_version_change($espresso_db_update);
469
+				break;
470
+			case EE_System::req_type_reactivation:
471
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__reactivation');
472
+				$this->_handle_core_version_change($espresso_db_update);
473
+				break;
474
+			case EE_System::req_type_upgrade:
475
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__upgrade');
476
+				// migrations may be required now that we've upgraded
477
+				$this->maintenance_mode->set_maintenance_mode_if_db_old();
478
+				$this->_handle_core_version_change($espresso_db_update);
479
+				break;
480
+			case EE_System::req_type_downgrade:
481
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__downgrade');
482
+				// its possible migrations are no longer required
483
+				$this->maintenance_mode->set_maintenance_mode_if_db_old();
484
+				$this->_handle_core_version_change($espresso_db_update);
485
+				break;
486
+			case EE_System::req_type_normal:
487
+			default:
488
+				break;
489
+		}
490
+		do_action('AHEE__EE_System__detect_if_activation_or_upgrade__complete');
491
+	}
492
+
493
+
494
+	/**
495
+	 * Updates the list of installed versions and sets hooks for
496
+	 * initializing the database later during the request
497
+	 *
498
+	 * @param array $espresso_db_update
499
+	 */
500
+	private function _handle_core_version_change($espresso_db_update)
501
+	{
502
+		$this->update_list_of_installed_versions($espresso_db_update);
503
+		// get ready to verify the DB is ok (provided we aren't in maintenance mode, of course)
504
+		add_action(
505
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
506
+			array($this, 'initialize_db_if_no_migrations_required')
507
+		);
508
+	}
509
+
510
+
511
+	/**
512
+	 * standardizes the wp option 'espresso_db_upgrade' which actually stores
513
+	 * information about what versions of EE have been installed and activated,
514
+	 * NOT necessarily the state of the database
515
+	 *
516
+	 * @param mixed $espresso_db_update           the value of the WordPress option.
517
+	 *                                            If not supplied, fetches it from the options table
518
+	 * @return array the correct value of 'espresso_db_upgrade', after saving it, if it needed correction
519
+	 */
520
+	private function fix_espresso_db_upgrade_option($espresso_db_update = null)
521
+	{
522
+		do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
523
+		if (! $espresso_db_update) {
524
+			$espresso_db_update = get_option('espresso_db_update');
525
+		}
526
+		// check that option is an array
527
+		if (! is_array($espresso_db_update)) {
528
+			// if option is FALSE, then it never existed
529
+			if ($espresso_db_update === false) {
530
+				// make $espresso_db_update an array and save option with autoload OFF
531
+				$espresso_db_update = array();
532
+				add_option('espresso_db_update', $espresso_db_update, '', 'no');
533
+			} else {
534
+				// option is NOT FALSE but also is NOT an array, so make it an array and save it
535
+				$espresso_db_update = array($espresso_db_update => array());
536
+				update_option('espresso_db_update', $espresso_db_update);
537
+			}
538
+		} else {
539
+			$corrected_db_update = array();
540
+			// if IS an array, but is it an array where KEYS are version numbers, and values are arrays?
541
+			foreach ($espresso_db_update as $should_be_version_string => $should_be_array) {
542
+				if (is_int($should_be_version_string) && ! is_array($should_be_array)) {
543
+					// the key is an int, and the value IS NOT an array
544
+					// so it must be numerically-indexed, where values are versions installed...
545
+					// fix it!
546
+					$version_string = $should_be_array;
547
+					$corrected_db_update[ $version_string ] = array('unknown-date');
548
+				} else {
549
+					// ok it checks out
550
+					$corrected_db_update[ $should_be_version_string ] = $should_be_array;
551
+				}
552
+			}
553
+			$espresso_db_update = $corrected_db_update;
554
+			update_option('espresso_db_update', $espresso_db_update);
555
+		}
556
+		do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__complete', $espresso_db_update);
557
+		return $espresso_db_update;
558
+	}
559
+
560
+
561
+	/**
562
+	 * Does the traditional work of setting up the plugin's database and adding default data.
563
+	 * If migration script/process did not exist, this is what would happen on every activation/reactivation/upgrade.
564
+	 * NOTE: if we're in maintenance mode (which would be the case if we detect there are data
565
+	 * migration scripts that need to be run and a version change happens), enqueues core for database initialization,
566
+	 * so that it will be done when migrations are finished
567
+	 *
568
+	 * @param boolean $initialize_addons_too if true, we double-check addons' database tables etc too;
569
+	 * @param boolean $verify_schema         if true will re-check the database tables have the correct schema.
570
+	 *                                       This is a resource-intensive job
571
+	 *                                       so we prefer to only do it when necessary
572
+	 * @return void
573
+	 * @throws EE_Error
574
+	 */
575
+	public function initialize_db_if_no_migrations_required($initialize_addons_too = false, $verify_schema = true)
576
+	{
577
+		$request_type = $this->detect_req_type();
578
+		// only initialize system if we're not in maintenance mode.
579
+		if ($this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
580
+			/** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
581
+			$rewrite_rules = $this->loader->getShared(
582
+				'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
583
+			);
584
+			$rewrite_rules->flush();
585
+			if ($verify_schema) {
586
+				EEH_Activation::initialize_db_and_folders();
587
+			}
588
+			EEH_Activation::initialize_db_content();
589
+			EEH_Activation::system_initialization();
590
+			if ($initialize_addons_too) {
591
+				$this->initialize_addons();
592
+			}
593
+		} else {
594
+			EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for('Core');
595
+		}
596
+		if (
597
+			$request_type === EE_System::req_type_new_activation
598
+			|| $request_type === EE_System::req_type_reactivation
599
+			|| (
600
+				$request_type === EE_System::req_type_upgrade
601
+				&& $this->is_major_version_change()
602
+			)
603
+		) {
604
+			add_action('AHEE__EE_System__initialize_last', array($this, 'redirect_to_about_ee'), 9);
605
+		}
606
+	}
607
+
608
+
609
+	/**
610
+	 * Initializes the db for all registered addons
611
+	 *
612
+	 * @throws EE_Error
613
+	 */
614
+	public function initialize_addons()
615
+	{
616
+		// foreach registered addon, make sure its db is up-to-date too
617
+		foreach ($this->registry->addons as $addon) {
618
+			if ($addon instanceof EE_Addon) {
619
+				$addon->initialize_db_if_no_migrations_required();
620
+			}
621
+		}
622
+	}
623
+
624
+
625
+	/**
626
+	 * Adds the current code version to the saved wp option which stores a list of all ee versions ever installed.
627
+	 *
628
+	 * @param    array  $version_history
629
+	 * @param    string $current_version_to_add version to be added to the version history
630
+	 * @return    boolean success as to whether or not this option was changed
631
+	 */
632
+	public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null)
633
+	{
634
+		if (! $version_history) {
635
+			$version_history = $this->fix_espresso_db_upgrade_option($version_history);
636
+		}
637
+		if ($current_version_to_add === null) {
638
+			$current_version_to_add = espresso_version();
639
+		}
640
+		$version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
641
+		// re-save
642
+		return update_option('espresso_db_update', $version_history);
643
+	}
644
+
645
+
646
+	/**
647
+	 * Detects if the current version indicated in the has existed in the list of
648
+	 * previously-installed versions of EE (espresso_db_update). Does NOT modify it (ie, no side-effect)
649
+	 *
650
+	 * @param array $espresso_db_update array from the wp option stored under the name 'espresso_db_update'.
651
+	 *                                  If not supplied, fetches it from the options table.
652
+	 *                                  Also, caches its result so later parts of the code can also know whether
653
+	 *                                  there's been an update or not. This way we can add the current version to
654
+	 *                                  espresso_db_update, but still know if this is a new install or not
655
+	 * @return int one of the constants on EE_System::req_type_
656
+	 */
657
+	public function detect_req_type($espresso_db_update = null)
658
+	{
659
+		if ($this->_req_type === null) {
660
+			$espresso_db_update = ! empty($espresso_db_update)
661
+				? $espresso_db_update
662
+				: $this->fix_espresso_db_upgrade_option();
663
+			$this->_req_type = EE_System::detect_req_type_given_activation_history(
664
+				$espresso_db_update,
665
+				'ee_espresso_activation',
666
+				espresso_version()
667
+			);
668
+			$this->_major_version_change = $this->_detect_major_version_change($espresso_db_update);
669
+			$this->request->setIsActivation($this->_req_type !== EE_System::req_type_normal);
670
+		}
671
+		return $this->_req_type;
672
+	}
673
+
674
+
675
+	/**
676
+	 * Returns whether or not there was a non-micro version change (ie, change in either
677
+	 * the first or second number in the version. Eg 4.9.0.rc.001 to 4.10.0.rc.000,
678
+	 * but not 4.9.0.rc.0001 to 4.9.1.rc.0001
679
+	 *
680
+	 * @param $activation_history
681
+	 * @return bool
682
+	 */
683
+	private function _detect_major_version_change($activation_history)
684
+	{
685
+		$previous_version = EE_System::_get_most_recently_active_version_from_activation_history($activation_history);
686
+		$previous_version_parts = explode('.', $previous_version);
687
+		$current_version_parts = explode('.', espresso_version());
688
+		return isset($previous_version_parts[0], $previous_version_parts[1], $current_version_parts[0], $current_version_parts[1])
689
+			   && ($previous_version_parts[0] !== $current_version_parts[0]
690
+				   || $previous_version_parts[1] !== $current_version_parts[1]
691
+			   );
692
+	}
693
+
694
+
695
+	/**
696
+	 * Returns true if either the major or minor version of EE changed during this request.
697
+	 * Eg 4.9.0.rc.001 to 4.10.0.rc.000, but not 4.9.0.rc.0001 to 4.9.1.rc.0001
698
+	 *
699
+	 * @return bool
700
+	 */
701
+	public function is_major_version_change()
702
+	{
703
+		return $this->_major_version_change;
704
+	}
705
+
706
+
707
+	/**
708
+	 * Determines the request type for any ee addon, given three piece of info: the current array of activation
709
+	 * histories (for core that' 'espresso_db_update' wp option); the name of the WordPress option which is temporarily
710
+	 * set upon activation of the plugin (for core it's 'ee_espresso_activation'); and the version that this plugin was
711
+	 * just activated to (for core that will always be espresso_version())
712
+	 *
713
+	 * @param array  $activation_history_for_addon     the option's value which stores the activation history for this
714
+	 *                                                 ee plugin. for core that's 'espresso_db_update'
715
+	 * @param string $activation_indicator_option_name the name of the WordPress option that is temporarily set to
716
+	 *                                                 indicate that this plugin was just activated
717
+	 * @param string $version_to_upgrade_to            the version that was just upgraded to (for core that will be
718
+	 *                                                 espresso_version())
719
+	 * @return int one of the constants on EE_System::req_type_*
720
+	 */
721
+	public static function detect_req_type_given_activation_history(
722
+		$activation_history_for_addon,
723
+		$activation_indicator_option_name,
724
+		$version_to_upgrade_to
725
+	) {
726
+		$version_is_higher = self::_new_version_is_higher($activation_history_for_addon, $version_to_upgrade_to);
727
+		if ($activation_history_for_addon) {
728
+			// it exists, so this isn't a completely new install
729
+			// check if this version already in that list of previously installed versions
730
+			if (! isset($activation_history_for_addon[ $version_to_upgrade_to ])) {
731
+				// it a version we haven't seen before
732
+				if ($version_is_higher === 1) {
733
+					$req_type = EE_System::req_type_upgrade;
734
+				} else {
735
+					$req_type = EE_System::req_type_downgrade;
736
+				}
737
+				delete_option($activation_indicator_option_name);
738
+			} else {
739
+				// its not an update. maybe a reactivation?
740
+				if (get_option($activation_indicator_option_name, false)) {
741
+					if ($version_is_higher === -1) {
742
+						$req_type = EE_System::req_type_downgrade;
743
+					} elseif ($version_is_higher === 0) {
744
+						// we've seen this version before, but it's an activation. must be a reactivation
745
+						$req_type = EE_System::req_type_reactivation;
746
+					} else {// $version_is_higher === 1
747
+						$req_type = EE_System::req_type_upgrade;
748
+					}
749
+					delete_option($activation_indicator_option_name);
750
+				} else {
751
+					// we've seen this version before and the activation indicate doesn't show it was just activated
752
+					if ($version_is_higher === -1) {
753
+						$req_type = EE_System::req_type_downgrade;
754
+					} elseif ($version_is_higher === 0) {
755
+						// we've seen this version before and it's not an activation. its normal request
756
+						$req_type = EE_System::req_type_normal;
757
+					} else {// $version_is_higher === 1
758
+						$req_type = EE_System::req_type_upgrade;
759
+					}
760
+				}
761
+			}
762
+		} else {
763
+			// brand new install
764
+			$req_type = EE_System::req_type_new_activation;
765
+			delete_option($activation_indicator_option_name);
766
+		}
767
+		return $req_type;
768
+	}
769
+
770
+
771
+	/**
772
+	 * Detects if the $version_to_upgrade_to is higher than the most recent version in
773
+	 * the $activation_history_for_addon
774
+	 *
775
+	 * @param array  $activation_history_for_addon (keys are versions, values are arrays of times activated,
776
+	 *                                             sometimes containing 'unknown-date'
777
+	 * @param string $version_to_upgrade_to        (current version)
778
+	 * @return int results of version_compare( $version_to_upgrade_to, $most_recently_active_version ).
779
+	 *                                             ie, -1 if $version_to_upgrade_to is LOWER (downgrade);
780
+	 *                                             0 if $version_to_upgrade_to MATCHES (reactivation or normal request);
781
+	 *                                             1 if $version_to_upgrade_to is HIGHER (upgrade) ;
782
+	 */
783
+	private static function _new_version_is_higher($activation_history_for_addon, $version_to_upgrade_to)
784
+	{
785
+		// find the most recently-activated version
786
+		$most_recently_active_version =
787
+			EE_System::_get_most_recently_active_version_from_activation_history($activation_history_for_addon);
788
+		return version_compare($version_to_upgrade_to, $most_recently_active_version);
789
+	}
790
+
791
+
792
+	/**
793
+	 * Gets the most recently active version listed in the activation history,
794
+	 * and if none are found (ie, it's a brand new install) returns '0.0.0.dev.000'.
795
+	 *
796
+	 * @param array $activation_history  (keys are versions, values are arrays of times activated,
797
+	 *                                   sometimes containing 'unknown-date'
798
+	 * @return string
799
+	 */
800
+	private static function _get_most_recently_active_version_from_activation_history($activation_history)
801
+	{
802
+		$most_recently_active_version_activation = '1970-01-01 00:00:00';
803
+		$most_recently_active_version = '0.0.0.dev.000';
804
+		if (is_array($activation_history)) {
805
+			foreach ($activation_history as $version => $times_activated) {
806
+				// check there is a record of when this version was activated. Otherwise,
807
+				// mark it as unknown
808
+				if (! $times_activated) {
809
+					$times_activated = array('unknown-date');
810
+				}
811
+				if (is_string($times_activated)) {
812
+					$times_activated = array($times_activated);
813
+				}
814
+				foreach ($times_activated as $an_activation) {
815
+					if (
816
+						$an_activation !== 'unknown-date'
817
+						&& $an_activation
818
+						   > $most_recently_active_version_activation
819
+					) {
820
+						$most_recently_active_version = $version;
821
+						$most_recently_active_version_activation = $an_activation === 'unknown-date'
822
+							? '1970-01-01 00:00:00'
823
+							: $an_activation;
824
+					}
825
+				}
826
+			}
827
+		}
828
+		return $most_recently_active_version;
829
+	}
830
+
831
+
832
+	/**
833
+	 * This redirects to the about EE page after activation
834
+	 *
835
+	 * @return void
836
+	 */
837
+	public function redirect_to_about_ee()
838
+	{
839
+		$notices = EE_Error::get_notices(false);
840
+		// if current user is an admin and it's not an ajax or rest request
841
+		if (
842
+			! isset($notices['errors'])
843
+			&& $this->request->isAdmin()
844
+			&& apply_filters(
845
+				'FHEE__EE_System__redirect_to_about_ee__do_redirect',
846
+				$this->capabilities->current_user_can('manage_options', 'espresso_about_default')
847
+			)
848
+		) {
849
+			$query_params = array('page' => 'espresso_about');
850
+			if (EE_System::instance()->detect_req_type() === EE_System::req_type_new_activation) {
851
+				$query_params['new_activation'] = true;
852
+			}
853
+			if (EE_System::instance()->detect_req_type() === EE_System::req_type_reactivation) {
854
+				$query_params['reactivation'] = true;
855
+			}
856
+			$url = add_query_arg($query_params, admin_url('admin.php'));
857
+			wp_safe_redirect($url);
858
+			exit();
859
+		}
860
+	}
861
+
862
+
863
+	/**
864
+	 * load_core_configuration
865
+	 * this is hooked into 'AHEE__EE_Bootstrap__load_core_configuration'
866
+	 * which runs during the WP 'plugins_loaded' action at priority 5
867
+	 *
868
+	 * @return void
869
+	 * @throws ReflectionException
870
+	 * @throws Exception
871
+	 */
872
+	public function load_core_configuration()
873
+	{
874
+		do_action('AHEE__EE_System__load_core_configuration__begin', $this);
875
+		$this->loader->getShared('EE_Load_Textdomain');
876
+		// load textdomain
877
+		EE_Load_Textdomain::load_textdomain();
878
+		// load caf stuff a chance to play during the activation process too.
879
+		$this->_maybe_brew_regular();
880
+		// load and setup EE_Config and EE_Network_Config
881
+		$config = $this->loader->getShared('EE_Config');
882
+		$this->loader->getShared('EE_Network_Config');
883
+		// setup autoloaders
884
+		// enable logging?
885
+		$this->loader->getShared('EventEspresso\core\services\orm\TrashLogger');
886
+		if ($config->admin->use_remote_logging) {
887
+			$this->loader->getShared('EE_Log');
888
+		}
889
+		// check for activation errors
890
+		$activation_errors = get_option('ee_plugin_activation_errors', false);
891
+		if ($activation_errors) {
892
+			EE_Error::add_error($activation_errors, __FILE__, __FUNCTION__, __LINE__);
893
+			update_option('ee_plugin_activation_errors', false);
894
+		}
895
+		// get model names
896
+		$this->_parse_model_names();
897
+		// configure custom post type definitions
898
+		$this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions');
899
+		$this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions');
900
+		do_action('AHEE__EE_System__load_core_configuration__complete', $this);
901
+	}
902
+
903
+
904
+	/**
905
+	 * cycles through all of the models/*.model.php files, and assembles an array of model names
906
+	 *
907
+	 * @return void
908
+	 * @throws ReflectionException
909
+	 */
910
+	private function _parse_model_names()
911
+	{
912
+		// get all the files in the EE_MODELS folder that end in .model.php
913
+		$models = glob(EE_MODELS . '*.model.php');
914
+		$model_names = array();
915
+		$non_abstract_db_models = array();
916
+		foreach ($models as $model) {
917
+			// get model classname
918
+			$classname = EEH_File::get_classname_from_filepath_with_standard_filename($model);
919
+			$short_name = str_replace('EEM_', '', $classname);
920
+			$reflectionClass = new ReflectionClass($classname);
921
+			if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
922
+				$non_abstract_db_models[ $short_name ] = $classname;
923
+			}
924
+			$model_names[ $short_name ] = $classname;
925
+		}
926
+		$this->registry->models = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
927
+		$this->registry->non_abstract_db_models = apply_filters(
928
+			'FHEE__EE_System__parse_implemented_model_names',
929
+			$non_abstract_db_models
930
+		);
931
+	}
932
+
933
+
934
+	/**
935
+	 * The purpose of this method is to simply check for a file named "caffeinated/brewing_regular.php" for any hooks
936
+	 * that need to be setup before our EE_System launches.
937
+	 *
938
+	 * @return void
939
+	 * @throws DomainException
940
+	 * @throws InvalidArgumentException
941
+	 * @throws InvalidDataTypeException
942
+	 * @throws InvalidInterfaceException
943
+	 * @throws InvalidClassException
944
+	 * @throws InvalidFilePathException
945
+	 */
946
+	private function _maybe_brew_regular()
947
+	{
948
+		/** @var Domain $domain */
949
+		$domain = DomainFactory::getShared(
950
+			new FullyQualifiedName(
951
+				'EventEspresso\core\domain\Domain'
952
+			),
953
+			array(
954
+				new FilePath(EVENT_ESPRESSO_MAIN_FILE),
955
+				Version::fromString(espresso_version()),
956
+			)
957
+		);
958
+		if ($domain->isCaffeinated()) {
959
+			require_once EE_CAFF_PATH . 'brewing_regular.php';
960
+		}
961
+	}
962
+
963
+
964
+	/**
965
+	 * @since 4.9.71.p
966
+	 * @throws Exception
967
+	 */
968
+	public function loadRouteMatchSpecifications()
969
+	{
970
+		try {
971
+			$this->loader->getShared(
972
+				'EventEspresso\core\services\route_match\RouteMatchSpecificationManager'
973
+			);
974
+		} catch (Exception $exception) {
975
+			new ExceptionStackTraceDisplay($exception);
976
+		}
977
+		do_action('AHEE__EE_System__loadRouteMatchSpecifications');
978
+	}
979
+
980
+
981
+	/**
982
+	 * loading CPT related classes earlier so that their definitions are available
983
+	 * but not performing any actual registration with WP core until load_CPTs_and_session() is called
984
+	 *
985
+	 * @since   4.10.21.p
986
+	 */
987
+	public function loadCustomPostTypes()
988
+	{
989
+		$this->register_custom_taxonomies = $this->loader->getShared(
990
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'
991
+		);
992
+		$this->register_custom_post_types = $this->loader->getShared(
993
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'
994
+		);
995
+		$this->register_custom_taxonomy_terms = $this->loader->getShared(
996
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms'
997
+		);
998
+		// integrate WP_Query with the EE models
999
+		$this->loader->getShared('EE_CPT_Strategy');
1000
+		// load legacy EE_Request_Handler in case add-ons still need it
1001
+		$this->loader->getShared('EE_Request_Handler');
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * register_shortcodes_modules_and_widgets
1007
+	 * generate lists of shortcodes and modules, then verify paths and classes
1008
+	 * This is hooked into 'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets'
1009
+	 * which runs during the WP 'plugins_loaded' action at priority 7
1010
+	 *
1011
+	 * @access public
1012
+	 * @return void
1013
+	 * @throws Exception
1014
+	 */
1015
+	public function register_shortcodes_modules_and_widgets()
1016
+	{
1017
+		if ($this->request->isFrontend() || $this->request->isIframe() || $this->request->isAjax()) {
1018
+			// load, register, and add shortcodes the new way
1019
+			$this->loader->getShared('EventEspresso\core\services\shortcodes\ShortcodesManager');
1020
+		}
1021
+		do_action('AHEE__EE_System__register_shortcodes_modules_and_widgets');
1022
+		// check for addons using old hook point
1023
+		if (has_action('AHEE__EE_System__register_shortcodes_modules_and_addons')) {
1024
+			$this->_incompatible_addon_error();
1025
+		}
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * _incompatible_addon_error
1031
+	 *
1032
+	 * @access public
1033
+	 * @return void
1034
+	 */
1035
+	private function _incompatible_addon_error()
1036
+	{
1037
+		// get array of classes hooking into here
1038
+		$class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
1039
+			'AHEE__EE_System__register_shortcodes_modules_and_addons'
1040
+		);
1041
+		if (! empty($class_names)) {
1042
+			$msg = esc_html__(
1043
+				'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
1044
+				'event_espresso'
1045
+			);
1046
+			$msg .= '<ul>';
1047
+			foreach ($class_names as $class_name) {
1048
+				$msg .= '<li><b>Event Espresso - '
1049
+						. str_replace(
1050
+							array('EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'),
1051
+							'',
1052
+							$class_name
1053
+						) . '</b></li>';
1054
+			}
1055
+			$msg .= '</ul>';
1056
+			$msg .= esc_html__(
1057
+				'Compatibility issues can be avoided and/or resolved by keeping addons and plugins updated to the latest version.',
1058
+				'event_espresso'
1059
+			);
1060
+			// save list of incompatible addons to wp-options for later use
1061
+			add_option('ee_incompatible_addons', $class_names, '', 'no');
1062
+			if (is_admin()) {
1063
+				EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1064
+			}
1065
+		}
1066
+	}
1067
+
1068
+
1069
+	/**
1070
+	 * brew_espresso
1071
+	 * begins the process of setting hooks for initializing EE in the correct order
1072
+	 * This is happening on the 'AHEE__EE_Bootstrap__brew_espresso' hook point
1073
+	 * which runs during the WP 'plugins_loaded' action at priority 9
1074
+	 *
1075
+	 * @return void
1076
+	 */
1077
+	public function brew_espresso()
1078
+	{
1079
+		do_action('AHEE__EE_System__brew_espresso__begin', $this);
1080
+		// load some final core systems
1081
+		add_action('init', array($this, 'set_hooks_for_core'), 1);
1082
+		add_action('init', array($this, 'perform_activations_upgrades_and_migrations'), 3);
1083
+		add_action('init', array($this, 'load_CPTs_and_session'), 5);
1084
+		add_action('init', array($this, 'load_controllers'), 7);
1085
+		add_action('init', array($this, 'core_loaded_and_ready'), 9);
1086
+		add_action('init', array($this, 'initialize'), 10);
1087
+		add_action('init', array($this, 'initialize_last'), 100);
1088
+		if (is_admin() && apply_filters('FHEE__EE_System__brew_espresso__load_pue', true)) {
1089
+			// pew pew pew
1090
+			$this->loader->getShared('EventEspresso\core\services\licensing\LicenseService');
1091
+			do_action('AHEE__EE_System__brew_espresso__after_pue_init');
1092
+		}
1093
+		do_action('AHEE__EE_System__brew_espresso__complete', $this);
1094
+	}
1095
+
1096
+
1097
+	/**
1098
+	 *    set_hooks_for_core
1099
+	 *
1100
+	 * @access public
1101
+	 * @return    void
1102
+	 * @throws EE_Error
1103
+	 */
1104
+	public function set_hooks_for_core()
1105
+	{
1106
+		$this->_deactivate_incompatible_addons();
1107
+		do_action('AHEE__EE_System__set_hooks_for_core');
1108
+		$this->loader->getShared('EventEspresso\core\domain\values\session\SessionLifespan');
1109
+		// caps need to be initialized on every request so that capability maps are set.
1110
+		// @see https://events.codebasehq.com/projects/event-espresso/tickets/8674
1111
+		$this->registry->CAP->init_caps();
1112
+	}
1113
+
1114
+
1115
+	/**
1116
+	 * Using the information gathered in EE_System::_incompatible_addon_error,
1117
+	 * deactivates any addons considered incompatible with the current version of EE
1118
+	 */
1119
+	private function _deactivate_incompatible_addons()
1120
+	{
1121
+		$incompatible_addons = get_option('ee_incompatible_addons', array());
1122
+		if (! empty($incompatible_addons)) {
1123
+			$active_plugins = get_option('active_plugins', array());
1124
+			foreach ($active_plugins as $active_plugin) {
1125
+				foreach ($incompatible_addons as $incompatible_addon) {
1126
+					if (strpos($active_plugin, $incompatible_addon) !== false) {
1127
+						$this->request->unSetRequestParams(['activate'], true);
1128
+						espresso_deactivate_plugin($active_plugin);
1129
+					}
1130
+				}
1131
+			}
1132
+		}
1133
+	}
1134
+
1135
+
1136
+	/**
1137
+	 *    perform_activations_upgrades_and_migrations
1138
+	 *
1139
+	 * @access public
1140
+	 * @return    void
1141
+	 */
1142
+	public function perform_activations_upgrades_and_migrations()
1143
+	{
1144
+		do_action('AHEE__EE_System__perform_activations_upgrades_and_migrations');
1145
+	}
1146
+
1147
+
1148
+	/**
1149
+	 * @return void
1150
+	 * @throws DomainException
1151
+	 */
1152
+	public function load_CPTs_and_session()
1153
+	{
1154
+		do_action('AHEE__EE_System__load_CPTs_and_session__start');
1155
+		$this->register_custom_taxonomies->registerCustomTaxonomies();
1156
+		$this->register_custom_post_types->registerCustomPostTypes();
1157
+		$this->register_custom_taxonomy_terms->registerCustomTaxonomyTerms();
1158
+		// load legacy Custom Post Types and Taxonomies
1159
+		$this->loader->getShared('EE_Register_CPTs');
1160
+		do_action('AHEE__EE_System__load_CPTs_and_session__complete');
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * load_controllers
1166
+	 * this is the best place to load any additional controllers that needs access to EE core.
1167
+	 * it is expected that all basic core EE systems, that are not dependant on the current request are loaded at this
1168
+	 * time
1169
+	 *
1170
+	 * @access public
1171
+	 * @return void
1172
+	 */
1173
+	public function load_controllers()
1174
+	{
1175
+		do_action('AHEE__EE_System__load_controllers__start');
1176
+		// let's get it started
1177
+		if (
1178
+			! $this->maintenance_mode->level()
1179
+			&& ($this->request->isFrontend() || $this->request->isFrontAjax())
1180
+		) {
1181
+			do_action('AHEE__EE_System__load_controllers__load_front_controllers');
1182
+			$this->loader->getShared('EE_Front_Controller');
1183
+		} elseif ($this->request->isAdmin() || $this->request->isAdminAjax()) {
1184
+			do_action('AHEE__EE_System__load_controllers__load_admin_controllers');
1185
+			$this->loader->getShared('EE_Admin');
1186
+			$this->loader->getShared(
1187
+				'EventEspresso\core\services\activation\plugin_prompt\DownloadPluginPromptManager'
1188
+			);
1189
+		} elseif ($this->request->isWordPressHeartbeat()) {
1190
+			$this->loader->getShared('EventEspresso\core\domain\services\admin\ajax\WordpressHeartbeat');
1191
+		}
1192
+		do_action('AHEE__EE_System__load_controllers__complete');
1193
+	}
1194
+
1195
+
1196
+	/**
1197
+	 * core_loaded_and_ready
1198
+	 * all of the basic EE core should be loaded at this point and available regardless of M-Mode
1199
+	 *
1200
+	 * @access public
1201
+	 * @return void
1202
+	 * @throws Exception
1203
+	 */
1204
+	public function core_loaded_and_ready()
1205
+	{
1206
+		if (
1207
+			$this->request->isAdmin()
1208
+			|| $this->request->isFrontend()
1209
+			|| $this->request->isIframe()
1210
+			|| $this->request->isWordPressApi()
1211
+		) {
1212
+			try {
1213
+				$this->loader->getShared('EventEspresso\core\services\assets\I18nRegistry', [[]]);
1214
+				$this->loader->getShared('EventEspresso\core\services\assets\Registry');
1215
+				$this->loader->getShared('EventEspresso\core\domain\services\assets\CoreAssetManager');
1216
+				if ($this->canLoadBlocks()) {
1217
+					$this->loader->getShared(
1218
+						'EventEspresso\core\services\editor\BlockRegistrationManager'
1219
+					);
1220
+				}
1221
+			} catch (Exception $exception) {
1222
+				new ExceptionStackTraceDisplay($exception);
1223
+			}
1224
+		}
1225
+		if (
1226
+			$this->request->isAdmin()
1227
+			|| $this->request->isEeAjax()
1228
+			|| $this->request->isFrontend()
1229
+		) {
1230
+			$this->loader->getShared('EE_Session');
1231
+		}
1232
+		do_action('AHEE__EE_System__core_loaded_and_ready');
1233
+		// always load template tags, because it's faster than checking if it's a front-end request, and many page
1234
+		// builders require these even on the front-end
1235
+		require_once EE_PUBLIC . 'template_tags.php';
1236
+		do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * initialize
1242
+	 * this is the best place to begin initializing client code
1243
+	 *
1244
+	 * @access public
1245
+	 * @return void
1246
+	 */
1247
+	public function initialize()
1248
+	{
1249
+		do_action('AHEE__EE_System__initialize');
1250
+		add_filter(
1251
+			'safe_style_css',
1252
+			function ($styles) {
1253
+				$styles[] = 'display';
1254
+				$styles[] = 'visibility';
1255
+				$styles[] = 'position';
1256
+				$styles[] = 'top';
1257
+				$styles[] = 'right';
1258
+				$styles[] = 'bottom';
1259
+				$styles[] = 'left';
1260
+				$styles[] = 'resize';
1261
+				$styles[] = 'max-width';
1262
+				$styles[] = 'max-height';
1263
+				return $styles;
1264
+			}
1265
+		);
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * initialize_last
1271
+	 * this is run really late during the WP init hook point, and ensures that mostly everything else that needs to
1272
+	 * initialize has done so
1273
+	 *
1274
+	 * @access public
1275
+	 * @return void
1276
+	 */
1277
+	public function initialize_last()
1278
+	{
1279
+		do_action('AHEE__EE_System__initialize_last');
1280
+		/** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
1281
+		$rewrite_rules = $this->loader->getShared(
1282
+			'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
1283
+		);
1284
+		$rewrite_rules->flushRewriteRules();
1285
+		add_action('admin_bar_init', array($this, 'addEspressoToolbar'));
1286
+		if (
1287
+			($this->request->isAjax() || $this->request->isAdmin())
1288
+			&& $this->maintenance_mode->models_can_query()
1289
+		) {
1290
+			$this->loader->getShared('EventEspresso\core\services\privacy\export\PersonalDataExporterManager');
1291
+			$this->loader->getShared('EventEspresso\core\services\privacy\erasure\PersonalDataEraserManager');
1292
+		}
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * @return void
1298
+	 * @throws EE_Error
1299
+	 */
1300
+	public function addEspressoToolbar()
1301
+	{
1302
+		$this->loader->getShared(
1303
+			'EventEspresso\core\domain\services\admin\AdminToolBar',
1304
+			array($this->registry->CAP)
1305
+		);
1306
+	}
1307
+
1308
+
1309
+	/**
1310
+	 * do_not_cache
1311
+	 * sets no cache headers and defines no cache constants for WP plugins
1312
+	 *
1313
+	 * @access public
1314
+	 * @return void
1315
+	 */
1316
+	public static function do_not_cache()
1317
+	{
1318
+		// set no cache constants
1319
+		if (! defined('DONOTCACHEPAGE')) {
1320
+			define('DONOTCACHEPAGE', true);
1321
+		}
1322
+		if (! defined('DONOTCACHCEOBJECT')) {
1323
+			define('DONOTCACHCEOBJECT', true);
1324
+		}
1325
+		if (! defined('DONOTCACHEDB')) {
1326
+			define('DONOTCACHEDB', true);
1327
+		}
1328
+		// add no cache headers
1329
+		add_action('send_headers', array('EE_System', 'nocache_headers'), 10);
1330
+		// plus a little extra for nginx and Google Chrome
1331
+		add_filter('nocache_headers', array('EE_System', 'extra_nocache_headers'), 10, 1);
1332
+		// prevent browsers from prefetching of the rel='next' link, because it may contain content that interferes with the registration process
1333
+		remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 *    extra_nocache_headers
1339
+	 *
1340
+	 * @access    public
1341
+	 * @param $headers
1342
+	 * @return    array
1343
+	 */
1344
+	public static function extra_nocache_headers($headers)
1345
+	{
1346
+		// for NGINX
1347
+		$headers['X-Accel-Expires'] = 0;
1348
+		// plus extra for Google Chrome since it doesn't seem to respect "no-cache", but WILL respect "no-store"
1349
+		$headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
1350
+		return $headers;
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 *    nocache_headers
1356
+	 *
1357
+	 * @access    public
1358
+	 * @return    void
1359
+	 */
1360
+	public static function nocache_headers()
1361
+	{
1362
+		nocache_headers();
1363
+	}
1364
+
1365
+
1366
+	/**
1367
+	 * simply hooks into "wp_list_pages_exclude" filter (for wp_list_pages method) and makes sure EE critical pages are
1368
+	 * never returned with the function.
1369
+	 *
1370
+	 * @param  array $exclude_array any existing pages being excluded are in this array.
1371
+	 * @return array
1372
+	 */
1373
+	public function remove_pages_from_wp_list_pages($exclude_array)
1374
+	{
1375
+		return array_merge($exclude_array, $this->registry->CFG->core->get_critical_pages_array());
1376
+	}
1377
+
1378
+
1379
+	/**
1380
+	 * Return whether blocks can be registered/loaded or not.
1381
+	 * @return bool
1382
+	 */
1383
+	private function canLoadBlocks()
1384
+	{
1385
+		return apply_filters('FHEE__EE_System__canLoadBlocks', true)
1386
+			   && function_exists('register_block_type')
1387
+			   // don't load blocks if in the Divi page builder editor context
1388
+			   // @see https://github.com/eventespresso/event-espresso-core/issues/814
1389
+			   && ! $this->request->getRequestParam('et_fb', false);
1390
+	}
1391 1391
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
         EE_Maintenance_Mode $maintenance_mode = null
155 155
     ) {
156 156
         // check if class object is instantiated
157
-        if (! self::$_instance instanceof EE_System) {
157
+        if ( ! self::$_instance instanceof EE_System) {
158 158
             self::$_instance = new self($registry, $loader, $request, $maintenance_mode);
159 159
         }
160 160
         return self::$_instance;
@@ -281,7 +281,7 @@  discard block
 block discarded – undo
281 281
         $this->capabilities = $this->loader->getShared('EE_Capabilities');
282 282
         add_action(
283 283
             'AHEE__EE_Capabilities__init_caps__before_initialization',
284
-            function () {
284
+            function() {
285 285
                 LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
286 286
             }
287 287
         );
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
     {
326 326
         // set autoloaders for all of the classes implementing EEI_Plugin_API
327 327
         // which provide helpers for EE plugin authors to more easily register certain components with EE.
328
-        EEH_Autoloader::instance()->register_autoloaders_for_each_file_in_folder(EE_LIBRARIES . 'plugin_api');
328
+        EEH_Autoloader::instance()->register_autoloaders_for_each_file_in_folder(EE_LIBRARIES.'plugin_api');
329 329
     }
330 330
 
331 331
 
@@ -344,14 +344,14 @@  discard block
 block discarded – undo
344 344
         $load_callback,
345 345
         $plugin_file_constant
346 346
     ) {
347
-        if (! defined($version_constant)) {
347
+        if ( ! defined($version_constant)) {
348 348
             return;
349 349
         }
350 350
         $addon_version = constant($version_constant);
351 351
         if ($addon_version && version_compare($addon_version, $min_version_required, '<')) {
352 352
             remove_action('AHEE__EE_System__load_espresso_addons', $load_callback);
353
-            if (! function_exists('deactivate_plugins')) {
354
-                require_once ABSPATH . 'wp-admin/includes/plugin.php';
353
+            if ( ! function_exists('deactivate_plugins')) {
354
+                require_once ABSPATH.'wp-admin/includes/plugin.php';
355 355
             }
356 356
             deactivate_plugins(plugin_basename(constant($plugin_file_constant)));
357 357
             $this->request->unSetRequestParams(['activate', 'activate-multi'], true);
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
                     $min_version_required
366 366
                 ),
367 367
                 __FILE__,
368
-                __FUNCTION__ . "({$addon_name})",
368
+                __FUNCTION__."({$addon_name})",
369 369
                 __LINE__
370 370
             );
371 371
             EE_Error::get_notices(false, true);
@@ -416,9 +416,9 @@  discard block
 block discarded – undo
416 416
                 array('activate', 'activate-selected'),
417 417
                 true
418 418
             )
419
-            && is_readable(EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php')
419
+            && is_readable(EE_THIRD_PARTY.'wp-api-basic-auth/basic-auth.php')
420 420
         ) {
421
-            include_once EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php';
421
+            include_once EE_THIRD_PARTY.'wp-api-basic-auth/basic-auth.php';
422 422
         }
423 423
         do_action('AHEE__EE_System__load_espresso_addons__complete');
424 424
     }
@@ -520,11 +520,11 @@  discard block
 block discarded – undo
520 520
     private function fix_espresso_db_upgrade_option($espresso_db_update = null)
521 521
     {
522 522
         do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
523
-        if (! $espresso_db_update) {
523
+        if ( ! $espresso_db_update) {
524 524
             $espresso_db_update = get_option('espresso_db_update');
525 525
         }
526 526
         // check that option is an array
527
-        if (! is_array($espresso_db_update)) {
527
+        if ( ! is_array($espresso_db_update)) {
528 528
             // if option is FALSE, then it never existed
529 529
             if ($espresso_db_update === false) {
530 530
                 // make $espresso_db_update an array and save option with autoload OFF
@@ -544,10 +544,10 @@  discard block
 block discarded – undo
544 544
                     // so it must be numerically-indexed, where values are versions installed...
545 545
                     // fix it!
546 546
                     $version_string = $should_be_array;
547
-                    $corrected_db_update[ $version_string ] = array('unknown-date');
547
+                    $corrected_db_update[$version_string] = array('unknown-date');
548 548
                 } else {
549 549
                     // ok it checks out
550
-                    $corrected_db_update[ $should_be_version_string ] = $should_be_array;
550
+                    $corrected_db_update[$should_be_version_string] = $should_be_array;
551 551
                 }
552 552
             }
553 553
             $espresso_db_update = $corrected_db_update;
@@ -631,13 +631,13 @@  discard block
 block discarded – undo
631 631
      */
632 632
     public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null)
633 633
     {
634
-        if (! $version_history) {
634
+        if ( ! $version_history) {
635 635
             $version_history = $this->fix_espresso_db_upgrade_option($version_history);
636 636
         }
637 637
         if ($current_version_to_add === null) {
638 638
             $current_version_to_add = espresso_version();
639 639
         }
640
-        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
640
+        $version_history[$current_version_to_add][] = date('Y-m-d H:i:s', time());
641 641
         // re-save
642 642
         return update_option('espresso_db_update', $version_history);
643 643
     }
@@ -727,7 +727,7 @@  discard block
 block discarded – undo
727 727
         if ($activation_history_for_addon) {
728 728
             // it exists, so this isn't a completely new install
729 729
             // check if this version already in that list of previously installed versions
730
-            if (! isset($activation_history_for_addon[ $version_to_upgrade_to ])) {
730
+            if ( ! isset($activation_history_for_addon[$version_to_upgrade_to])) {
731 731
                 // it a version we haven't seen before
732 732
                 if ($version_is_higher === 1) {
733 733
                     $req_type = EE_System::req_type_upgrade;
@@ -805,7 +805,7 @@  discard block
 block discarded – undo
805 805
             foreach ($activation_history as $version => $times_activated) {
806 806
                 // check there is a record of when this version was activated. Otherwise,
807 807
                 // mark it as unknown
808
-                if (! $times_activated) {
808
+                if ( ! $times_activated) {
809 809
                     $times_activated = array('unknown-date');
810 810
                 }
811 811
                 if (is_string($times_activated)) {
@@ -910,7 +910,7 @@  discard block
 block discarded – undo
910 910
     private function _parse_model_names()
911 911
     {
912 912
         // get all the files in the EE_MODELS folder that end in .model.php
913
-        $models = glob(EE_MODELS . '*.model.php');
913
+        $models = glob(EE_MODELS.'*.model.php');
914 914
         $model_names = array();
915 915
         $non_abstract_db_models = array();
916 916
         foreach ($models as $model) {
@@ -919,9 +919,9 @@  discard block
 block discarded – undo
919 919
             $short_name = str_replace('EEM_', '', $classname);
920 920
             $reflectionClass = new ReflectionClass($classname);
921 921
             if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
922
-                $non_abstract_db_models[ $short_name ] = $classname;
922
+                $non_abstract_db_models[$short_name] = $classname;
923 923
             }
924
-            $model_names[ $short_name ] = $classname;
924
+            $model_names[$short_name] = $classname;
925 925
         }
926 926
         $this->registry->models = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
927 927
         $this->registry->non_abstract_db_models = apply_filters(
@@ -956,7 +956,7 @@  discard block
 block discarded – undo
956 956
             )
957 957
         );
958 958
         if ($domain->isCaffeinated()) {
959
-            require_once EE_CAFF_PATH . 'brewing_regular.php';
959
+            require_once EE_CAFF_PATH.'brewing_regular.php';
960 960
         }
961 961
     }
962 962
 
@@ -1038,7 +1038,7 @@  discard block
 block discarded – undo
1038 1038
         $class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
1039 1039
             'AHEE__EE_System__register_shortcodes_modules_and_addons'
1040 1040
         );
1041
-        if (! empty($class_names)) {
1041
+        if ( ! empty($class_names)) {
1042 1042
             $msg = esc_html__(
1043 1043
                 'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
1044 1044
                 'event_espresso'
@@ -1050,7 +1050,7 @@  discard block
 block discarded – undo
1050 1050
                             array('EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'),
1051 1051
                             '',
1052 1052
                             $class_name
1053
-                        ) . '</b></li>';
1053
+                        ).'</b></li>';
1054 1054
             }
1055 1055
             $msg .= '</ul>';
1056 1056
             $msg .= esc_html__(
@@ -1119,7 +1119,7 @@  discard block
 block discarded – undo
1119 1119
     private function _deactivate_incompatible_addons()
1120 1120
     {
1121 1121
         $incompatible_addons = get_option('ee_incompatible_addons', array());
1122
-        if (! empty($incompatible_addons)) {
1122
+        if ( ! empty($incompatible_addons)) {
1123 1123
             $active_plugins = get_option('active_plugins', array());
1124 1124
             foreach ($active_plugins as $active_plugin) {
1125 1125
                 foreach ($incompatible_addons as $incompatible_addon) {
@@ -1232,7 +1232,7 @@  discard block
 block discarded – undo
1232 1232
         do_action('AHEE__EE_System__core_loaded_and_ready');
1233 1233
         // always load template tags, because it's faster than checking if it's a front-end request, and many page
1234 1234
         // builders require these even on the front-end
1235
-        require_once EE_PUBLIC . 'template_tags.php';
1235
+        require_once EE_PUBLIC.'template_tags.php';
1236 1236
         do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1237 1237
     }
1238 1238
 
@@ -1249,7 +1249,7 @@  discard block
 block discarded – undo
1249 1249
         do_action('AHEE__EE_System__initialize');
1250 1250
         add_filter(
1251 1251
             'safe_style_css',
1252
-            function ($styles) {
1252
+            function($styles) {
1253 1253
                 $styles[] = 'display';
1254 1254
                 $styles[] = 'visibility';
1255 1255
                 $styles[] = 'position';
@@ -1316,13 +1316,13 @@  discard block
 block discarded – undo
1316 1316
     public static function do_not_cache()
1317 1317
     {
1318 1318
         // set no cache constants
1319
-        if (! defined('DONOTCACHEPAGE')) {
1319
+        if ( ! defined('DONOTCACHEPAGE')) {
1320 1320
             define('DONOTCACHEPAGE', true);
1321 1321
         }
1322
-        if (! defined('DONOTCACHCEOBJECT')) {
1322
+        if ( ! defined('DONOTCACHCEOBJECT')) {
1323 1323
             define('DONOTCACHCEOBJECT', true);
1324 1324
         }
1325
-        if (! defined('DONOTCACHEDB')) {
1325
+        if ( ! defined('DONOTCACHEDB')) {
1326 1326
             define('DONOTCACHEDB', true);
1327 1327
         }
1328 1328
         // add no cache headers
Please login to merge, or discard this patch.
core/EE_Registry.core.php 1 patch
Indentation   +1717 added lines, -1717 removed lines patch added patch discarded remove patch
@@ -22,1721 +22,1721 @@
 block discarded – undo
22 22
  */
23 23
 class EE_Registry implements ResettableInterface
24 24
 {
25
-    /**
26
-     * @var EE_Registry $_instance
27
-     */
28
-    private static $_instance;
29
-
30
-    /**
31
-     * @var EE_Dependency_Map $_dependency_map
32
-     */
33
-    protected $_dependency_map;
34
-
35
-    /**
36
-     * @var Mirror
37
-     */
38
-    private $mirror;
39
-
40
-    /**
41
-     * @var ClassInterfaceCache $class_cache
42
-     */
43
-    private $class_cache;
44
-
45
-    /**
46
-     * @var array $_class_abbreviations
47
-     */
48
-    protected $_class_abbreviations = [];
49
-
50
-    /**
51
-     * @var CommandBusInterface $BUS
52
-     */
53
-    public $BUS;
54
-
55
-    /**
56
-     * @var EE_Cart $CART
57
-     */
58
-    public $CART;
59
-
60
-    /**
61
-     * @var EE_Config $CFG
62
-     */
63
-    public $CFG;
64
-
65
-    /**
66
-     * @var EE_Network_Config $NET_CFG
67
-     */
68
-    public $NET_CFG;
69
-
70
-    /**
71
-     * RegistryContainer for storing library classes in
72
-     *
73
-     * @var RegistryContainer $LIB
74
-     */
75
-    public $LIB;
76
-
77
-    /**
78
-     * @var EE_Request_Handler $REQ
79
-     * @deprecated 4.10.14.p
80
-     */
81
-    public $REQ;
82
-
83
-    /**
84
-     * @var EE_Session $SSN
85
-     */
86
-    public $SSN;
87
-
88
-    /**
89
-     * @since 4.5.0
90
-     * @var EE_Capabilities $CAP
91
-     */
92
-    public $CAP;
93
-
94
-    /**
95
-     * @since 4.9.0
96
-     * @var EE_Message_Resource_Manager $MRM
97
-     */
98
-    public $MRM;
99
-
100
-    /**
101
-     * @var Registry $AssetsRegistry
102
-     */
103
-    public $AssetsRegistry;
104
-
105
-    /**
106
-     * RegistryContainer for holding addons which have registered themselves to work with EE core
107
-     *
108
-     * @var RegistryContainer|EE_Addon[] $addons
109
-     */
110
-    public $addons;
111
-
112
-    /**
113
-     * keys are 'short names' (eg Event), values are class names (eg 'EEM_Event')
114
-     *
115
-     * @var EEM_Base[] $models
116
-     */
117
-    public $models = [];
118
-
119
-    /**
120
-     * @var RegistryContainer|EED_Module[] $modules
121
-     */
122
-    public $modules;
123
-
124
-    /**
125
-     * @var RegistryContainer|EES_Shortcode[] $shortcodes
126
-     */
127
-    public $shortcodes;
128
-
129
-    /**
130
-     * @var RegistryContainer|WP_Widget[] $widgets
131
-     */
132
-    public $widgets;
133
-
134
-    /**
135
-     * this is an array of all implemented model names (i.e. not the parent abstract models, or models
136
-     * which don't actually fetch items from the DB in the normal way (ie, are not children of EEM_Base)).
137
-     * Keys are model "short names" (eg "Event") as used in model relations, and values are
138
-     * classnames (eg "EEM_Event")
139
-     *
140
-     * @var array $non_abstract_db_models
141
-     */
142
-    public $non_abstract_db_models = [];
143
-
144
-    /**
145
-     * internationalization for JS strings
146
-     *    usage:   EE_Registry::i18n_js_strings['string_key'] = esc_html__( 'string to translate.', 'event_espresso' );
147
-     *    in js file:  var translatedString = eei18n.string_key;
148
-     *
149
-     * @var array $i18n_js_strings
150
-     */
151
-    public static $i18n_js_strings = [];
152
-
153
-    /**
154
-     * $main_file - path to espresso.php
155
-     *
156
-     * @var array $main_file
157
-     */
158
-    public $main_file;
159
-
160
-    /**
161
-     * array of ReflectionClass objects where the key is the class name
162
-     *
163
-     * @deprecated 4.9.62.p
164
-     * @var ReflectionClass[] $_reflectors
165
-     */
166
-    public $_reflectors;
167
-
168
-    /**
169
-     * boolean flag to indicate whether or not to load/save dependencies from/to the cache
170
-     *
171
-     * @var boolean $_cache_on
172
-     */
173
-    protected $_cache_on = true;
174
-
175
-    /**
176
-     * @var ObjectIdentifier
177
-     */
178
-    private $object_identifier;
179
-
180
-
181
-    /**
182
-     * @singleton method used to instantiate class object
183
-     * @param EE_Dependency_Map|null   $dependency_map
184
-     * @param Mirror|null              $mirror
185
-     * @param ClassInterfaceCache|null $class_cache
186
-     * @param ObjectIdentifier|null    $object_identifier
187
-     * @return EE_Registry instance
188
-     */
189
-    public static function instance(
190
-        EE_Dependency_Map $dependency_map = null,
191
-        Mirror $mirror = null,
192
-        ClassInterfaceCache $class_cache = null,
193
-        ObjectIdentifier $object_identifier = null
194
-    ): EE_Registry {
195
-        // check if class object is instantiated
196
-        if (
197
-            ! self::$_instance instanceof EE_Registry
198
-            && $dependency_map instanceof EE_Dependency_Map
199
-            && $mirror instanceof Mirror
200
-            && $class_cache instanceof ClassInterfaceCache
201
-            && $object_identifier instanceof ObjectIdentifier
202
-        ) {
203
-            self::$_instance = new self(
204
-                $dependency_map,
205
-                $mirror,
206
-                $class_cache,
207
-                $object_identifier
208
-            );
209
-        }
210
-        return self::$_instance;
211
-    }
212
-
213
-
214
-    /**
215
-     * protected constructor to prevent direct creation
216
-     *
217
-     * @Constructor
218
-     * @param EE_Dependency_Map   $dependency_map
219
-     * @param Mirror              $mirror
220
-     * @param ClassInterfaceCache $class_cache
221
-     * @param ObjectIdentifier    $object_identifier
222
-     */
223
-    protected function __construct(
224
-        EE_Dependency_Map $dependency_map,
225
-        Mirror $mirror,
226
-        ClassInterfaceCache $class_cache,
227
-        ObjectIdentifier $object_identifier
228
-    ) {
229
-        $this->_dependency_map   = $dependency_map;
230
-        $this->mirror            = $mirror;
231
-        $this->class_cache       = $class_cache;
232
-        $this->object_identifier = $object_identifier;
233
-        // $registry_container = new RegistryContainer();
234
-        $this->LIB        = new RegistryContainer();
235
-        $this->addons     = new RegistryContainer();
236
-        $this->modules    = new RegistryContainer();
237
-        $this->shortcodes = new RegistryContainer();
238
-        $this->widgets    = new RegistryContainer();
239
-        add_action('EE_Load_Espresso_Core__handle_request__initialize_core_loading', [$this, 'initialize']);
240
-    }
241
-
242
-
243
-    /**
244
-     * initialize
245
-     *
246
-     * @throws OutOfBoundsException
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidInterfaceException
249
-     * @throws InvalidDataTypeException
250
-     * @throws EE_Error
251
-     * @throws ReflectionException
252
-     */
253
-    public function initialize()
254
-    {
255
-        $this->_class_abbreviations = apply_filters(
256
-            'FHEE__EE_Registry____construct___class_abbreviations',
257
-            [
258
-                'EE_Config'                                       => 'CFG',
259
-                'EE_Session'                                      => 'SSN',
260
-                'EE_Capabilities'                                 => 'CAP',
261
-                'EE_Cart'                                         => 'CART',
262
-                'EE_Network_Config'                               => 'NET_CFG',
263
-                'EE_Request_Handler'                              => 'REQ',
264
-                'EE_Message_Resource_Manager'                     => 'MRM',
265
-                'EventEspresso\core\services\commands\CommandBus' => 'BUS',
266
-                'EventEspresso\core\services\assets\Registry'     => 'AssetsRegistry',
267
-            ]
268
-        );
269
-        $this->load_core('Base', [], true);
270
-        // add our request and response objects to the cache
271
-        $request_loader = $this->_dependency_map->class_loader(
272
-            'EventEspresso\core\services\request\Request'
273
-        );
274
-        $this->_set_cached_class(
275
-            $request_loader(),
276
-            'EventEspresso\core\services\request\Request'
277
-        );
278
-        $response_loader = $this->_dependency_map->class_loader(
279
-            'EventEspresso\core\services\request\Response'
280
-        );
281
-        $this->_set_cached_class(
282
-            $response_loader(),
283
-            'EventEspresso\core\services\request\Response'
284
-        );
285
-        add_action('AHEE__EE_System__set_hooks_for_core', [$this, 'init']);
286
-    }
287
-
288
-
289
-    /**
290
-     * @return void
291
-     */
292
-    public function init()
293
-    {
294
-        // Get current page protocol
295
-        $protocol = is_ssl() ? 'https://' : 'http://';
296
-        // Output admin-ajax.php URL with same protocol as current page
297
-        self::$i18n_js_strings['ajax_url'] = admin_url('admin-ajax.php', $protocol);
298
-        self::$i18n_js_strings['wp_debug'] = defined('WP_DEBUG') && WP_DEBUG;
299
-    }
300
-
301
-
302
-    /**
303
-     * @return array
304
-     */
305
-    public static function sanitize_i18n_js_strings(): array
306
-    {
307
-        $i18n_js_strings = (array) self::$i18n_js_strings;
308
-        foreach ($i18n_js_strings as $key => $value) {
309
-            if (is_scalar($value)) {
310
-                $decoded_value           = html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
311
-                $i18n_js_strings[ $key ] = wp_strip_all_tags($decoded_value);
312
-            }
313
-        }
314
-        return $i18n_js_strings;
315
-    }
316
-
317
-
318
-    /**
319
-     * localize_i18n_js_strings
320
-     *
321
-     * @return string
322
-     */
323
-    public static function localize_i18n_js_strings(): string
324
-    {
325
-        $i18n_js_strings = EE_Registry::sanitize_i18n_js_strings();
326
-        return '/* <![CDATA[ */ var eei18n = ' . wp_json_encode($i18n_js_strings) . '; /* ]]> */';
327
-    }
328
-
329
-
330
-    /**
331
-     * @param mixed string | EED_Module $module
332
-     * @throws OutOfBoundsException
333
-     * @throws InvalidArgumentException
334
-     * @throws InvalidInterfaceException
335
-     * @throws InvalidDataTypeException
336
-     * @throws EE_Error
337
-     * @throws ReflectionException
338
-     */
339
-    public function add_module($module)
340
-    {
341
-        if ($module instanceof EED_Module) {
342
-            $module_class = get_class($module);
343
-            $this->modules->add($module_class, $module);
344
-        } else {
345
-            if (! class_exists('EE_Module_Request_Router', false)) {
346
-                $this->load_core('Module_Request_Router');
347
-            }
348
-            EE_Module_Request_Router::module_factory($module);
349
-        }
350
-    }
351
-
352
-
353
-    /**
354
-     * @param string $module_name
355
-     * @return mixed EED_Module | NULL
356
-     */
357
-    public function get_module(string $module_name = '')
358
-    {
359
-        return $this->modules->get($module_name);
360
-    }
361
-
362
-
363
-    /**
364
-     * loads core classes - must be singletons
365
-     *
366
-     * @param string $class_name - simple class name ie: session
367
-     * @param mixed  $arguments
368
-     * @param bool   $load_only
369
-     * @return bool|null|object
370
-     * @throws InvalidInterfaceException
371
-     * @throws InvalidDataTypeException
372
-     * @throws EE_Error
373
-     * @throws ReflectionException
374
-     * @throws InvalidArgumentException
375
-     */
376
-    public function load_core(string $class_name, $arguments = [], bool $load_only = false)
377
-    {
378
-        $core_paths = (array) apply_filters(
379
-            'FHEE__EE_Registry__load_core__core_paths',
380
-            [
381
-                EE_CORE,
382
-                EE_ADMIN,
383
-                EE_CPTS,
384
-                EE_CORE . 'CPTs/',
385
-                EE_CORE . 'data_migration_scripts/',
386
-                EE_CORE . 'request_stack/',
387
-                EE_CORE . 'middleware/',
388
-            ]
389
-        );
390
-        // retrieve instantiated class
391
-        return $this->_load(
392
-            $core_paths,
393
-            'EE_',
394
-            $class_name,
395
-            'core',
396
-            $arguments,
397
-            false,
398
-            true,
399
-            $load_only
400
-        );
401
-    }
402
-
403
-
404
-    /**
405
-     * loads service classes
406
-     *
407
-     * @param string $class_name - simple class name ie: session
408
-     * @param mixed  $arguments
409
-     * @param bool   $load_only
410
-     * @return bool|null|object
411
-     * @throws InvalidInterfaceException
412
-     * @throws InvalidDataTypeException
413
-     * @throws EE_Error
414
-     * @throws ReflectionException
415
-     * @throws InvalidArgumentException
416
-     * @deprecated  4.10.33.p
417
-     */
418
-    public function load_service(string $class_name, $arguments = [], bool $load_only = false)
419
-    {
420
-        $service_paths = (array) apply_filters(
421
-            'FHEE__EE_Registry__load_service__service_paths',
422
-            [
423
-                EE_CORE . 'services/',
424
-            ]
425
-        );
426
-        // retrieve instantiated class
427
-        return $this->_load(
428
-            $service_paths,
429
-            'EE_',
430
-            $class_name,
431
-            'class',
432
-            $arguments,
433
-            false,
434
-            true,
435
-            $load_only
436
-        );
437
-    }
438
-
439
-
440
-    /**
441
-     * loads data_migration_scripts
442
-     *
443
-     * @param string $class_name - class name for the DMS ie: EE_DMS_Core_4_2_0
444
-     * @param mixed  $arguments
445
-     * @return bool|null|object
446
-     * @throws InvalidInterfaceException
447
-     * @throws InvalidDataTypeException
448
-     * @throws EE_Error
449
-     * @throws ReflectionException
450
-     * @throws InvalidArgumentException
451
-     */
452
-    public function load_dms(string $class_name, $arguments = [])
453
-    {
454
-        // retrieve instantiated class
455
-        return $this->_load(
456
-            EE_Data_Migration_Manager::instance()->get_data_migration_script_folders(),
457
-            'EE_DMS_',
458
-            $class_name,
459
-            'dms',
460
-            $arguments,
461
-            false,
462
-            false
463
-        );
464
-    }
465
-
466
-
467
-    /**
468
-     * loads object creating classes - must be singletons
469
-     *
470
-     * @param string $class_name - simple class name ie: attendee
471
-     * @param mixed  $arguments  - an array of arguments to pass to the class
472
-     * @param bool   $from_db    - some classes are instantiated from the db and thus call a different method to
473
-     *                           instantiate
474
-     * @param bool   $cache      if you don't want the class to be stored in the internal cache (non-persistent) then
475
-     *                           set this to FALSE (ie. when instantiating model objects from client in a loop)
476
-     * @param bool   $load_only  whether or not to just load the file and NOT instantiate, or load AND instantiate
477
-     *                           (default)
478
-     * @return bool|null|object
479
-     * @throws InvalidInterfaceException
480
-     * @throws InvalidDataTypeException
481
-     * @throws EE_Error
482
-     * @throws ReflectionException
483
-     * @throws InvalidArgumentException
484
-     */
485
-    public function load_class(
486
-        string $class_name,
487
-        $arguments = [],
488
-        bool $from_db = false,
489
-        bool $cache = true,
490
-        bool $load_only = false
491
-    ) {
492
-        $paths = (array) apply_filters(
493
-            'FHEE__EE_Registry__load_class__paths',
494
-            [
495
-                EE_CORE,
496
-                EE_CLASSES,
497
-                EE_BUSINESS,
498
-            ]
499
-        );
500
-        // retrieve instantiated class
501
-        return $this->_load(
502
-            $paths,
503
-            'EE_',
504
-            $class_name,
505
-            'class',
506
-            $arguments,
507
-            $from_db,
508
-            $cache,
509
-            $load_only
510
-        );
511
-    }
512
-
513
-
514
-    /**
515
-     * loads helper classes - must be singletons
516
-     *
517
-     * @param string $class_name - simple class name ie: price
518
-     * @param mixed  $arguments
519
-     * @param bool   $load_only
520
-     * @return bool|null|object
521
-     * @throws InvalidInterfaceException
522
-     * @throws InvalidDataTypeException
523
-     * @throws EE_Error
524
-     * @throws ReflectionException
525
-     * @throws InvalidArgumentException
526
-     */
527
-    public function load_helper(string $class_name, $arguments = [], bool $load_only = true)
528
-    {
529
-        // todo: add doing_it_wrong() in a few versions after all addons have had calls to this method removed
530
-        $helper_paths = (array) apply_filters('FHEE__EE_Registry__load_helper__helper_paths', [EE_HELPERS]);
531
-        // retrieve instantiated class
532
-        return $this->_load(
533
-            $helper_paths,
534
-            'EEH_',
535
-            $class_name,
536
-            'helper',
537
-            $arguments,
538
-            false,
539
-            true,
540
-            $load_only
541
-        );
542
-    }
543
-
544
-
545
-    /**
546
-     * loads core classes - must be singletons
547
-     *
548
-     * @param string $class_name - simple class name ie: session
549
-     * @param mixed  $arguments
550
-     * @param bool   $load_only
551
-     * @param bool   $cache      whether to cache the object or not.
552
-     * @return bool|null|object
553
-     * @throws InvalidInterfaceException
554
-     * @throws InvalidDataTypeException
555
-     * @throws EE_Error
556
-     * @throws ReflectionException
557
-     * @throws InvalidArgumentException
558
-     */
559
-    public function load_lib(string $class_name, $arguments = [], bool $load_only = false, bool $cache = true)
560
-    {
561
-        $paths = [
562
-            EE_LIBRARIES,
563
-            EE_LIBRARIES . 'messages/',
564
-            EE_LIBRARIES . 'shortcodes/',
565
-            EE_LIBRARIES . 'qtips/',
566
-            EE_LIBRARIES . 'payment_methods/',
567
-        ];
568
-        // retrieve instantiated class
569
-        return $this->_load(
570
-            $paths,
571
-            'EE_',
572
-            $class_name,
573
-            'lib',
574
-            $arguments,
575
-            false,
576
-            $cache,
577
-            $load_only
578
-        );
579
-    }
580
-
581
-
582
-    /**
583
-     * loads model classes - must be singletons
584
-     *
585
-     * @param string $class_name - simple class name ie: price
586
-     * @param mixed  $arguments
587
-     * @param bool   $load_only
588
-     * @return EEM_Base|bool|null
589
-     * @throws InvalidInterfaceException
590
-     * @throws InvalidDataTypeException
591
-     * @throws EE_Error
592
-     * @throws ReflectionException
593
-     * @throws InvalidArgumentException
594
-     */
595
-    public function load_model(string $class_name, $arguments = [], bool $load_only = false)
596
-    {
597
-        $paths = (array) apply_filters(
598
-            'FHEE__EE_Registry__load_model__paths',
599
-            [
600
-                EE_MODELS,
601
-                EE_CORE,
602
-            ]
603
-        );
604
-        // retrieve instantiated class
605
-        return $this->_load(
606
-            $paths,
607
-            'EEM_',
608
-            $class_name,
609
-            'model',
610
-            $arguments,
611
-            false,
612
-            true,
613
-            $load_only
614
-        );
615
-    }
616
-
617
-
618
-    /**
619
-     * loads model classes - must be singletons
620
-     *
621
-     * @param string $class_name - simple class name ie: price
622
-     * @param mixed  $arguments
623
-     * @param bool   $load_only
624
-     * @return bool|null|object
625
-     * @throws InvalidInterfaceException
626
-     * @throws InvalidDataTypeException
627
-     * @throws EE_Error
628
-     * @throws ReflectionException
629
-     * @throws InvalidArgumentException
630
-     * @deprecated  4.10.33.p
631
-     */
632
-    public function load_model_class(string $class_name, $arguments = [], bool $load_only = true)
633
-    {
634
-        $paths = [
635
-            EE_MODELS . 'fields/',
636
-            EE_MODELS . 'helpers/',
637
-            EE_MODELS . 'relations/',
638
-            EE_MODELS . 'strategies/',
639
-        ];
640
-        // retrieve instantiated class
641
-        return $this->_load(
642
-            $paths,
643
-            'EE_',
644
-            $class_name,
645
-            '',
646
-            $arguments,
647
-            false,
648
-            true,
649
-            $load_only
650
-        );
651
-    }
652
-
653
-
654
-    /**
655
-     * Determines if $model_name is the name of an actual EE model.
656
-     *
657
-     * @param string $model_name like Event, Attendee, Question_Group_Question, etc.
658
-     * @return boolean
659
-     */
660
-    public function is_model_name(string $model_name): bool
661
-    {
662
-        return isset($this->models[ $model_name ]);
663
-    }
664
-
665
-
666
-    /**
667
-     * generic class loader
668
-     *
669
-     * @param string $path_to_file - directory path to file location, not including filename
670
-     * @param string $file_name    - file name  ie:  my_file.php, including extension
671
-     * @param string $type         - file type - core? class? helper? model?
672
-     * @param mixed  $arguments
673
-     * @param bool   $load_only
674
-     * @return bool|null|object
675
-     * @throws InvalidInterfaceException
676
-     * @throws InvalidDataTypeException
677
-     * @throws EE_Error
678
-     * @throws ReflectionException
679
-     * @throws InvalidArgumentException
680
-     */
681
-    public function load_file(
682
-        string $path_to_file,
683
-        string $file_name,
684
-        string $type = '',
685
-        $arguments = [],
686
-        bool $load_only = true
687
-    ) {
688
-        // retrieve instantiated class
689
-        return $this->_load(
690
-            (array) $path_to_file,
691
-            '',
692
-            $file_name,
693
-            $type,
694
-            $arguments,
695
-            false,
696
-            true,
697
-            $load_only
698
-        );
699
-    }
700
-
701
-
702
-    /**
703
-     * @param string $path_to_file - directory path to file location, not including filename
704
-     * @param string $class_name   - full class name  ie:  My_Class
705
-     * @param string $type         - file type - core? class? helper? model?
706
-     * @param mixed  $arguments
707
-     * @param bool   $load_only
708
-     * @return bool|null|object
709
-     * @throws InvalidInterfaceException
710
-     * @throws InvalidDataTypeException
711
-     * @throws EE_Error
712
-     * @throws ReflectionException
713
-     * @throws InvalidArgumentException
714
-     * @deprecated  4.10.33.p
715
-     */
716
-    public function load_addon(
717
-        string $path_to_file,
718
-        string $class_name,
719
-        string $type = 'class',
720
-        $arguments = [],
721
-        bool $load_only = false
722
-    ) {
723
-        // retrieve instantiated class
724
-        return $this->_load(
725
-            (array) $path_to_file,
726
-            'addon',
727
-            $class_name,
728
-            $type,
729
-            $arguments,
730
-            false,
731
-            true,
732
-            $load_only
733
-        );
734
-    }
735
-
736
-
737
-    /**
738
-     * instantiates, caches, and automatically resolves dependencies
739
-     * for classes that use a Fully Qualified Class Name.
740
-     * if the class is not capable of being loaded using PSR-4 autoloading,
741
-     * then you need to use one of the existing load_*() methods
742
-     * which can resolve the classname and filepath from the passed arguments
743
-     *
744
-     * @param string      $class_name Fully Qualified Class Name
745
-     * @param array       $arguments  an argument, or array of arguments to pass to the class upon instantiation
746
-     * @param bool        $cache      whether to cache the instantiated object for reuse
747
-     * @param bool        $from_db    some classes are instantiated from the db
748
-     *                                and thus call a different method to instantiate
749
-     * @param bool        $load_only  if true, will only load the file, but will NOT instantiate an object
750
-     * @param bool|string $addon      if true, will cache the object in the EE_Registry->$addons array
751
-     * @return bool|null|mixed     null = failure to load or instantiate class object.
752
-     *                                object = class loaded and instantiated successfully.
753
-     *                                bool = fail or success when $load_only is true
754
-     * @throws InvalidInterfaceException
755
-     * @throws InvalidDataTypeException
756
-     * @throws EE_Error
757
-     * @throws ReflectionException
758
-     * @throws InvalidArgumentException
759
-     */
760
-    public function create(
761
-        string $class_name = '',
762
-        array $arguments = [],
763
-        bool $cache = false,
764
-        bool $from_db = false,
765
-        bool $load_only = false,
766
-        bool $addon = false
767
-    ) {
768
-        $class_name   = ltrim($class_name, '\\');
769
-        $class_name   = $this->class_cache->getFqnForAlias($class_name);
770
-        $class_exists = $this->loadOrVerifyClassExists($class_name, $arguments);
771
-        // if a non-FQCN was passed, then
772
-        // verifyClassExists() might return an object
773
-        // or it could return null if the class just could not be found anywhere
774
-        if ($class_exists instanceof $class_name || $class_exists === null) {
775
-            // either way, return the results
776
-            return $class_exists;
777
-        }
778
-        $class_name = $class_exists;
779
-        // if we're only loading the class and it already exists, then let's just return true immediately
780
-        if ($load_only) {
781
-            return true;
782
-        }
783
-        $addon = $addon ? 'addon' : '';
784
-        // $this->_cache_on is toggled during the recursive loading that can occur with dependency injection
785
-        // $cache is controlled by individual calls to separate Registry loader methods like load_class()
786
-        // $load_only is also controlled by individual calls to separate Registry loader methods like load_file()
787
-        if ($this->_cache_on && $cache) {
788
-            // return object if it's already cached
789
-            $cached_class = $this->_get_cached_class($class_name, $addon, $arguments);
790
-            if ($cached_class !== null) {
791
-                return $cached_class;
792
-            }
793
-        }                                                           // obtain the loader method from the dependency map
794
-        $loader = $this->_dependency_map->class_loader($class_name);// instantiate the requested object
795
-        if ($loader instanceof Closure) {
796
-            $class_obj = $loader($arguments);
797
-        } else {
798
-            if ($loader && method_exists($this, $loader)) {
799
-                $class_obj = $this->{$loader}($class_name, $arguments);
800
-            } else {
801
-                $class_obj = $this->_create_object($class_name, $arguments, $addon, $from_db);
802
-            }
803
-        }
804
-        if (($this->_cache_on && $cache) || $this->get_class_abbreviation($class_name, '')) {
805
-            // save it for later... kinda like gum  { : $
806
-            $this->_set_cached_class(
807
-                $class_obj,
808
-                $class_name,
809
-                $addon,
810
-                $from_db,
811
-                $arguments
812
-            );
813
-        }
814
-        $this->_cache_on = true;
815
-        return $class_obj;
816
-    }
817
-
818
-
819
-    /**
820
-     * Recursively checks that a class exists and potentially attempts to load classes with non-FQCNs
821
-     *
822
-     * @param string|mixed $class_name
823
-     * @param array        $arguments
824
-     * @param int          $attempt
825
-     * @return mixed
826
-     */
827
-    private function loadOrVerifyClassExists($class_name, array $arguments, int $attempt = 1)
828
-    {
829
-        if (is_object($class_name) || class_exists($class_name)) {
830
-            return $class_name;
831
-        }
832
-        switch ($attempt) {
833
-            case 1:
834
-                // if it's a FQCN then maybe the class is registered with a preceding \
835
-                $class_name = strpos($class_name, '\\') !== false
836
-                    ? '\\' . ltrim($class_name, '\\')
837
-                    : $class_name;
838
-                break;
839
-            case 2:
840
-                //
841
-                $loader = $this->_dependency_map->class_loader($class_name);
842
-                if ($loader && method_exists($this, $loader)) {
843
-                    return $this->{$loader}($class_name, $arguments);
844
-                }
845
-                break;
846
-            case 3:
847
-            default:
848
-                return null;
849
-        }
850
-        $attempt++;
851
-        return $this->loadOrVerifyClassExists($class_name, $arguments, $attempt);
852
-    }
853
-
854
-
855
-    /**
856
-     * instantiates, caches, and injects dependencies for classes
857
-     *
858
-     * @param array  $file_paths         an array of paths to folders to look in
859
-     * @param string $class_prefix       EE  or EEM or... ???
860
-     * @param string $class_name         $class name
861
-     * @param string $type               file type - core? class? helper? model?
862
-     * @param mixed  $arguments          an argument or array of arguments to pass to the class upon instantiation
863
-     * @param bool   $from_db            some classes are instantiated from the db
864
-     *                                   and thus call a different method to instantiate
865
-     * @param bool   $cache              whether to cache the instantiated object for reuse
866
-     * @param bool   $load_only          if true, will only load the file, but will NOT instantiate an object
867
-     * @return bool|null|mixed           null   = failure to load or instantiate class object.
868
-     *                                   mixed = class loaded and instantiated successfully.
869
-     *                                   bool   = fail or success when $load_only is true
870
-     * @throws EE_Error
871
-     * @throws ReflectionException
872
-     * @throws InvalidInterfaceException
873
-     * @throws InvalidDataTypeException
874
-     * @throws InvalidArgumentException
875
-     */
876
-    protected function _load(
877
-        array $file_paths = [],
878
-        string $class_prefix = 'EE_',
879
-        string $class_name = '',
880
-        string $type = 'class',
881
-        array $arguments = [],
882
-        bool $from_db = false,
883
-        bool $cache = true,
884
-        bool $load_only = false
885
-    ) {
886
-        $class_name = ltrim($class_name, '\\');
887
-        // strip php file extension
888
-        $class_name = str_replace('.php', '', trim($class_name));
889
-        // does the class have a prefix ?
890
-        if (! empty($class_prefix) && $class_prefix !== 'addon') {
891
-            // make sure $class_prefix is uppercase
892
-            $class_prefix = strtoupper(trim($class_prefix));
893
-            // add class prefix ONCE!!!
894
-            $class_name = $class_prefix . str_replace($class_prefix, '', $class_name);
895
-        }
896
-        $class_name   = $this->class_cache->getFqnForAlias($class_name);
897
-        $class_exists = class_exists($class_name, false);
898
-        // if we're only loading the class and it already exists, then let's just return true immediately
899
-        if ($load_only && $class_exists) {
900
-            return true;
901
-        }
902
-        $arguments = is_array($arguments) ? $arguments : [$arguments];
903
-        // $this->_cache_on is toggled during the recursive loading that can occur with dependency injection
904
-        // $cache is controlled by individual calls to separate Registry loader methods like load_class()
905
-        // $load_only is also controlled by individual calls to separate Registry loader methods like load_file()
906
-        if ($this->_cache_on && $cache && ! $load_only) {
907
-            // return object if it's already cached
908
-            $cached_class = $this->_get_cached_class($class_name, $class_prefix, $arguments);
909
-            if ($cached_class !== null) {
910
-                return $cached_class;
911
-            }
912
-        }
913
-        // if the class doesn't already exist.. then we need to try and find the file and load it
914
-        if (! $class_exists) {
915
-            // get full path to file
916
-            $path = $this->_resolve_path($class_name, $type, $file_paths);
917
-            // load the file
918
-            $loaded = $this->_require_file($path, $class_name, $type, $file_paths);
919
-            // if we are only loading a file but NOT instantiating an object
920
-            // then return boolean for whether class was loaded or not
921
-            if ($load_only) {
922
-                return $loaded;
923
-            }
924
-            // if an object was expected but loading failed, then return nothing
925
-            if (! $loaded) {
926
-                return null;
927
-            }
928
-        }
929
-        // instantiate the requested object
930
-        $class_obj = $this->_create_object($class_name, $arguments, $type, $from_db);
931
-        if ($this->_cache_on && $cache) {
932
-            // save it for later... kinda like gum  { : $
933
-            $this->_set_cached_class(
934
-                $class_obj,
935
-                $class_name,
936
-                $class_prefix,
937
-                $from_db,
938
-                $arguments
939
-            );
940
-        }
941
-        $this->_cache_on = true;
942
-        return $class_obj;
943
-    }
944
-
945
-
946
-    /**
947
-     * @param string $class_name
948
-     * @param string $default have to specify something, but not anything that will conflict
949
-     * @return mixed|string
950
-     */
951
-    protected function get_class_abbreviation(string $class_name, string $default = 'FANCY_BATMAN_PANTS')
952
-    {
953
-        return $this->_class_abbreviations[ $class_name ] ?? $default;
954
-    }
955
-
956
-
957
-    /**
958
-     * attempts to find a cached version of the requested class
959
-     * by looking in the following places:
960
-     *        $this->{$class_abbreviation}            ie:    $this->CART
961
-     *        $this->{$class_name}                        ie:    $this->Some_Class
962
-     *        $this->LIB->{$class_name}                ie:    $this->LIB->Some_Class
963
-     *        $this->addon->{$class_name}    ie:    $this->addon->Some_Addon_Class
964
-     *
965
-     * @param string $class_name
966
-     * @param string $class_prefix
967
-     * @param array  $arguments
968
-     * @return mixed
969
-     */
970
-    protected function _get_cached_class(
971
-        string $class_name,
972
-        string $class_prefix = '',
973
-        array $arguments = []
974
-    ) {
975
-        if ($class_name === 'EE_Registry') {
976
-            return $this;
977
-        }
978
-        $class_abbreviation = $this->get_class_abbreviation($class_name);
979
-        // check if class has already been loaded, and return it if it has been
980
-        if (isset($this->{$class_abbreviation})) {
981
-            return $this->{$class_abbreviation};
982
-        }
983
-        $class_name = str_replace('\\', '_', $class_name);
984
-        if (isset($this->{$class_name})) {
985
-            return $this->{$class_name};
986
-        }
987
-        if ($class_prefix === 'addon' && $this->addons->has($class_name)) {
988
-            return $this->addons->get($class_name);
989
-        }
990
-        $object_identifier = $this->object_identifier->getIdentifier($class_name, $arguments);
991
-        if ($this->LIB->has($object_identifier)) {
992
-            return $this->LIB->get($object_identifier);
993
-        }
994
-        foreach ($this->LIB as $key => $object) {
995
-            if (
996
-                // request does not contain new arguments and therefore no args identifier
997
-                ! $this->object_identifier->hasArguments($object_identifier)
998
-                // but previously cached class with args was found
999
-                && $this->object_identifier->fqcnMatchesObjectIdentifier($class_name, $key)
1000
-            ) {
1001
-                return $object;
1002
-            }
1003
-        }
1004
-        return null;
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * removes a cached version of the requested class
1010
-     *
1011
-     * @param string  $class_name
1012
-     * @param boolean $addon
1013
-     * @param array   $arguments
1014
-     * @return boolean
1015
-     */
1016
-    public function clear_cached_class(
1017
-        string $class_name,
1018
-        bool $addon = false,
1019
-        array $arguments = []
1020
-    ): bool {
1021
-        $class_abbreviation = $this->get_class_abbreviation($class_name);
1022
-        // check if class has already been loaded, and return it if it has been
1023
-        if (isset($this->{$class_abbreviation})) {
1024
-            $this->{$class_abbreviation} = null;
1025
-            return true;
1026
-        }
1027
-        $class_name = str_replace('\\', '_', $class_name);
1028
-        if (isset($this->{$class_name})) {
1029
-            $this->{$class_name} = null;
1030
-            return true;
1031
-        }
1032
-        if ($addon && $this->addons->has($class_name)) {
1033
-            $this->addons->remove($class_name);
1034
-            return true;
1035
-        }
1036
-        $class_name = $this->object_identifier->getIdentifier($class_name, $arguments);
1037
-        if ($this->LIB->has($class_name)) {
1038
-            $this->LIB->remove($class_name);
1039
-            return true;
1040
-        }
1041
-        return false;
1042
-    }
1043
-
1044
-
1045
-    /**
1046
-     * _set_cached_class
1047
-     * attempts to cache the instantiated class locally
1048
-     * in one of the following places, in the following order:
1049
-     *        $this->{class_abbreviation}   ie:    $this->CART
1050
-     *        $this->{$class_name}          ie:    $this->Some_Class
1051
-     *        $this->addon->{$$class_name}    ie:    $this->addon->Some_Addon_Class
1052
-     *        $this->LIB->{$class_name}     ie:    $this->LIB->Some_Class
1053
-     *
1054
-     * @param object $class_obj
1055
-     * @param string $class_name
1056
-     * @param string $class_prefix
1057
-     * @param bool   $from_db
1058
-     * @param array  $arguments
1059
-     * @return void
1060
-     */
1061
-    protected function _set_cached_class(
1062
-        $class_obj,
1063
-        string $class_name,
1064
-        string $class_prefix = '',
1065
-        bool $from_db = false,
1066
-        array $arguments = []
1067
-    ) {
1068
-        if ($class_name === 'EE_Registry' || empty($class_obj)) {
1069
-            return;
1070
-        }
1071
-        // return newly instantiated class
1072
-        $class_abbreviation = $this->get_class_abbreviation($class_name, '');
1073
-        if ($class_abbreviation) {
1074
-            $this->{$class_abbreviation} = $class_obj;
1075
-            return;
1076
-        }
1077
-        $class_name = str_replace('\\', '_', $class_name);
1078
-        if (property_exists($this, $class_name)) {
1079
-            $this->{$class_name} = $class_obj;
1080
-            return;
1081
-        }
1082
-        if ($class_prefix === 'addon') {
1083
-            $this->addons->add($class_name, $class_obj);
1084
-            return;
1085
-        }
1086
-        if (! $from_db) {
1087
-            $class_name = $this->object_identifier->getIdentifier($class_name, $arguments);
1088
-            $this->LIB->add($class_name, $class_obj);
1089
-        }
1090
-    }
1091
-
1092
-
1093
-    /**
1094
-     * attempts to find a full valid filepath for the requested class.
1095
-     * loops thru each of the base paths in the $file_paths array and appends : "{classname} . {file type} . php"
1096
-     * then returns that path if the target file has been found and is readable
1097
-     *
1098
-     * @param string $class_name
1099
-     * @param string $type
1100
-     * @param array  $file_paths
1101
-     * @return string | bool
1102
-     */
1103
-    protected function _resolve_path(string $class_name, string $type = '', array $file_paths = [])
1104
-    {
1105
-        // make sure $file_paths is an array
1106
-        $file_paths = is_array($file_paths)
1107
-            ? $file_paths
1108
-            : [$file_paths];
1109
-        // cycle thru paths
1110
-        foreach ($file_paths as $key => $file_path) {
1111
-            // convert all separators to proper /, if no filepath, then use EE_CLASSES
1112
-            $file_path = $file_path
1113
-                ? str_replace(['/', '\\'], '/', $file_path)
1114
-                : EE_CLASSES;
1115
-            // prep file type
1116
-            $type = ! empty($type)
1117
-                ? trim($type, '.') . '.'
1118
-                : '';
1119
-            // build full file path
1120
-            $file_paths[ $key ] = rtrim($file_path, '/') . '/' . $class_name . '.' . $type . 'php';
1121
-            // does the file exist and can be read ?
1122
-            if (is_readable($file_paths[ $key ])) {
1123
-                return $file_paths[ $key ];
1124
-            }
1125
-        }
1126
-        return false;
1127
-    }
1128
-
1129
-
1130
-    /**
1131
-     * basically just performs a require_once()
1132
-     * but with some error handling
1133
-     *
1134
-     * @param string $path
1135
-     * @param string $class_name
1136
-     * @param string $type
1137
-     * @param array  $file_paths
1138
-     * @return bool
1139
-     * @throws EE_Error
1140
-     * @throws ReflectionException
1141
-     */
1142
-    protected function _require_file(string $path, string $class_name, string $type = '', array $file_paths = []): bool
1143
-    {
1144
-        $this->resolve_legacy_class_parent($class_name);
1145
-        // don't give up! you gotta...
1146
-        try {
1147
-            // does the file exist and can it be read ?
1148
-            if (! $path) {
1149
-                // just in case the file has already been autoloaded,
1150
-                // but discrepancies in the naming schema are preventing it from
1151
-                // being loaded via one of the EE_Registry::load_*() methods,
1152
-                // then let's try one last hail mary before throwing an exception
1153
-                // and call class_exists() again, but with autoloading turned ON
1154
-                if (class_exists($class_name)) {
1155
-                    return true;
1156
-                }
1157
-                // so sorry, can't find the file
1158
-                throw new EE_Error(
1159
-                    sprintf(
1160
-                        esc_html__(
1161
-                            'The %1$s file %2$s could not be located or is not readable due to file permissions. Please ensure that the following filepath(s) are correct: %3$s',
1162
-                            'event_espresso'
1163
-                        ),
1164
-                        trim($type, '.'),
1165
-                        $class_name,
1166
-                        '<br />' . implode(',<br />', $file_paths)
1167
-                    )
1168
-                );
1169
-            }
1170
-            // get the file
1171
-            require_once($path);
1172
-            // if the class isn't already declared somewhere
1173
-            if (class_exists($class_name, false) === false) {
1174
-                // so sorry, not a class
1175
-                throw new EE_Error(
1176
-                    sprintf(
1177
-                        esc_html__(
1178
-                            'The %s file %s does not appear to contain the %s Class.',
1179
-                            'event_espresso'
1180
-                        ),
1181
-                        $type,
1182
-                        $path,
1183
-                        $class_name
1184
-                    )
1185
-                );
1186
-            }
1187
-        } catch (EE_Error $e) {
1188
-            $e->get_error();
1189
-            return false;
1190
-        }
1191
-        return true;
1192
-    }
1193
-
1194
-
1195
-    /**
1196
-     * Some of our legacy classes that extended a parent class would simply use a require() statement
1197
-     * before their class declaration in order to ensure that the parent class was loaded.
1198
-     * This is not ideal, but it's nearly impossible to determine the parent class of a non-namespaced class,
1199
-     * without triggering a fatal error because the parent class has yet to be loaded and therefore doesn't exist.
1200
-     *
1201
-     * @param string $class_name
1202
-     */
1203
-    protected function resolve_legacy_class_parent(string $class_name = '')
1204
-    {
1205
-        try {
1206
-            $legacy_parent_class_map = [
1207
-                'EE_Payment_Processor' => 'core/business/EE_Processor_Base.class.php',
1208
-            ];
1209
-            if (isset($legacy_parent_class_map[ $class_name ])) {
1210
-                require_once EE_PLUGIN_DIR_PATH . $legacy_parent_class_map[ $class_name ];
1211
-            }
1212
-        } catch (Exception $exception) {
1213
-        }
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * _create_object
1219
-     * Attempts to instantiate the requested class via any of the
1220
-     * commonly used instantiation methods employed throughout EE.
1221
-     * The priority for instantiation is as follows:
1222
-     *        - abstract classes or any class flagged as "load only" (no instantiation occurs)
1223
-     *        - model objects via their 'new_instance_from_db' method
1224
-     *        - model objects via their 'new_instance' method
1225
-     *        - "singleton" classes" via their 'instance' method
1226
-     *    - standard instantiable classes via their __constructor
1227
-     * Prior to instantiation, if the classname exists in the dependency_map,
1228
-     * then the constructor for the requested class will be examined to determine
1229
-     * if any dependencies exist, and if they can be injected.
1230
-     * If so, then those classes will be added to the array of arguments passed to the constructor
1231
-     *
1232
-     * @param string $class_name
1233
-     * @param array  $arguments
1234
-     * @param string $type
1235
-     * @param bool   $from_db
1236
-     * @return null|object|bool
1237
-     * @throws InvalidArgumentException
1238
-     * @throws InvalidInterfaceException
1239
-     * @throws EE_Error
1240
-     * @throws ReflectionException
1241
-     * @throws InvalidDataTypeException
1242
-     */
1243
-    protected function _create_object(
1244
-        string $class_name,
1245
-        array $arguments = [],
1246
-        string $type = '',
1247
-        bool $from_db = false
1248
-    ) {
1249
-        // create reflection
1250
-        $reflector = $this->mirror->getReflectionClass($class_name);
1251
-        // make sure arguments are an array
1252
-        $arguments = is_array($arguments)
1253
-            ? $arguments
1254
-            : [$arguments];
1255
-        // and if arguments array is numerically and sequentially indexed, then we want it to remain as is,
1256
-        // else wrap it in an additional array so that it doesn't get split into multiple parameters
1257
-        $arguments = $this->_array_is_numerically_and_sequentially_indexed($arguments)
1258
-            ? $arguments
1259
-            : [$arguments];
1260
-        // attempt to inject dependencies ?
1261
-        if ($this->_dependency_map->has($class_name)) {
1262
-            $arguments = $this->_resolve_dependencies($reflector, $class_name, $arguments);
1263
-        }
1264
-        // instantiate the class if possible
1265
-        if ($reflector->isAbstract()) {
1266
-            // nothing to instantiate, loading file was enough
1267
-            // does not throw an exception so $instantiation_mode is unused
1268
-            // $instantiation_mode = "1) no constructor abstract class";
1269
-            return true;
1270
-        }
1271
-        if (
1272
-            empty($arguments)
1273
-            && $this->mirror->getConstructorFromReflection($reflector) === null
1274
-            && $reflector->isInstantiable()
1275
-        ) {
1276
-            // no constructor = static methods only... nothing to instantiate, loading file was enough
1277
-            // $instantiation_mode = "2) no constructor but instantiable";
1278
-            return $reflector->newInstance();
1279
-        }
1280
-        if ($from_db && method_exists($class_name, 'new_instance_from_db')) {
1281
-            // $instantiation_mode = "3) new_instance_from_db()";
1282
-            return call_user_func_array([$class_name, 'new_instance_from_db'], $arguments);
1283
-        }
1284
-        if (method_exists($class_name, 'new_instance')) {
1285
-            // $instantiation_mode = "4) new_instance()";
1286
-            return call_user_func_array([$class_name, 'new_instance'], $arguments);
1287
-        }
1288
-        if (method_exists($class_name, 'instance')) {
1289
-            // $instantiation_mode = "5) instance()";
1290
-            return call_user_func_array([$class_name, 'instance'], $arguments);
1291
-        }
1292
-        if ($reflector->isInstantiable()) {
1293
-            // $instantiation_mode = "6) constructor";
1294
-            return $reflector->newInstanceArgs($arguments);
1295
-        }
1296
-        // heh ? something's not right !
1297
-        throw new EE_Error(
1298
-            sprintf(
1299
-                esc_html__('The %s file %s could not be instantiated.', 'event_espresso'),
1300
-                $type,
1301
-                $class_name
1302
-            )
1303
-        );
1304
-    }
1305
-
1306
-
1307
-    /**
1308
-     * @see http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
1309
-     * @param array $array
1310
-     * @return bool
1311
-     */
1312
-    protected function _array_is_numerically_and_sequentially_indexed(array $array): bool
1313
-    {
1314
-        return empty($array) || array_keys($array) === range(0, count($array) - 1);
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * _resolve_dependencies
1320
-     * examines the constructor for the requested class to determine
1321
-     * if any dependencies exist, and if they can be injected.
1322
-     * If so, then those classes will be added to the array of arguments passed to the constructor
1323
-     * PLZ NOTE: this is achieved by type hinting the constructor params
1324
-     * For example:
1325
-     *        if attempting to load a class "Foo" with the following constructor:
1326
-     *        __construct( Bar $bar_class, Fighter $grohl_class )
1327
-     *        then $bar_class and $grohl_class will be added to the $arguments array,
1328
-     *        but only IF they are NOT already present in the incoming arguments array,
1329
-     *        and the correct classes can be loaded
1330
-     *
1331
-     * @param ReflectionClass $reflector
1332
-     * @param string          $class_name
1333
-     * @param array           $arguments
1334
-     * @return array
1335
-     * @throws InvalidArgumentException
1336
-     * @throws InvalidDataTypeException
1337
-     * @throws InvalidInterfaceException
1338
-     * @throws ReflectionException
1339
-     */
1340
-    protected function _resolve_dependencies(
1341
-        ReflectionClass $reflector,
1342
-        string $class_name,
1343
-        array $arguments = []
1344
-    ): array {
1345
-        // let's examine the constructor
1346
-        $constructor = $this->mirror->getConstructorFromReflection($reflector);
1347
-        // whu? huh? nothing?
1348
-        if (! $constructor) {
1349
-            return $arguments;
1350
-        }
1351
-        // get constructor parameters
1352
-        $params = $this->mirror->getParametersFromReflection($reflector);
1353
-        // and the keys for the incoming arguments array so that we can compare existing arguments with what is expected
1354
-        $argument_keys = array_keys($arguments);
1355
-        // now loop thru all of the constructors expected parameters
1356
-        foreach ($params as $index => $param) {
1357
-            try {
1358
-                // is this a dependency for a specific class ?
1359
-                $param_class = $this->mirror->getParameterClassName($param, $class_name, $index);
1360
-            } catch (ReflectionException $exception) {
1361
-                // uh-oh... most likely a legacy class that has not been autoloaded
1362
-                // let's try to derive the classname from what we have now
1363
-                // and hope that the property var name is close to the class name
1364
-                $param_class = $param->getName();
1365
-                $param_class = str_replace('_', ' ', $param_class);
1366
-                $param_class = ucwords($param_class);
1367
-                $param_class = str_replace(' ', '_', $param_class);
1368
-            }
1369
-            // BUT WAIT !!! This class may be an alias for something else (or getting replaced at runtime)
1370
-            $param_class = $this->class_cache->isAlias($param_class, $class_name)
1371
-                ? $this->class_cache->getFqnForAlias($param_class, $class_name)
1372
-                : $param_class;
1373
-            // param is not even a class
1374
-            if (
1375
-                ! empty($param_class)
1376
-                && ! class_exists($param_class, false)
1377
-                // and something already exists in the incoming arguments for this param
1378
-                && array_key_exists($index, $argument_keys)
1379
-                && isset($arguments[ $argument_keys[ $index ] ])
1380
-            ) {
1381
-                // so let's skip this argument and move on to the next
1382
-                continue;
1383
-            }
1384
-            // parameter is type hinted as a class
1385
-            if ($param_class !== null) {
1386
-                // parameter exists as an incoming argument, AND it's the correct class
1387
-                if (
1388
-                    array_key_exists($index, $argument_keys)
1389
-                    && isset($arguments[ $argument_keys[ $index ] ])
1390
-                    && $arguments[ $argument_keys[ $index ] ] instanceof $param_class
1391
-                ) {
1392
-                    // skip this argument and move on to the next
1393
-                    continue;
1394
-                }
1395
-                // parameter should be injected
1396
-                if ($this->_dependency_map->has_dependency_for_class($class_name, $param_class)) {
1397
-                    $arguments = $this->_resolve_dependency(
1398
-                        $class_name,
1399
-                        $param_class,
1400
-                        $arguments,
1401
-                        $index
1402
-                    );
1403
-                }
1404
-            }
1405
-            if (empty($arguments[ $index ])) {
1406
-                $default_value = $this->mirror->getParameterDefaultValue(
1407
-                    $param,
1408
-                    $class_name,
1409
-                    $index
1410
-                );
1411
-                // if there's no default value, and the incoming argument is an array (albeit empty), then use that
1412
-                $arguments[ $index ] = $default_value === null
1413
-                                       && isset($arguments[ $index ])
1414
-                                       && is_array($arguments[ $index ])
1415
-                    ? $arguments[ $index ]
1416
-                    : $default_value;
1417
-            }
1418
-        }
1419
-        return $arguments;
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * @param string $class_name
1425
-     * @param string $param_class
1426
-     * @param array  $arguments
1427
-     * @param mixed  $index
1428
-     * @return array
1429
-     * @throws InvalidArgumentException
1430
-     * @throws InvalidInterfaceException
1431
-     * @throws InvalidDataTypeException
1432
-     */
1433
-    protected function _resolve_dependency(string $class_name, string $param_class, array $arguments, $index): array
1434
-    {
1435
-        $dependency = null;
1436
-        // should dependency be loaded from cache ?
1437
-        $cache_on = $this->_dependency_map->loading_strategy_for_class_dependency(
1438
-            $class_name,
1439
-            $param_class
1440
-        );
1441
-        $cache_on = $cache_on !== EE_Dependency_Map::load_new_object;
1442
-        // we might have a dependency...
1443
-        // let's MAYBE try and find it in our cache if that's what's been requested
1444
-        $cached_class = $cache_on
1445
-            ? $this->_get_cached_class($param_class)
1446
-            : null;
1447
-        // and grab it if it exists
1448
-        if ($cached_class instanceof $param_class) {
1449
-            $dependency = $cached_class;
1450
-        } elseif ($param_class !== $class_name) {
1451
-            // obtain the loader method from the dependency map
1452
-            $loader = $this->_dependency_map->class_loader($param_class);
1453
-            // is loader a custom closure ?
1454
-            if ($loader instanceof Closure) {
1455
-                $dependency = $loader($arguments);
1456
-            } else {
1457
-                // set the cache on property for the recursive loading call
1458
-                $this->_cache_on = $cache_on;
1459
-                // if not, then let's try and load it via the registry
1460
-                if ($loader && method_exists($this, $loader)) {
1461
-                    $dependency = $this->{$loader}($param_class);
1462
-                } else {
1463
-                    $dependency = LoaderFactory::getLoader()->load(
1464
-                        $param_class,
1465
-                        [],
1466
-                        $cache_on
1467
-                    );
1468
-                }
1469
-            }
1470
-        }
1471
-        // did we successfully find the correct dependency ?
1472
-        if ($dependency instanceof $param_class) {
1473
-            // then let's inject it into the incoming array of arguments at the correct location
1474
-            $arguments[ $index ] = $dependency;
1475
-        }
1476
-        return $arguments;
1477
-    }
1478
-
1479
-
1480
-    /**
1481
-     * call any loader that's been registered in the EE_Dependency_Map::$_class_loaders array
1482
-     *
1483
-     * @param string $classname PLEASE NOTE: the class name needs to match what's registered
1484
-     *                          in the EE_Dependency_Map::$_class_loaders array,
1485
-     *                          including the class prefix, ie: "EE_", "EEM_", "EEH_", etc
1486
-     * @param array  $arguments
1487
-     * @return object
1488
-     */
1489
-    public static function factory(string $classname, array $arguments = [])
1490
-    {
1491
-        $loader = self::instance()->_dependency_map->class_loader($classname);
1492
-        if ($loader instanceof Closure) {
1493
-            return $loader($arguments);
1494
-        }
1495
-        if (method_exists(self::instance(), $loader)) {
1496
-            return self::instance()->{$loader}($classname, $arguments);
1497
-        }
1498
-        return null;
1499
-    }
1500
-
1501
-
1502
-    /**
1503
-     * Gets the addon by its class name
1504
-     *
1505
-     * @param string $class_name
1506
-     * @return EE_Addon
1507
-     */
1508
-    public function getAddon(string $class_name): ?EE_Addon
1509
-    {
1510
-        $class_name = str_replace('\\', '_', $class_name);
1511
-        return $this->addons->{$class_name} ?? null;
1512
-    }
1513
-
1514
-
1515
-    /**
1516
-     * removes the addon from the internal cache
1517
-     *
1518
-     * @param string $class_name
1519
-     * @return void
1520
-     */
1521
-    public function removeAddon(string $class_name)
1522
-    {
1523
-        $class_name = str_replace('\\', '_', $class_name);
1524
-        $this->addons->remove($class_name);
1525
-    }
1526
-
1527
-
1528
-    /**
1529
-     * Gets the addon by its name/slug (not classname. For that, just
1530
-     * use the get_addon() method above
1531
-     *
1532
-     * @param string $name
1533
-     * @return EE_Addon
1534
-     */
1535
-    public function get_addon_by_name(string $name): ?EE_Addon
1536
-    {
1537
-        foreach ($this->addons as $addon) {
1538
-            if ($addon->name() === $name) {
1539
-                return $addon;
1540
-            }
1541
-        }
1542
-        return null;
1543
-    }
1544
-
1545
-
1546
-    /**
1547
-     * Gets an array of all the registered addons, where the keys are their names.
1548
-     * (ie, what each returns for their name() function)
1549
-     * They're already available on EE_Registry::instance()->addons as properties,
1550
-     * where each property's name is the addon's classname,
1551
-     * So if you just want to get the addon by classname,
1552
-     * OR use the get_addon() method above.
1553
-     * PLEASE  NOTE:
1554
-     * addons with Fully Qualified Class Names
1555
-     * have had the namespace separators converted to underscores,
1556
-     * so a classname like Fully\Qualified\ClassName
1557
-     * would have been converted to Fully_Qualified_ClassName
1558
-     *
1559
-     * @return EE_Addon[] where the KEYS are the addon's name()
1560
-     */
1561
-    public function get_addons_by_name(): array
1562
-    {
1563
-        $addons = [];
1564
-        foreach ($this->addons as $addon) {
1565
-            $addons[ $addon->name() ] = $addon;
1566
-        }
1567
-        return $addons;
1568
-    }
1569
-
1570
-
1571
-    /**
1572
-     * Resets the specified model's instance AND makes sure EE_Registry doesn't keep
1573
-     * a stale copy of it around
1574
-     *
1575
-     * @param string $model_name
1576
-     * @return EEM_Base
1577
-     * @throws EE_Error
1578
-     * @throws ReflectionException
1579
-     */
1580
-    public function reset_model(string $model_name): ?EEM_Base
1581
-    {
1582
-        $model_class_name = strpos($model_name, 'EEM_') !== 0
1583
-            ? "EEM_$model_name"
1584
-            : $model_name;
1585
-        if (! $this->LIB->has($model_class_name)) {
1586
-            return null;
1587
-        }
1588
-        $model = $this->LIB->get($model_class_name);
1589
-        if (! $model instanceof EEM_Base) {
1590
-            return null;
1591
-        }
1592
-        // get that model reset it and make sure we nuke the old reference to it
1593
-        if ($model instanceof $model_class_name && is_callable([$model_class_name, 'reset'])) {
1594
-            $this->LIB->remove($model_class_name);
1595
-            $this->LIB->add($model_class_name, $model->reset());
1596
-        } else {
1597
-            throw new EE_Error(
1598
-                sprintf(
1599
-                    esc_html__('Model %s does not have a method "reset"', 'event_espresso'),
1600
-                    $model_name
1601
-                )
1602
-            );
1603
-        }
1604
-        return $model;
1605
-    }
1606
-
1607
-
1608
-    /**
1609
-     * Resets the registry.
1610
-     * The criteria for what gets reset is based on what can be shared between sites on the same request when
1611
-     * switch_to_blog is used in a multisite install.  Here is a list of things that are NOT reset.
1612
-     * - $_dependency_map
1613
-     * - $_class_abbreviations
1614
-     * - $NET_CFG (EE_Network_Config): The config is shared network wide so no need to reset.
1615
-     * - $REQ:  Still on the same request so no need to change.
1616
-     * - $CAP: There is no site specific state in the EE_Capability class.
1617
-     * - $SSN: Although ideally, the session should not be shared between site switches, we can't reset it because only
1618
-     * one Session can be active in a single request.  Resetting could resolve in "headers already sent" errors.
1619
-     * - $addons:  In multisite, the state of the addons is something controlled via hooks etc in a normal request.  So
1620
-     *             for now, we won't reset the addons because it could break calls to an add-ons class/methods in the
1621
-     *             switch or on the restore.
1622
-     * - $modules
1623
-     * - $shortcodes
1624
-     * - $widgets
1625
-     *
1626
-     * @param boolean $hard             [deprecated]
1627
-     * @param boolean $reinstantiate    whether to create new instances of EE_Registry's singletons too,
1628
-     *                                  or just reset without re-instantiating (handy to set to FALSE if you're not
1629
-     *                                  sure if you CAN currently reinstantiate the singletons at the moment)
1630
-     * @param bool    $reset_models     Defaults to true.  When false, then the models are not reset.  This is so
1631
-     *                                  client
1632
-     *                                  code instead can just change the model context to a different blog id if
1633
-     *                                  necessary
1634
-     * @return EE_Registry
1635
-     * @throws EE_Error
1636
-     * @throws ReflectionException
1637
-     */
1638
-    public static function reset(bool $hard = false, bool $reinstantiate = true, bool $reset_models = true): EE_Registry
1639
-    {
1640
-        $instance            = self::instance();
1641
-        $instance->_cache_on = true;
1642
-        // reset some "special" classes
1643
-        EEH_Activation::reset();
1644
-        $hard                     = apply_filters('FHEE__EE_Registry__reset__hard', $hard);
1645
-        $instance->CFG            = EE_Config::reset($hard, $reinstantiate);
1646
-        $instance->CART           = null;
1647
-        $instance->MRM            = null;
1648
-        $instance->AssetsRegistry = LoaderFactory::getLoader()->getShared(
1649
-            'EventEspresso\core\services\assets\Registry'
1650
-        );
1651
-        // messages reset
1652
-        EED_Messages::reset();
1653
-        // handle of objects cached on LIB
1654
-        foreach (['LIB', 'modules'] as $cache) {
1655
-            foreach ($instance->{$cache} as $class_name => $class) {
1656
-                if (self::_reset_and_unset_object($class, $reset_models)) {
1657
-                    unset($instance->{$cache}->{$class_name});
1658
-                }
1659
-            }
1660
-        }
1661
-        return $instance;
1662
-    }
1663
-
1664
-
1665
-    /**
1666
-     * if passed object implements ResettableInterface, then call it's reset() method
1667
-     * if passed object implements InterminableInterface, then return false,
1668
-     * to indicate that it should NOT be cleared from the Registry cache
1669
-     *
1670
-     * @param      $object
1671
-     * @param bool $reset_models
1672
-     * @return bool returns true if cached object should be unset
1673
-     * @throws EE_Error
1674
-     * @throws ReflectionException
1675
-     */
1676
-    private static function _reset_and_unset_object($object, bool $reset_models): bool
1677
-    {
1678
-        if (! is_object($object)) {
1679
-            // don't unset anything that's not an object
1680
-            return false;
1681
-        }
1682
-        if ($object instanceof EED_Module) {
1683
-            $object::reset();
1684
-            // don't unset modules
1685
-            return false;
1686
-        }
1687
-        if ($object instanceof ResettableInterface) {
1688
-            if ($object instanceof EEM_Base) {
1689
-                if ($reset_models) {
1690
-                    $object->reset();
1691
-                    return true;
1692
-                }
1693
-                return false;
1694
-            }
1695
-            $object->reset();
1696
-            return true;
1697
-        }
1698
-        if (! $object instanceof InterminableInterface) {
1699
-            return true;
1700
-        }
1701
-        return false;
1702
-    }
1703
-
1704
-
1705
-    /**
1706
-     * Gets all the custom post type models defined
1707
-     *
1708
-     * @return array keys are model "short names" (Eg "Event") and keys are classnames (eg "EEM_Event")
1709
-     */
1710
-    public function cpt_models(): array
1711
-    {
1712
-        $cpt_models = [];
1713
-        foreach ($this->non_abstract_db_models as $short_name => $classname) {
1714
-            if (is_subclass_of($classname, 'EEM_CPT_Base')) {
1715
-                $cpt_models[ $short_name ] = $classname;
1716
-            }
1717
-        }
1718
-        return $cpt_models;
1719
-    }
1720
-
1721
-
1722
-    /**
1723
-     * @return EE_Config
1724
-     */
1725
-    public static function CFG(): EE_Config
1726
-    {
1727
-        return self::instance()->CFG;
1728
-    }
1729
-
1730
-
1731
-    /**
1732
-     * @param string $class_name
1733
-     * @return ReflectionClass
1734
-     * @throws ReflectionException
1735
-     * @throws InvalidDataTypeException
1736
-     * @deprecated 4.9.62.p
1737
-     */
1738
-    public function get_ReflectionClass(string $class_name): ReflectionClass
1739
-    {
1740
-        return $this->mirror->getReflectionClass($class_name);
1741
-    }
25
+	/**
26
+	 * @var EE_Registry $_instance
27
+	 */
28
+	private static $_instance;
29
+
30
+	/**
31
+	 * @var EE_Dependency_Map $_dependency_map
32
+	 */
33
+	protected $_dependency_map;
34
+
35
+	/**
36
+	 * @var Mirror
37
+	 */
38
+	private $mirror;
39
+
40
+	/**
41
+	 * @var ClassInterfaceCache $class_cache
42
+	 */
43
+	private $class_cache;
44
+
45
+	/**
46
+	 * @var array $_class_abbreviations
47
+	 */
48
+	protected $_class_abbreviations = [];
49
+
50
+	/**
51
+	 * @var CommandBusInterface $BUS
52
+	 */
53
+	public $BUS;
54
+
55
+	/**
56
+	 * @var EE_Cart $CART
57
+	 */
58
+	public $CART;
59
+
60
+	/**
61
+	 * @var EE_Config $CFG
62
+	 */
63
+	public $CFG;
64
+
65
+	/**
66
+	 * @var EE_Network_Config $NET_CFG
67
+	 */
68
+	public $NET_CFG;
69
+
70
+	/**
71
+	 * RegistryContainer for storing library classes in
72
+	 *
73
+	 * @var RegistryContainer $LIB
74
+	 */
75
+	public $LIB;
76
+
77
+	/**
78
+	 * @var EE_Request_Handler $REQ
79
+	 * @deprecated 4.10.14.p
80
+	 */
81
+	public $REQ;
82
+
83
+	/**
84
+	 * @var EE_Session $SSN
85
+	 */
86
+	public $SSN;
87
+
88
+	/**
89
+	 * @since 4.5.0
90
+	 * @var EE_Capabilities $CAP
91
+	 */
92
+	public $CAP;
93
+
94
+	/**
95
+	 * @since 4.9.0
96
+	 * @var EE_Message_Resource_Manager $MRM
97
+	 */
98
+	public $MRM;
99
+
100
+	/**
101
+	 * @var Registry $AssetsRegistry
102
+	 */
103
+	public $AssetsRegistry;
104
+
105
+	/**
106
+	 * RegistryContainer for holding addons which have registered themselves to work with EE core
107
+	 *
108
+	 * @var RegistryContainer|EE_Addon[] $addons
109
+	 */
110
+	public $addons;
111
+
112
+	/**
113
+	 * keys are 'short names' (eg Event), values are class names (eg 'EEM_Event')
114
+	 *
115
+	 * @var EEM_Base[] $models
116
+	 */
117
+	public $models = [];
118
+
119
+	/**
120
+	 * @var RegistryContainer|EED_Module[] $modules
121
+	 */
122
+	public $modules;
123
+
124
+	/**
125
+	 * @var RegistryContainer|EES_Shortcode[] $shortcodes
126
+	 */
127
+	public $shortcodes;
128
+
129
+	/**
130
+	 * @var RegistryContainer|WP_Widget[] $widgets
131
+	 */
132
+	public $widgets;
133
+
134
+	/**
135
+	 * this is an array of all implemented model names (i.e. not the parent abstract models, or models
136
+	 * which don't actually fetch items from the DB in the normal way (ie, are not children of EEM_Base)).
137
+	 * Keys are model "short names" (eg "Event") as used in model relations, and values are
138
+	 * classnames (eg "EEM_Event")
139
+	 *
140
+	 * @var array $non_abstract_db_models
141
+	 */
142
+	public $non_abstract_db_models = [];
143
+
144
+	/**
145
+	 * internationalization for JS strings
146
+	 *    usage:   EE_Registry::i18n_js_strings['string_key'] = esc_html__( 'string to translate.', 'event_espresso' );
147
+	 *    in js file:  var translatedString = eei18n.string_key;
148
+	 *
149
+	 * @var array $i18n_js_strings
150
+	 */
151
+	public static $i18n_js_strings = [];
152
+
153
+	/**
154
+	 * $main_file - path to espresso.php
155
+	 *
156
+	 * @var array $main_file
157
+	 */
158
+	public $main_file;
159
+
160
+	/**
161
+	 * array of ReflectionClass objects where the key is the class name
162
+	 *
163
+	 * @deprecated 4.9.62.p
164
+	 * @var ReflectionClass[] $_reflectors
165
+	 */
166
+	public $_reflectors;
167
+
168
+	/**
169
+	 * boolean flag to indicate whether or not to load/save dependencies from/to the cache
170
+	 *
171
+	 * @var boolean $_cache_on
172
+	 */
173
+	protected $_cache_on = true;
174
+
175
+	/**
176
+	 * @var ObjectIdentifier
177
+	 */
178
+	private $object_identifier;
179
+
180
+
181
+	/**
182
+	 * @singleton method used to instantiate class object
183
+	 * @param EE_Dependency_Map|null   $dependency_map
184
+	 * @param Mirror|null              $mirror
185
+	 * @param ClassInterfaceCache|null $class_cache
186
+	 * @param ObjectIdentifier|null    $object_identifier
187
+	 * @return EE_Registry instance
188
+	 */
189
+	public static function instance(
190
+		EE_Dependency_Map $dependency_map = null,
191
+		Mirror $mirror = null,
192
+		ClassInterfaceCache $class_cache = null,
193
+		ObjectIdentifier $object_identifier = null
194
+	): EE_Registry {
195
+		// check if class object is instantiated
196
+		if (
197
+			! self::$_instance instanceof EE_Registry
198
+			&& $dependency_map instanceof EE_Dependency_Map
199
+			&& $mirror instanceof Mirror
200
+			&& $class_cache instanceof ClassInterfaceCache
201
+			&& $object_identifier instanceof ObjectIdentifier
202
+		) {
203
+			self::$_instance = new self(
204
+				$dependency_map,
205
+				$mirror,
206
+				$class_cache,
207
+				$object_identifier
208
+			);
209
+		}
210
+		return self::$_instance;
211
+	}
212
+
213
+
214
+	/**
215
+	 * protected constructor to prevent direct creation
216
+	 *
217
+	 * @Constructor
218
+	 * @param EE_Dependency_Map   $dependency_map
219
+	 * @param Mirror              $mirror
220
+	 * @param ClassInterfaceCache $class_cache
221
+	 * @param ObjectIdentifier    $object_identifier
222
+	 */
223
+	protected function __construct(
224
+		EE_Dependency_Map $dependency_map,
225
+		Mirror $mirror,
226
+		ClassInterfaceCache $class_cache,
227
+		ObjectIdentifier $object_identifier
228
+	) {
229
+		$this->_dependency_map   = $dependency_map;
230
+		$this->mirror            = $mirror;
231
+		$this->class_cache       = $class_cache;
232
+		$this->object_identifier = $object_identifier;
233
+		// $registry_container = new RegistryContainer();
234
+		$this->LIB        = new RegistryContainer();
235
+		$this->addons     = new RegistryContainer();
236
+		$this->modules    = new RegistryContainer();
237
+		$this->shortcodes = new RegistryContainer();
238
+		$this->widgets    = new RegistryContainer();
239
+		add_action('EE_Load_Espresso_Core__handle_request__initialize_core_loading', [$this, 'initialize']);
240
+	}
241
+
242
+
243
+	/**
244
+	 * initialize
245
+	 *
246
+	 * @throws OutOfBoundsException
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws InvalidDataTypeException
250
+	 * @throws EE_Error
251
+	 * @throws ReflectionException
252
+	 */
253
+	public function initialize()
254
+	{
255
+		$this->_class_abbreviations = apply_filters(
256
+			'FHEE__EE_Registry____construct___class_abbreviations',
257
+			[
258
+				'EE_Config'                                       => 'CFG',
259
+				'EE_Session'                                      => 'SSN',
260
+				'EE_Capabilities'                                 => 'CAP',
261
+				'EE_Cart'                                         => 'CART',
262
+				'EE_Network_Config'                               => 'NET_CFG',
263
+				'EE_Request_Handler'                              => 'REQ',
264
+				'EE_Message_Resource_Manager'                     => 'MRM',
265
+				'EventEspresso\core\services\commands\CommandBus' => 'BUS',
266
+				'EventEspresso\core\services\assets\Registry'     => 'AssetsRegistry',
267
+			]
268
+		);
269
+		$this->load_core('Base', [], true);
270
+		// add our request and response objects to the cache
271
+		$request_loader = $this->_dependency_map->class_loader(
272
+			'EventEspresso\core\services\request\Request'
273
+		);
274
+		$this->_set_cached_class(
275
+			$request_loader(),
276
+			'EventEspresso\core\services\request\Request'
277
+		);
278
+		$response_loader = $this->_dependency_map->class_loader(
279
+			'EventEspresso\core\services\request\Response'
280
+		);
281
+		$this->_set_cached_class(
282
+			$response_loader(),
283
+			'EventEspresso\core\services\request\Response'
284
+		);
285
+		add_action('AHEE__EE_System__set_hooks_for_core', [$this, 'init']);
286
+	}
287
+
288
+
289
+	/**
290
+	 * @return void
291
+	 */
292
+	public function init()
293
+	{
294
+		// Get current page protocol
295
+		$protocol = is_ssl() ? 'https://' : 'http://';
296
+		// Output admin-ajax.php URL with same protocol as current page
297
+		self::$i18n_js_strings['ajax_url'] = admin_url('admin-ajax.php', $protocol);
298
+		self::$i18n_js_strings['wp_debug'] = defined('WP_DEBUG') && WP_DEBUG;
299
+	}
300
+
301
+
302
+	/**
303
+	 * @return array
304
+	 */
305
+	public static function sanitize_i18n_js_strings(): array
306
+	{
307
+		$i18n_js_strings = (array) self::$i18n_js_strings;
308
+		foreach ($i18n_js_strings as $key => $value) {
309
+			if (is_scalar($value)) {
310
+				$decoded_value           = html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
311
+				$i18n_js_strings[ $key ] = wp_strip_all_tags($decoded_value);
312
+			}
313
+		}
314
+		return $i18n_js_strings;
315
+	}
316
+
317
+
318
+	/**
319
+	 * localize_i18n_js_strings
320
+	 *
321
+	 * @return string
322
+	 */
323
+	public static function localize_i18n_js_strings(): string
324
+	{
325
+		$i18n_js_strings = EE_Registry::sanitize_i18n_js_strings();
326
+		return '/* <![CDATA[ */ var eei18n = ' . wp_json_encode($i18n_js_strings) . '; /* ]]> */';
327
+	}
328
+
329
+
330
+	/**
331
+	 * @param mixed string | EED_Module $module
332
+	 * @throws OutOfBoundsException
333
+	 * @throws InvalidArgumentException
334
+	 * @throws InvalidInterfaceException
335
+	 * @throws InvalidDataTypeException
336
+	 * @throws EE_Error
337
+	 * @throws ReflectionException
338
+	 */
339
+	public function add_module($module)
340
+	{
341
+		if ($module instanceof EED_Module) {
342
+			$module_class = get_class($module);
343
+			$this->modules->add($module_class, $module);
344
+		} else {
345
+			if (! class_exists('EE_Module_Request_Router', false)) {
346
+				$this->load_core('Module_Request_Router');
347
+			}
348
+			EE_Module_Request_Router::module_factory($module);
349
+		}
350
+	}
351
+
352
+
353
+	/**
354
+	 * @param string $module_name
355
+	 * @return mixed EED_Module | NULL
356
+	 */
357
+	public function get_module(string $module_name = '')
358
+	{
359
+		return $this->modules->get($module_name);
360
+	}
361
+
362
+
363
+	/**
364
+	 * loads core classes - must be singletons
365
+	 *
366
+	 * @param string $class_name - simple class name ie: session
367
+	 * @param mixed  $arguments
368
+	 * @param bool   $load_only
369
+	 * @return bool|null|object
370
+	 * @throws InvalidInterfaceException
371
+	 * @throws InvalidDataTypeException
372
+	 * @throws EE_Error
373
+	 * @throws ReflectionException
374
+	 * @throws InvalidArgumentException
375
+	 */
376
+	public function load_core(string $class_name, $arguments = [], bool $load_only = false)
377
+	{
378
+		$core_paths = (array) apply_filters(
379
+			'FHEE__EE_Registry__load_core__core_paths',
380
+			[
381
+				EE_CORE,
382
+				EE_ADMIN,
383
+				EE_CPTS,
384
+				EE_CORE . 'CPTs/',
385
+				EE_CORE . 'data_migration_scripts/',
386
+				EE_CORE . 'request_stack/',
387
+				EE_CORE . 'middleware/',
388
+			]
389
+		);
390
+		// retrieve instantiated class
391
+		return $this->_load(
392
+			$core_paths,
393
+			'EE_',
394
+			$class_name,
395
+			'core',
396
+			$arguments,
397
+			false,
398
+			true,
399
+			$load_only
400
+		);
401
+	}
402
+
403
+
404
+	/**
405
+	 * loads service classes
406
+	 *
407
+	 * @param string $class_name - simple class name ie: session
408
+	 * @param mixed  $arguments
409
+	 * @param bool   $load_only
410
+	 * @return bool|null|object
411
+	 * @throws InvalidInterfaceException
412
+	 * @throws InvalidDataTypeException
413
+	 * @throws EE_Error
414
+	 * @throws ReflectionException
415
+	 * @throws InvalidArgumentException
416
+	 * @deprecated  4.10.33.p
417
+	 */
418
+	public function load_service(string $class_name, $arguments = [], bool $load_only = false)
419
+	{
420
+		$service_paths = (array) apply_filters(
421
+			'FHEE__EE_Registry__load_service__service_paths',
422
+			[
423
+				EE_CORE . 'services/',
424
+			]
425
+		);
426
+		// retrieve instantiated class
427
+		return $this->_load(
428
+			$service_paths,
429
+			'EE_',
430
+			$class_name,
431
+			'class',
432
+			$arguments,
433
+			false,
434
+			true,
435
+			$load_only
436
+		);
437
+	}
438
+
439
+
440
+	/**
441
+	 * loads data_migration_scripts
442
+	 *
443
+	 * @param string $class_name - class name for the DMS ie: EE_DMS_Core_4_2_0
444
+	 * @param mixed  $arguments
445
+	 * @return bool|null|object
446
+	 * @throws InvalidInterfaceException
447
+	 * @throws InvalidDataTypeException
448
+	 * @throws EE_Error
449
+	 * @throws ReflectionException
450
+	 * @throws InvalidArgumentException
451
+	 */
452
+	public function load_dms(string $class_name, $arguments = [])
453
+	{
454
+		// retrieve instantiated class
455
+		return $this->_load(
456
+			EE_Data_Migration_Manager::instance()->get_data_migration_script_folders(),
457
+			'EE_DMS_',
458
+			$class_name,
459
+			'dms',
460
+			$arguments,
461
+			false,
462
+			false
463
+		);
464
+	}
465
+
466
+
467
+	/**
468
+	 * loads object creating classes - must be singletons
469
+	 *
470
+	 * @param string $class_name - simple class name ie: attendee
471
+	 * @param mixed  $arguments  - an array of arguments to pass to the class
472
+	 * @param bool   $from_db    - some classes are instantiated from the db and thus call a different method to
473
+	 *                           instantiate
474
+	 * @param bool   $cache      if you don't want the class to be stored in the internal cache (non-persistent) then
475
+	 *                           set this to FALSE (ie. when instantiating model objects from client in a loop)
476
+	 * @param bool   $load_only  whether or not to just load the file and NOT instantiate, or load AND instantiate
477
+	 *                           (default)
478
+	 * @return bool|null|object
479
+	 * @throws InvalidInterfaceException
480
+	 * @throws InvalidDataTypeException
481
+	 * @throws EE_Error
482
+	 * @throws ReflectionException
483
+	 * @throws InvalidArgumentException
484
+	 */
485
+	public function load_class(
486
+		string $class_name,
487
+		$arguments = [],
488
+		bool $from_db = false,
489
+		bool $cache = true,
490
+		bool $load_only = false
491
+	) {
492
+		$paths = (array) apply_filters(
493
+			'FHEE__EE_Registry__load_class__paths',
494
+			[
495
+				EE_CORE,
496
+				EE_CLASSES,
497
+				EE_BUSINESS,
498
+			]
499
+		);
500
+		// retrieve instantiated class
501
+		return $this->_load(
502
+			$paths,
503
+			'EE_',
504
+			$class_name,
505
+			'class',
506
+			$arguments,
507
+			$from_db,
508
+			$cache,
509
+			$load_only
510
+		);
511
+	}
512
+
513
+
514
+	/**
515
+	 * loads helper classes - must be singletons
516
+	 *
517
+	 * @param string $class_name - simple class name ie: price
518
+	 * @param mixed  $arguments
519
+	 * @param bool   $load_only
520
+	 * @return bool|null|object
521
+	 * @throws InvalidInterfaceException
522
+	 * @throws InvalidDataTypeException
523
+	 * @throws EE_Error
524
+	 * @throws ReflectionException
525
+	 * @throws InvalidArgumentException
526
+	 */
527
+	public function load_helper(string $class_name, $arguments = [], bool $load_only = true)
528
+	{
529
+		// todo: add doing_it_wrong() in a few versions after all addons have had calls to this method removed
530
+		$helper_paths = (array) apply_filters('FHEE__EE_Registry__load_helper__helper_paths', [EE_HELPERS]);
531
+		// retrieve instantiated class
532
+		return $this->_load(
533
+			$helper_paths,
534
+			'EEH_',
535
+			$class_name,
536
+			'helper',
537
+			$arguments,
538
+			false,
539
+			true,
540
+			$load_only
541
+		);
542
+	}
543
+
544
+
545
+	/**
546
+	 * loads core classes - must be singletons
547
+	 *
548
+	 * @param string $class_name - simple class name ie: session
549
+	 * @param mixed  $arguments
550
+	 * @param bool   $load_only
551
+	 * @param bool   $cache      whether to cache the object or not.
552
+	 * @return bool|null|object
553
+	 * @throws InvalidInterfaceException
554
+	 * @throws InvalidDataTypeException
555
+	 * @throws EE_Error
556
+	 * @throws ReflectionException
557
+	 * @throws InvalidArgumentException
558
+	 */
559
+	public function load_lib(string $class_name, $arguments = [], bool $load_only = false, bool $cache = true)
560
+	{
561
+		$paths = [
562
+			EE_LIBRARIES,
563
+			EE_LIBRARIES . 'messages/',
564
+			EE_LIBRARIES . 'shortcodes/',
565
+			EE_LIBRARIES . 'qtips/',
566
+			EE_LIBRARIES . 'payment_methods/',
567
+		];
568
+		// retrieve instantiated class
569
+		return $this->_load(
570
+			$paths,
571
+			'EE_',
572
+			$class_name,
573
+			'lib',
574
+			$arguments,
575
+			false,
576
+			$cache,
577
+			$load_only
578
+		);
579
+	}
580
+
581
+
582
+	/**
583
+	 * loads model classes - must be singletons
584
+	 *
585
+	 * @param string $class_name - simple class name ie: price
586
+	 * @param mixed  $arguments
587
+	 * @param bool   $load_only
588
+	 * @return EEM_Base|bool|null
589
+	 * @throws InvalidInterfaceException
590
+	 * @throws InvalidDataTypeException
591
+	 * @throws EE_Error
592
+	 * @throws ReflectionException
593
+	 * @throws InvalidArgumentException
594
+	 */
595
+	public function load_model(string $class_name, $arguments = [], bool $load_only = false)
596
+	{
597
+		$paths = (array) apply_filters(
598
+			'FHEE__EE_Registry__load_model__paths',
599
+			[
600
+				EE_MODELS,
601
+				EE_CORE,
602
+			]
603
+		);
604
+		// retrieve instantiated class
605
+		return $this->_load(
606
+			$paths,
607
+			'EEM_',
608
+			$class_name,
609
+			'model',
610
+			$arguments,
611
+			false,
612
+			true,
613
+			$load_only
614
+		);
615
+	}
616
+
617
+
618
+	/**
619
+	 * loads model classes - must be singletons
620
+	 *
621
+	 * @param string $class_name - simple class name ie: price
622
+	 * @param mixed  $arguments
623
+	 * @param bool   $load_only
624
+	 * @return bool|null|object
625
+	 * @throws InvalidInterfaceException
626
+	 * @throws InvalidDataTypeException
627
+	 * @throws EE_Error
628
+	 * @throws ReflectionException
629
+	 * @throws InvalidArgumentException
630
+	 * @deprecated  4.10.33.p
631
+	 */
632
+	public function load_model_class(string $class_name, $arguments = [], bool $load_only = true)
633
+	{
634
+		$paths = [
635
+			EE_MODELS . 'fields/',
636
+			EE_MODELS . 'helpers/',
637
+			EE_MODELS . 'relations/',
638
+			EE_MODELS . 'strategies/',
639
+		];
640
+		// retrieve instantiated class
641
+		return $this->_load(
642
+			$paths,
643
+			'EE_',
644
+			$class_name,
645
+			'',
646
+			$arguments,
647
+			false,
648
+			true,
649
+			$load_only
650
+		);
651
+	}
652
+
653
+
654
+	/**
655
+	 * Determines if $model_name is the name of an actual EE model.
656
+	 *
657
+	 * @param string $model_name like Event, Attendee, Question_Group_Question, etc.
658
+	 * @return boolean
659
+	 */
660
+	public function is_model_name(string $model_name): bool
661
+	{
662
+		return isset($this->models[ $model_name ]);
663
+	}
664
+
665
+
666
+	/**
667
+	 * generic class loader
668
+	 *
669
+	 * @param string $path_to_file - directory path to file location, not including filename
670
+	 * @param string $file_name    - file name  ie:  my_file.php, including extension
671
+	 * @param string $type         - file type - core? class? helper? model?
672
+	 * @param mixed  $arguments
673
+	 * @param bool   $load_only
674
+	 * @return bool|null|object
675
+	 * @throws InvalidInterfaceException
676
+	 * @throws InvalidDataTypeException
677
+	 * @throws EE_Error
678
+	 * @throws ReflectionException
679
+	 * @throws InvalidArgumentException
680
+	 */
681
+	public function load_file(
682
+		string $path_to_file,
683
+		string $file_name,
684
+		string $type = '',
685
+		$arguments = [],
686
+		bool $load_only = true
687
+	) {
688
+		// retrieve instantiated class
689
+		return $this->_load(
690
+			(array) $path_to_file,
691
+			'',
692
+			$file_name,
693
+			$type,
694
+			$arguments,
695
+			false,
696
+			true,
697
+			$load_only
698
+		);
699
+	}
700
+
701
+
702
+	/**
703
+	 * @param string $path_to_file - directory path to file location, not including filename
704
+	 * @param string $class_name   - full class name  ie:  My_Class
705
+	 * @param string $type         - file type - core? class? helper? model?
706
+	 * @param mixed  $arguments
707
+	 * @param bool   $load_only
708
+	 * @return bool|null|object
709
+	 * @throws InvalidInterfaceException
710
+	 * @throws InvalidDataTypeException
711
+	 * @throws EE_Error
712
+	 * @throws ReflectionException
713
+	 * @throws InvalidArgumentException
714
+	 * @deprecated  4.10.33.p
715
+	 */
716
+	public function load_addon(
717
+		string $path_to_file,
718
+		string $class_name,
719
+		string $type = 'class',
720
+		$arguments = [],
721
+		bool $load_only = false
722
+	) {
723
+		// retrieve instantiated class
724
+		return $this->_load(
725
+			(array) $path_to_file,
726
+			'addon',
727
+			$class_name,
728
+			$type,
729
+			$arguments,
730
+			false,
731
+			true,
732
+			$load_only
733
+		);
734
+	}
735
+
736
+
737
+	/**
738
+	 * instantiates, caches, and automatically resolves dependencies
739
+	 * for classes that use a Fully Qualified Class Name.
740
+	 * if the class is not capable of being loaded using PSR-4 autoloading,
741
+	 * then you need to use one of the existing load_*() methods
742
+	 * which can resolve the classname and filepath from the passed arguments
743
+	 *
744
+	 * @param string      $class_name Fully Qualified Class Name
745
+	 * @param array       $arguments  an argument, or array of arguments to pass to the class upon instantiation
746
+	 * @param bool        $cache      whether to cache the instantiated object for reuse
747
+	 * @param bool        $from_db    some classes are instantiated from the db
748
+	 *                                and thus call a different method to instantiate
749
+	 * @param bool        $load_only  if true, will only load the file, but will NOT instantiate an object
750
+	 * @param bool|string $addon      if true, will cache the object in the EE_Registry->$addons array
751
+	 * @return bool|null|mixed     null = failure to load or instantiate class object.
752
+	 *                                object = class loaded and instantiated successfully.
753
+	 *                                bool = fail or success when $load_only is true
754
+	 * @throws InvalidInterfaceException
755
+	 * @throws InvalidDataTypeException
756
+	 * @throws EE_Error
757
+	 * @throws ReflectionException
758
+	 * @throws InvalidArgumentException
759
+	 */
760
+	public function create(
761
+		string $class_name = '',
762
+		array $arguments = [],
763
+		bool $cache = false,
764
+		bool $from_db = false,
765
+		bool $load_only = false,
766
+		bool $addon = false
767
+	) {
768
+		$class_name   = ltrim($class_name, '\\');
769
+		$class_name   = $this->class_cache->getFqnForAlias($class_name);
770
+		$class_exists = $this->loadOrVerifyClassExists($class_name, $arguments);
771
+		// if a non-FQCN was passed, then
772
+		// verifyClassExists() might return an object
773
+		// or it could return null if the class just could not be found anywhere
774
+		if ($class_exists instanceof $class_name || $class_exists === null) {
775
+			// either way, return the results
776
+			return $class_exists;
777
+		}
778
+		$class_name = $class_exists;
779
+		// if we're only loading the class and it already exists, then let's just return true immediately
780
+		if ($load_only) {
781
+			return true;
782
+		}
783
+		$addon = $addon ? 'addon' : '';
784
+		// $this->_cache_on is toggled during the recursive loading that can occur with dependency injection
785
+		// $cache is controlled by individual calls to separate Registry loader methods like load_class()
786
+		// $load_only is also controlled by individual calls to separate Registry loader methods like load_file()
787
+		if ($this->_cache_on && $cache) {
788
+			// return object if it's already cached
789
+			$cached_class = $this->_get_cached_class($class_name, $addon, $arguments);
790
+			if ($cached_class !== null) {
791
+				return $cached_class;
792
+			}
793
+		}                                                           // obtain the loader method from the dependency map
794
+		$loader = $this->_dependency_map->class_loader($class_name);// instantiate the requested object
795
+		if ($loader instanceof Closure) {
796
+			$class_obj = $loader($arguments);
797
+		} else {
798
+			if ($loader && method_exists($this, $loader)) {
799
+				$class_obj = $this->{$loader}($class_name, $arguments);
800
+			} else {
801
+				$class_obj = $this->_create_object($class_name, $arguments, $addon, $from_db);
802
+			}
803
+		}
804
+		if (($this->_cache_on && $cache) || $this->get_class_abbreviation($class_name, '')) {
805
+			// save it for later... kinda like gum  { : $
806
+			$this->_set_cached_class(
807
+				$class_obj,
808
+				$class_name,
809
+				$addon,
810
+				$from_db,
811
+				$arguments
812
+			);
813
+		}
814
+		$this->_cache_on = true;
815
+		return $class_obj;
816
+	}
817
+
818
+
819
+	/**
820
+	 * Recursively checks that a class exists and potentially attempts to load classes with non-FQCNs
821
+	 *
822
+	 * @param string|mixed $class_name
823
+	 * @param array        $arguments
824
+	 * @param int          $attempt
825
+	 * @return mixed
826
+	 */
827
+	private function loadOrVerifyClassExists($class_name, array $arguments, int $attempt = 1)
828
+	{
829
+		if (is_object($class_name) || class_exists($class_name)) {
830
+			return $class_name;
831
+		}
832
+		switch ($attempt) {
833
+			case 1:
834
+				// if it's a FQCN then maybe the class is registered with a preceding \
835
+				$class_name = strpos($class_name, '\\') !== false
836
+					? '\\' . ltrim($class_name, '\\')
837
+					: $class_name;
838
+				break;
839
+			case 2:
840
+				//
841
+				$loader = $this->_dependency_map->class_loader($class_name);
842
+				if ($loader && method_exists($this, $loader)) {
843
+					return $this->{$loader}($class_name, $arguments);
844
+				}
845
+				break;
846
+			case 3:
847
+			default:
848
+				return null;
849
+		}
850
+		$attempt++;
851
+		return $this->loadOrVerifyClassExists($class_name, $arguments, $attempt);
852
+	}
853
+
854
+
855
+	/**
856
+	 * instantiates, caches, and injects dependencies for classes
857
+	 *
858
+	 * @param array  $file_paths         an array of paths to folders to look in
859
+	 * @param string $class_prefix       EE  or EEM or... ???
860
+	 * @param string $class_name         $class name
861
+	 * @param string $type               file type - core? class? helper? model?
862
+	 * @param mixed  $arguments          an argument or array of arguments to pass to the class upon instantiation
863
+	 * @param bool   $from_db            some classes are instantiated from the db
864
+	 *                                   and thus call a different method to instantiate
865
+	 * @param bool   $cache              whether to cache the instantiated object for reuse
866
+	 * @param bool   $load_only          if true, will only load the file, but will NOT instantiate an object
867
+	 * @return bool|null|mixed           null   = failure to load or instantiate class object.
868
+	 *                                   mixed = class loaded and instantiated successfully.
869
+	 *                                   bool   = fail or success when $load_only is true
870
+	 * @throws EE_Error
871
+	 * @throws ReflectionException
872
+	 * @throws InvalidInterfaceException
873
+	 * @throws InvalidDataTypeException
874
+	 * @throws InvalidArgumentException
875
+	 */
876
+	protected function _load(
877
+		array $file_paths = [],
878
+		string $class_prefix = 'EE_',
879
+		string $class_name = '',
880
+		string $type = 'class',
881
+		array $arguments = [],
882
+		bool $from_db = false,
883
+		bool $cache = true,
884
+		bool $load_only = false
885
+	) {
886
+		$class_name = ltrim($class_name, '\\');
887
+		// strip php file extension
888
+		$class_name = str_replace('.php', '', trim($class_name));
889
+		// does the class have a prefix ?
890
+		if (! empty($class_prefix) && $class_prefix !== 'addon') {
891
+			// make sure $class_prefix is uppercase
892
+			$class_prefix = strtoupper(trim($class_prefix));
893
+			// add class prefix ONCE!!!
894
+			$class_name = $class_prefix . str_replace($class_prefix, '', $class_name);
895
+		}
896
+		$class_name   = $this->class_cache->getFqnForAlias($class_name);
897
+		$class_exists = class_exists($class_name, false);
898
+		// if we're only loading the class and it already exists, then let's just return true immediately
899
+		if ($load_only && $class_exists) {
900
+			return true;
901
+		}
902
+		$arguments = is_array($arguments) ? $arguments : [$arguments];
903
+		// $this->_cache_on is toggled during the recursive loading that can occur with dependency injection
904
+		// $cache is controlled by individual calls to separate Registry loader methods like load_class()
905
+		// $load_only is also controlled by individual calls to separate Registry loader methods like load_file()
906
+		if ($this->_cache_on && $cache && ! $load_only) {
907
+			// return object if it's already cached
908
+			$cached_class = $this->_get_cached_class($class_name, $class_prefix, $arguments);
909
+			if ($cached_class !== null) {
910
+				return $cached_class;
911
+			}
912
+		}
913
+		// if the class doesn't already exist.. then we need to try and find the file and load it
914
+		if (! $class_exists) {
915
+			// get full path to file
916
+			$path = $this->_resolve_path($class_name, $type, $file_paths);
917
+			// load the file
918
+			$loaded = $this->_require_file($path, $class_name, $type, $file_paths);
919
+			// if we are only loading a file but NOT instantiating an object
920
+			// then return boolean for whether class was loaded or not
921
+			if ($load_only) {
922
+				return $loaded;
923
+			}
924
+			// if an object was expected but loading failed, then return nothing
925
+			if (! $loaded) {
926
+				return null;
927
+			}
928
+		}
929
+		// instantiate the requested object
930
+		$class_obj = $this->_create_object($class_name, $arguments, $type, $from_db);
931
+		if ($this->_cache_on && $cache) {
932
+			// save it for later... kinda like gum  { : $
933
+			$this->_set_cached_class(
934
+				$class_obj,
935
+				$class_name,
936
+				$class_prefix,
937
+				$from_db,
938
+				$arguments
939
+			);
940
+		}
941
+		$this->_cache_on = true;
942
+		return $class_obj;
943
+	}
944
+
945
+
946
+	/**
947
+	 * @param string $class_name
948
+	 * @param string $default have to specify something, but not anything that will conflict
949
+	 * @return mixed|string
950
+	 */
951
+	protected function get_class_abbreviation(string $class_name, string $default = 'FANCY_BATMAN_PANTS')
952
+	{
953
+		return $this->_class_abbreviations[ $class_name ] ?? $default;
954
+	}
955
+
956
+
957
+	/**
958
+	 * attempts to find a cached version of the requested class
959
+	 * by looking in the following places:
960
+	 *        $this->{$class_abbreviation}            ie:    $this->CART
961
+	 *        $this->{$class_name}                        ie:    $this->Some_Class
962
+	 *        $this->LIB->{$class_name}                ie:    $this->LIB->Some_Class
963
+	 *        $this->addon->{$class_name}    ie:    $this->addon->Some_Addon_Class
964
+	 *
965
+	 * @param string $class_name
966
+	 * @param string $class_prefix
967
+	 * @param array  $arguments
968
+	 * @return mixed
969
+	 */
970
+	protected function _get_cached_class(
971
+		string $class_name,
972
+		string $class_prefix = '',
973
+		array $arguments = []
974
+	) {
975
+		if ($class_name === 'EE_Registry') {
976
+			return $this;
977
+		}
978
+		$class_abbreviation = $this->get_class_abbreviation($class_name);
979
+		// check if class has already been loaded, and return it if it has been
980
+		if (isset($this->{$class_abbreviation})) {
981
+			return $this->{$class_abbreviation};
982
+		}
983
+		$class_name = str_replace('\\', '_', $class_name);
984
+		if (isset($this->{$class_name})) {
985
+			return $this->{$class_name};
986
+		}
987
+		if ($class_prefix === 'addon' && $this->addons->has($class_name)) {
988
+			return $this->addons->get($class_name);
989
+		}
990
+		$object_identifier = $this->object_identifier->getIdentifier($class_name, $arguments);
991
+		if ($this->LIB->has($object_identifier)) {
992
+			return $this->LIB->get($object_identifier);
993
+		}
994
+		foreach ($this->LIB as $key => $object) {
995
+			if (
996
+				// request does not contain new arguments and therefore no args identifier
997
+				! $this->object_identifier->hasArguments($object_identifier)
998
+				// but previously cached class with args was found
999
+				&& $this->object_identifier->fqcnMatchesObjectIdentifier($class_name, $key)
1000
+			) {
1001
+				return $object;
1002
+			}
1003
+		}
1004
+		return null;
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * removes a cached version of the requested class
1010
+	 *
1011
+	 * @param string  $class_name
1012
+	 * @param boolean $addon
1013
+	 * @param array   $arguments
1014
+	 * @return boolean
1015
+	 */
1016
+	public function clear_cached_class(
1017
+		string $class_name,
1018
+		bool $addon = false,
1019
+		array $arguments = []
1020
+	): bool {
1021
+		$class_abbreviation = $this->get_class_abbreviation($class_name);
1022
+		// check if class has already been loaded, and return it if it has been
1023
+		if (isset($this->{$class_abbreviation})) {
1024
+			$this->{$class_abbreviation} = null;
1025
+			return true;
1026
+		}
1027
+		$class_name = str_replace('\\', '_', $class_name);
1028
+		if (isset($this->{$class_name})) {
1029
+			$this->{$class_name} = null;
1030
+			return true;
1031
+		}
1032
+		if ($addon && $this->addons->has($class_name)) {
1033
+			$this->addons->remove($class_name);
1034
+			return true;
1035
+		}
1036
+		$class_name = $this->object_identifier->getIdentifier($class_name, $arguments);
1037
+		if ($this->LIB->has($class_name)) {
1038
+			$this->LIB->remove($class_name);
1039
+			return true;
1040
+		}
1041
+		return false;
1042
+	}
1043
+
1044
+
1045
+	/**
1046
+	 * _set_cached_class
1047
+	 * attempts to cache the instantiated class locally
1048
+	 * in one of the following places, in the following order:
1049
+	 *        $this->{class_abbreviation}   ie:    $this->CART
1050
+	 *        $this->{$class_name}          ie:    $this->Some_Class
1051
+	 *        $this->addon->{$$class_name}    ie:    $this->addon->Some_Addon_Class
1052
+	 *        $this->LIB->{$class_name}     ie:    $this->LIB->Some_Class
1053
+	 *
1054
+	 * @param object $class_obj
1055
+	 * @param string $class_name
1056
+	 * @param string $class_prefix
1057
+	 * @param bool   $from_db
1058
+	 * @param array  $arguments
1059
+	 * @return void
1060
+	 */
1061
+	protected function _set_cached_class(
1062
+		$class_obj,
1063
+		string $class_name,
1064
+		string $class_prefix = '',
1065
+		bool $from_db = false,
1066
+		array $arguments = []
1067
+	) {
1068
+		if ($class_name === 'EE_Registry' || empty($class_obj)) {
1069
+			return;
1070
+		}
1071
+		// return newly instantiated class
1072
+		$class_abbreviation = $this->get_class_abbreviation($class_name, '');
1073
+		if ($class_abbreviation) {
1074
+			$this->{$class_abbreviation} = $class_obj;
1075
+			return;
1076
+		}
1077
+		$class_name = str_replace('\\', '_', $class_name);
1078
+		if (property_exists($this, $class_name)) {
1079
+			$this->{$class_name} = $class_obj;
1080
+			return;
1081
+		}
1082
+		if ($class_prefix === 'addon') {
1083
+			$this->addons->add($class_name, $class_obj);
1084
+			return;
1085
+		}
1086
+		if (! $from_db) {
1087
+			$class_name = $this->object_identifier->getIdentifier($class_name, $arguments);
1088
+			$this->LIB->add($class_name, $class_obj);
1089
+		}
1090
+	}
1091
+
1092
+
1093
+	/**
1094
+	 * attempts to find a full valid filepath for the requested class.
1095
+	 * loops thru each of the base paths in the $file_paths array and appends : "{classname} . {file type} . php"
1096
+	 * then returns that path if the target file has been found and is readable
1097
+	 *
1098
+	 * @param string $class_name
1099
+	 * @param string $type
1100
+	 * @param array  $file_paths
1101
+	 * @return string | bool
1102
+	 */
1103
+	protected function _resolve_path(string $class_name, string $type = '', array $file_paths = [])
1104
+	{
1105
+		// make sure $file_paths is an array
1106
+		$file_paths = is_array($file_paths)
1107
+			? $file_paths
1108
+			: [$file_paths];
1109
+		// cycle thru paths
1110
+		foreach ($file_paths as $key => $file_path) {
1111
+			// convert all separators to proper /, if no filepath, then use EE_CLASSES
1112
+			$file_path = $file_path
1113
+				? str_replace(['/', '\\'], '/', $file_path)
1114
+				: EE_CLASSES;
1115
+			// prep file type
1116
+			$type = ! empty($type)
1117
+				? trim($type, '.') . '.'
1118
+				: '';
1119
+			// build full file path
1120
+			$file_paths[ $key ] = rtrim($file_path, '/') . '/' . $class_name . '.' . $type . 'php';
1121
+			// does the file exist and can be read ?
1122
+			if (is_readable($file_paths[ $key ])) {
1123
+				return $file_paths[ $key ];
1124
+			}
1125
+		}
1126
+		return false;
1127
+	}
1128
+
1129
+
1130
+	/**
1131
+	 * basically just performs a require_once()
1132
+	 * but with some error handling
1133
+	 *
1134
+	 * @param string $path
1135
+	 * @param string $class_name
1136
+	 * @param string $type
1137
+	 * @param array  $file_paths
1138
+	 * @return bool
1139
+	 * @throws EE_Error
1140
+	 * @throws ReflectionException
1141
+	 */
1142
+	protected function _require_file(string $path, string $class_name, string $type = '', array $file_paths = []): bool
1143
+	{
1144
+		$this->resolve_legacy_class_parent($class_name);
1145
+		// don't give up! you gotta...
1146
+		try {
1147
+			// does the file exist and can it be read ?
1148
+			if (! $path) {
1149
+				// just in case the file has already been autoloaded,
1150
+				// but discrepancies in the naming schema are preventing it from
1151
+				// being loaded via one of the EE_Registry::load_*() methods,
1152
+				// then let's try one last hail mary before throwing an exception
1153
+				// and call class_exists() again, but with autoloading turned ON
1154
+				if (class_exists($class_name)) {
1155
+					return true;
1156
+				}
1157
+				// so sorry, can't find the file
1158
+				throw new EE_Error(
1159
+					sprintf(
1160
+						esc_html__(
1161
+							'The %1$s file %2$s could not be located or is not readable due to file permissions. Please ensure that the following filepath(s) are correct: %3$s',
1162
+							'event_espresso'
1163
+						),
1164
+						trim($type, '.'),
1165
+						$class_name,
1166
+						'<br />' . implode(',<br />', $file_paths)
1167
+					)
1168
+				);
1169
+			}
1170
+			// get the file
1171
+			require_once($path);
1172
+			// if the class isn't already declared somewhere
1173
+			if (class_exists($class_name, false) === false) {
1174
+				// so sorry, not a class
1175
+				throw new EE_Error(
1176
+					sprintf(
1177
+						esc_html__(
1178
+							'The %s file %s does not appear to contain the %s Class.',
1179
+							'event_espresso'
1180
+						),
1181
+						$type,
1182
+						$path,
1183
+						$class_name
1184
+					)
1185
+				);
1186
+			}
1187
+		} catch (EE_Error $e) {
1188
+			$e->get_error();
1189
+			return false;
1190
+		}
1191
+		return true;
1192
+	}
1193
+
1194
+
1195
+	/**
1196
+	 * Some of our legacy classes that extended a parent class would simply use a require() statement
1197
+	 * before their class declaration in order to ensure that the parent class was loaded.
1198
+	 * This is not ideal, but it's nearly impossible to determine the parent class of a non-namespaced class,
1199
+	 * without triggering a fatal error because the parent class has yet to be loaded and therefore doesn't exist.
1200
+	 *
1201
+	 * @param string $class_name
1202
+	 */
1203
+	protected function resolve_legacy_class_parent(string $class_name = '')
1204
+	{
1205
+		try {
1206
+			$legacy_parent_class_map = [
1207
+				'EE_Payment_Processor' => 'core/business/EE_Processor_Base.class.php',
1208
+			];
1209
+			if (isset($legacy_parent_class_map[ $class_name ])) {
1210
+				require_once EE_PLUGIN_DIR_PATH . $legacy_parent_class_map[ $class_name ];
1211
+			}
1212
+		} catch (Exception $exception) {
1213
+		}
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * _create_object
1219
+	 * Attempts to instantiate the requested class via any of the
1220
+	 * commonly used instantiation methods employed throughout EE.
1221
+	 * The priority for instantiation is as follows:
1222
+	 *        - abstract classes or any class flagged as "load only" (no instantiation occurs)
1223
+	 *        - model objects via their 'new_instance_from_db' method
1224
+	 *        - model objects via their 'new_instance' method
1225
+	 *        - "singleton" classes" via their 'instance' method
1226
+	 *    - standard instantiable classes via their __constructor
1227
+	 * Prior to instantiation, if the classname exists in the dependency_map,
1228
+	 * then the constructor for the requested class will be examined to determine
1229
+	 * if any dependencies exist, and if they can be injected.
1230
+	 * If so, then those classes will be added to the array of arguments passed to the constructor
1231
+	 *
1232
+	 * @param string $class_name
1233
+	 * @param array  $arguments
1234
+	 * @param string $type
1235
+	 * @param bool   $from_db
1236
+	 * @return null|object|bool
1237
+	 * @throws InvalidArgumentException
1238
+	 * @throws InvalidInterfaceException
1239
+	 * @throws EE_Error
1240
+	 * @throws ReflectionException
1241
+	 * @throws InvalidDataTypeException
1242
+	 */
1243
+	protected function _create_object(
1244
+		string $class_name,
1245
+		array $arguments = [],
1246
+		string $type = '',
1247
+		bool $from_db = false
1248
+	) {
1249
+		// create reflection
1250
+		$reflector = $this->mirror->getReflectionClass($class_name);
1251
+		// make sure arguments are an array
1252
+		$arguments = is_array($arguments)
1253
+			? $arguments
1254
+			: [$arguments];
1255
+		// and if arguments array is numerically and sequentially indexed, then we want it to remain as is,
1256
+		// else wrap it in an additional array so that it doesn't get split into multiple parameters
1257
+		$arguments = $this->_array_is_numerically_and_sequentially_indexed($arguments)
1258
+			? $arguments
1259
+			: [$arguments];
1260
+		// attempt to inject dependencies ?
1261
+		if ($this->_dependency_map->has($class_name)) {
1262
+			$arguments = $this->_resolve_dependencies($reflector, $class_name, $arguments);
1263
+		}
1264
+		// instantiate the class if possible
1265
+		if ($reflector->isAbstract()) {
1266
+			// nothing to instantiate, loading file was enough
1267
+			// does not throw an exception so $instantiation_mode is unused
1268
+			// $instantiation_mode = "1) no constructor abstract class";
1269
+			return true;
1270
+		}
1271
+		if (
1272
+			empty($arguments)
1273
+			&& $this->mirror->getConstructorFromReflection($reflector) === null
1274
+			&& $reflector->isInstantiable()
1275
+		) {
1276
+			// no constructor = static methods only... nothing to instantiate, loading file was enough
1277
+			// $instantiation_mode = "2) no constructor but instantiable";
1278
+			return $reflector->newInstance();
1279
+		}
1280
+		if ($from_db && method_exists($class_name, 'new_instance_from_db')) {
1281
+			// $instantiation_mode = "3) new_instance_from_db()";
1282
+			return call_user_func_array([$class_name, 'new_instance_from_db'], $arguments);
1283
+		}
1284
+		if (method_exists($class_name, 'new_instance')) {
1285
+			// $instantiation_mode = "4) new_instance()";
1286
+			return call_user_func_array([$class_name, 'new_instance'], $arguments);
1287
+		}
1288
+		if (method_exists($class_name, 'instance')) {
1289
+			// $instantiation_mode = "5) instance()";
1290
+			return call_user_func_array([$class_name, 'instance'], $arguments);
1291
+		}
1292
+		if ($reflector->isInstantiable()) {
1293
+			// $instantiation_mode = "6) constructor";
1294
+			return $reflector->newInstanceArgs($arguments);
1295
+		}
1296
+		// heh ? something's not right !
1297
+		throw new EE_Error(
1298
+			sprintf(
1299
+				esc_html__('The %s file %s could not be instantiated.', 'event_espresso'),
1300
+				$type,
1301
+				$class_name
1302
+			)
1303
+		);
1304
+	}
1305
+
1306
+
1307
+	/**
1308
+	 * @see http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
1309
+	 * @param array $array
1310
+	 * @return bool
1311
+	 */
1312
+	protected function _array_is_numerically_and_sequentially_indexed(array $array): bool
1313
+	{
1314
+		return empty($array) || array_keys($array) === range(0, count($array) - 1);
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * _resolve_dependencies
1320
+	 * examines the constructor for the requested class to determine
1321
+	 * if any dependencies exist, and if they can be injected.
1322
+	 * If so, then those classes will be added to the array of arguments passed to the constructor
1323
+	 * PLZ NOTE: this is achieved by type hinting the constructor params
1324
+	 * For example:
1325
+	 *        if attempting to load a class "Foo" with the following constructor:
1326
+	 *        __construct( Bar $bar_class, Fighter $grohl_class )
1327
+	 *        then $bar_class and $grohl_class will be added to the $arguments array,
1328
+	 *        but only IF they are NOT already present in the incoming arguments array,
1329
+	 *        and the correct classes can be loaded
1330
+	 *
1331
+	 * @param ReflectionClass $reflector
1332
+	 * @param string          $class_name
1333
+	 * @param array           $arguments
1334
+	 * @return array
1335
+	 * @throws InvalidArgumentException
1336
+	 * @throws InvalidDataTypeException
1337
+	 * @throws InvalidInterfaceException
1338
+	 * @throws ReflectionException
1339
+	 */
1340
+	protected function _resolve_dependencies(
1341
+		ReflectionClass $reflector,
1342
+		string $class_name,
1343
+		array $arguments = []
1344
+	): array {
1345
+		// let's examine the constructor
1346
+		$constructor = $this->mirror->getConstructorFromReflection($reflector);
1347
+		// whu? huh? nothing?
1348
+		if (! $constructor) {
1349
+			return $arguments;
1350
+		}
1351
+		// get constructor parameters
1352
+		$params = $this->mirror->getParametersFromReflection($reflector);
1353
+		// and the keys for the incoming arguments array so that we can compare existing arguments with what is expected
1354
+		$argument_keys = array_keys($arguments);
1355
+		// now loop thru all of the constructors expected parameters
1356
+		foreach ($params as $index => $param) {
1357
+			try {
1358
+				// is this a dependency for a specific class ?
1359
+				$param_class = $this->mirror->getParameterClassName($param, $class_name, $index);
1360
+			} catch (ReflectionException $exception) {
1361
+				// uh-oh... most likely a legacy class that has not been autoloaded
1362
+				// let's try to derive the classname from what we have now
1363
+				// and hope that the property var name is close to the class name
1364
+				$param_class = $param->getName();
1365
+				$param_class = str_replace('_', ' ', $param_class);
1366
+				$param_class = ucwords($param_class);
1367
+				$param_class = str_replace(' ', '_', $param_class);
1368
+			}
1369
+			// BUT WAIT !!! This class may be an alias for something else (or getting replaced at runtime)
1370
+			$param_class = $this->class_cache->isAlias($param_class, $class_name)
1371
+				? $this->class_cache->getFqnForAlias($param_class, $class_name)
1372
+				: $param_class;
1373
+			// param is not even a class
1374
+			if (
1375
+				! empty($param_class)
1376
+				&& ! class_exists($param_class, false)
1377
+				// and something already exists in the incoming arguments for this param
1378
+				&& array_key_exists($index, $argument_keys)
1379
+				&& isset($arguments[ $argument_keys[ $index ] ])
1380
+			) {
1381
+				// so let's skip this argument and move on to the next
1382
+				continue;
1383
+			}
1384
+			// parameter is type hinted as a class
1385
+			if ($param_class !== null) {
1386
+				// parameter exists as an incoming argument, AND it's the correct class
1387
+				if (
1388
+					array_key_exists($index, $argument_keys)
1389
+					&& isset($arguments[ $argument_keys[ $index ] ])
1390
+					&& $arguments[ $argument_keys[ $index ] ] instanceof $param_class
1391
+				) {
1392
+					// skip this argument and move on to the next
1393
+					continue;
1394
+				}
1395
+				// parameter should be injected
1396
+				if ($this->_dependency_map->has_dependency_for_class($class_name, $param_class)) {
1397
+					$arguments = $this->_resolve_dependency(
1398
+						$class_name,
1399
+						$param_class,
1400
+						$arguments,
1401
+						$index
1402
+					);
1403
+				}
1404
+			}
1405
+			if (empty($arguments[ $index ])) {
1406
+				$default_value = $this->mirror->getParameterDefaultValue(
1407
+					$param,
1408
+					$class_name,
1409
+					$index
1410
+				);
1411
+				// if there's no default value, and the incoming argument is an array (albeit empty), then use that
1412
+				$arguments[ $index ] = $default_value === null
1413
+									   && isset($arguments[ $index ])
1414
+									   && is_array($arguments[ $index ])
1415
+					? $arguments[ $index ]
1416
+					: $default_value;
1417
+			}
1418
+		}
1419
+		return $arguments;
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * @param string $class_name
1425
+	 * @param string $param_class
1426
+	 * @param array  $arguments
1427
+	 * @param mixed  $index
1428
+	 * @return array
1429
+	 * @throws InvalidArgumentException
1430
+	 * @throws InvalidInterfaceException
1431
+	 * @throws InvalidDataTypeException
1432
+	 */
1433
+	protected function _resolve_dependency(string $class_name, string $param_class, array $arguments, $index): array
1434
+	{
1435
+		$dependency = null;
1436
+		// should dependency be loaded from cache ?
1437
+		$cache_on = $this->_dependency_map->loading_strategy_for_class_dependency(
1438
+			$class_name,
1439
+			$param_class
1440
+		);
1441
+		$cache_on = $cache_on !== EE_Dependency_Map::load_new_object;
1442
+		// we might have a dependency...
1443
+		// let's MAYBE try and find it in our cache if that's what's been requested
1444
+		$cached_class = $cache_on
1445
+			? $this->_get_cached_class($param_class)
1446
+			: null;
1447
+		// and grab it if it exists
1448
+		if ($cached_class instanceof $param_class) {
1449
+			$dependency = $cached_class;
1450
+		} elseif ($param_class !== $class_name) {
1451
+			// obtain the loader method from the dependency map
1452
+			$loader = $this->_dependency_map->class_loader($param_class);
1453
+			// is loader a custom closure ?
1454
+			if ($loader instanceof Closure) {
1455
+				$dependency = $loader($arguments);
1456
+			} else {
1457
+				// set the cache on property for the recursive loading call
1458
+				$this->_cache_on = $cache_on;
1459
+				// if not, then let's try and load it via the registry
1460
+				if ($loader && method_exists($this, $loader)) {
1461
+					$dependency = $this->{$loader}($param_class);
1462
+				} else {
1463
+					$dependency = LoaderFactory::getLoader()->load(
1464
+						$param_class,
1465
+						[],
1466
+						$cache_on
1467
+					);
1468
+				}
1469
+			}
1470
+		}
1471
+		// did we successfully find the correct dependency ?
1472
+		if ($dependency instanceof $param_class) {
1473
+			// then let's inject it into the incoming array of arguments at the correct location
1474
+			$arguments[ $index ] = $dependency;
1475
+		}
1476
+		return $arguments;
1477
+	}
1478
+
1479
+
1480
+	/**
1481
+	 * call any loader that's been registered in the EE_Dependency_Map::$_class_loaders array
1482
+	 *
1483
+	 * @param string $classname PLEASE NOTE: the class name needs to match what's registered
1484
+	 *                          in the EE_Dependency_Map::$_class_loaders array,
1485
+	 *                          including the class prefix, ie: "EE_", "EEM_", "EEH_", etc
1486
+	 * @param array  $arguments
1487
+	 * @return object
1488
+	 */
1489
+	public static function factory(string $classname, array $arguments = [])
1490
+	{
1491
+		$loader = self::instance()->_dependency_map->class_loader($classname);
1492
+		if ($loader instanceof Closure) {
1493
+			return $loader($arguments);
1494
+		}
1495
+		if (method_exists(self::instance(), $loader)) {
1496
+			return self::instance()->{$loader}($classname, $arguments);
1497
+		}
1498
+		return null;
1499
+	}
1500
+
1501
+
1502
+	/**
1503
+	 * Gets the addon by its class name
1504
+	 *
1505
+	 * @param string $class_name
1506
+	 * @return EE_Addon
1507
+	 */
1508
+	public function getAddon(string $class_name): ?EE_Addon
1509
+	{
1510
+		$class_name = str_replace('\\', '_', $class_name);
1511
+		return $this->addons->{$class_name} ?? null;
1512
+	}
1513
+
1514
+
1515
+	/**
1516
+	 * removes the addon from the internal cache
1517
+	 *
1518
+	 * @param string $class_name
1519
+	 * @return void
1520
+	 */
1521
+	public function removeAddon(string $class_name)
1522
+	{
1523
+		$class_name = str_replace('\\', '_', $class_name);
1524
+		$this->addons->remove($class_name);
1525
+	}
1526
+
1527
+
1528
+	/**
1529
+	 * Gets the addon by its name/slug (not classname. For that, just
1530
+	 * use the get_addon() method above
1531
+	 *
1532
+	 * @param string $name
1533
+	 * @return EE_Addon
1534
+	 */
1535
+	public function get_addon_by_name(string $name): ?EE_Addon
1536
+	{
1537
+		foreach ($this->addons as $addon) {
1538
+			if ($addon->name() === $name) {
1539
+				return $addon;
1540
+			}
1541
+		}
1542
+		return null;
1543
+	}
1544
+
1545
+
1546
+	/**
1547
+	 * Gets an array of all the registered addons, where the keys are their names.
1548
+	 * (ie, what each returns for their name() function)
1549
+	 * They're already available on EE_Registry::instance()->addons as properties,
1550
+	 * where each property's name is the addon's classname,
1551
+	 * So if you just want to get the addon by classname,
1552
+	 * OR use the get_addon() method above.
1553
+	 * PLEASE  NOTE:
1554
+	 * addons with Fully Qualified Class Names
1555
+	 * have had the namespace separators converted to underscores,
1556
+	 * so a classname like Fully\Qualified\ClassName
1557
+	 * would have been converted to Fully_Qualified_ClassName
1558
+	 *
1559
+	 * @return EE_Addon[] where the KEYS are the addon's name()
1560
+	 */
1561
+	public function get_addons_by_name(): array
1562
+	{
1563
+		$addons = [];
1564
+		foreach ($this->addons as $addon) {
1565
+			$addons[ $addon->name() ] = $addon;
1566
+		}
1567
+		return $addons;
1568
+	}
1569
+
1570
+
1571
+	/**
1572
+	 * Resets the specified model's instance AND makes sure EE_Registry doesn't keep
1573
+	 * a stale copy of it around
1574
+	 *
1575
+	 * @param string $model_name
1576
+	 * @return EEM_Base
1577
+	 * @throws EE_Error
1578
+	 * @throws ReflectionException
1579
+	 */
1580
+	public function reset_model(string $model_name): ?EEM_Base
1581
+	{
1582
+		$model_class_name = strpos($model_name, 'EEM_') !== 0
1583
+			? "EEM_$model_name"
1584
+			: $model_name;
1585
+		if (! $this->LIB->has($model_class_name)) {
1586
+			return null;
1587
+		}
1588
+		$model = $this->LIB->get($model_class_name);
1589
+		if (! $model instanceof EEM_Base) {
1590
+			return null;
1591
+		}
1592
+		// get that model reset it and make sure we nuke the old reference to it
1593
+		if ($model instanceof $model_class_name && is_callable([$model_class_name, 'reset'])) {
1594
+			$this->LIB->remove($model_class_name);
1595
+			$this->LIB->add($model_class_name, $model->reset());
1596
+		} else {
1597
+			throw new EE_Error(
1598
+				sprintf(
1599
+					esc_html__('Model %s does not have a method "reset"', 'event_espresso'),
1600
+					$model_name
1601
+				)
1602
+			);
1603
+		}
1604
+		return $model;
1605
+	}
1606
+
1607
+
1608
+	/**
1609
+	 * Resets the registry.
1610
+	 * The criteria for what gets reset is based on what can be shared between sites on the same request when
1611
+	 * switch_to_blog is used in a multisite install.  Here is a list of things that are NOT reset.
1612
+	 * - $_dependency_map
1613
+	 * - $_class_abbreviations
1614
+	 * - $NET_CFG (EE_Network_Config): The config is shared network wide so no need to reset.
1615
+	 * - $REQ:  Still on the same request so no need to change.
1616
+	 * - $CAP: There is no site specific state in the EE_Capability class.
1617
+	 * - $SSN: Although ideally, the session should not be shared between site switches, we can't reset it because only
1618
+	 * one Session can be active in a single request.  Resetting could resolve in "headers already sent" errors.
1619
+	 * - $addons:  In multisite, the state of the addons is something controlled via hooks etc in a normal request.  So
1620
+	 *             for now, we won't reset the addons because it could break calls to an add-ons class/methods in the
1621
+	 *             switch or on the restore.
1622
+	 * - $modules
1623
+	 * - $shortcodes
1624
+	 * - $widgets
1625
+	 *
1626
+	 * @param boolean $hard             [deprecated]
1627
+	 * @param boolean $reinstantiate    whether to create new instances of EE_Registry's singletons too,
1628
+	 *                                  or just reset without re-instantiating (handy to set to FALSE if you're not
1629
+	 *                                  sure if you CAN currently reinstantiate the singletons at the moment)
1630
+	 * @param bool    $reset_models     Defaults to true.  When false, then the models are not reset.  This is so
1631
+	 *                                  client
1632
+	 *                                  code instead can just change the model context to a different blog id if
1633
+	 *                                  necessary
1634
+	 * @return EE_Registry
1635
+	 * @throws EE_Error
1636
+	 * @throws ReflectionException
1637
+	 */
1638
+	public static function reset(bool $hard = false, bool $reinstantiate = true, bool $reset_models = true): EE_Registry
1639
+	{
1640
+		$instance            = self::instance();
1641
+		$instance->_cache_on = true;
1642
+		// reset some "special" classes
1643
+		EEH_Activation::reset();
1644
+		$hard                     = apply_filters('FHEE__EE_Registry__reset__hard', $hard);
1645
+		$instance->CFG            = EE_Config::reset($hard, $reinstantiate);
1646
+		$instance->CART           = null;
1647
+		$instance->MRM            = null;
1648
+		$instance->AssetsRegistry = LoaderFactory::getLoader()->getShared(
1649
+			'EventEspresso\core\services\assets\Registry'
1650
+		);
1651
+		// messages reset
1652
+		EED_Messages::reset();
1653
+		// handle of objects cached on LIB
1654
+		foreach (['LIB', 'modules'] as $cache) {
1655
+			foreach ($instance->{$cache} as $class_name => $class) {
1656
+				if (self::_reset_and_unset_object($class, $reset_models)) {
1657
+					unset($instance->{$cache}->{$class_name});
1658
+				}
1659
+			}
1660
+		}
1661
+		return $instance;
1662
+	}
1663
+
1664
+
1665
+	/**
1666
+	 * if passed object implements ResettableInterface, then call it's reset() method
1667
+	 * if passed object implements InterminableInterface, then return false,
1668
+	 * to indicate that it should NOT be cleared from the Registry cache
1669
+	 *
1670
+	 * @param      $object
1671
+	 * @param bool $reset_models
1672
+	 * @return bool returns true if cached object should be unset
1673
+	 * @throws EE_Error
1674
+	 * @throws ReflectionException
1675
+	 */
1676
+	private static function _reset_and_unset_object($object, bool $reset_models): bool
1677
+	{
1678
+		if (! is_object($object)) {
1679
+			// don't unset anything that's not an object
1680
+			return false;
1681
+		}
1682
+		if ($object instanceof EED_Module) {
1683
+			$object::reset();
1684
+			// don't unset modules
1685
+			return false;
1686
+		}
1687
+		if ($object instanceof ResettableInterface) {
1688
+			if ($object instanceof EEM_Base) {
1689
+				if ($reset_models) {
1690
+					$object->reset();
1691
+					return true;
1692
+				}
1693
+				return false;
1694
+			}
1695
+			$object->reset();
1696
+			return true;
1697
+		}
1698
+		if (! $object instanceof InterminableInterface) {
1699
+			return true;
1700
+		}
1701
+		return false;
1702
+	}
1703
+
1704
+
1705
+	/**
1706
+	 * Gets all the custom post type models defined
1707
+	 *
1708
+	 * @return array keys are model "short names" (Eg "Event") and keys are classnames (eg "EEM_Event")
1709
+	 */
1710
+	public function cpt_models(): array
1711
+	{
1712
+		$cpt_models = [];
1713
+		foreach ($this->non_abstract_db_models as $short_name => $classname) {
1714
+			if (is_subclass_of($classname, 'EEM_CPT_Base')) {
1715
+				$cpt_models[ $short_name ] = $classname;
1716
+			}
1717
+		}
1718
+		return $cpt_models;
1719
+	}
1720
+
1721
+
1722
+	/**
1723
+	 * @return EE_Config
1724
+	 */
1725
+	public static function CFG(): EE_Config
1726
+	{
1727
+		return self::instance()->CFG;
1728
+	}
1729
+
1730
+
1731
+	/**
1732
+	 * @param string $class_name
1733
+	 * @return ReflectionClass
1734
+	 * @throws ReflectionException
1735
+	 * @throws InvalidDataTypeException
1736
+	 * @deprecated 4.9.62.p
1737
+	 */
1738
+	public function get_ReflectionClass(string $class_name): ReflectionClass
1739
+	{
1740
+		return $this->mirror->getReflectionClass($class_name);
1741
+	}
1742 1742
 }
Please login to merge, or discard this patch.