Completed
Push — develop ( fc3971...71605d )
by David
03:09 queued 11s
created

Analysis_Response_Ops::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file provides a class to manipulate the analysis response.
4
 *
5
 * @author David Riccitelli <[email protected]>
6
 * @since 3.25.0
7
 * @package Wordlift\Analysis\Response
8
 */
9
10
namespace Wordlift\Analysis\Response;
11
12
use stdClass;
13
use Wordlift\Entity\Entity_Helper;
14
15
class Analysis_Response_Ops {
16
17
	/**
18
	 * The analysis response json.
19
	 *
20
	 * @since 3.21.5
21
	 * @access private
22
	 * @var mixed $json Holds the analysis response json.
23
	 */
24
	private $json;
25
26
	/**
27
	 * Holds the {@link Wordlift_Entity_Uri_Service}.
28
	 *
29
	 * @since 3.21.5
30
	 * @access private
31
	 * @var \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
32
	 */
33
	private $entity_uri_service;
34
35
	private $entity_service;
36
37
	/**
38
	 * @var \Wordlift_Entity_Type_Service
39
	 */
40
	private $entity_type_service;
41
	/**
42
	 * @var \Wordlift_Post_Image_Storage
43
	 */
44
	private $post_image_storage;
45
46
	/**
47
	 * @var Entity_Helper
48
	 */
49
	private $entity_helper;
50
51
	/**
52
	 * Analysis_Response_Ops constructor.
53
	 *
54
	 * @param \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service}.
55
	 * @param \Wordlift_Entity_Service $entity_service The {@link Wordlift_Entity_Service}.
56
	 * @param \Wordlift_Entity_Type_Service $entity_type_service The {@link Wordlift_Entity_Type_Service}.
57
	 * @param \Wordlift_Post_Image_Storage $post_image_storage A {@link Wordlift_Post_Image_Storage} instance.
58
	 * @param Entity_Helper $entity_helper The {@link Entity_Helper}.
59
	 * @param mixed $json The analysis response json.
60
	 *
61
	 * @since 3.21.5
62
	 */
63 View Code Duplication
	public function __construct( $entity_uri_service, $entity_service, $entity_type_service, $post_image_storage, $entity_helper, $json ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
65
		$this->json                = $json;
66
		$this->entity_uri_service  = $entity_uri_service;
67
		$this->entity_service      = $entity_service;
68
		$this->entity_type_service = $entity_type_service;
69
		$this->post_image_storage  = $post_image_storage;
70
		$this->entity_helper       = $entity_helper;
71
72
	}
73
74
	/**
75
	 * Switches remote entities, i.e. entities with id outside the local dataset, to local entities.
76
	 *
77
	 * The function takes all the entities that have an id which is not local. For each remote entity, a list of URIs
78
	 * is built comprising the entity id and the sameAs. Then a query is issued in the local database to find potential
79
	 * matches from the local vocabulary.
80
	 *
81
	 * If found, the entity id is swapped with the local id and the remote id is added to the sameAs.
82
	 *
83
	 * @return Analysis_Response_Ops The current Analysis_Response_Ops instance.
84
	 */
85
	public function make_entities_local() {
86
87
		if ( ! isset( $this->json->entities ) ) {
88
			return $this;
89
		}
90
91
		// Get the URIs.
92
		$uris     = array_keys( get_object_vars( $this->json->entities ) );
93
		$mappings = $this->entity_helper->map_many_to_local( $uris );
94
95
		foreach ( $mappings as $external_uri => $internal_uri ) {
96
97
			// Move the data from the external URI to the internal URI.
98
			if ( ! isset( $this->json->entities->{$internal_uri} ) ) {
99
				$this->json->entities->{$internal_uri} = $this->json->entities->{$external_uri};
100
			}
101
102
			// Ensure sameAs is an array.
103
			if ( ! isset( $this->json->entities->{$internal_uri}->sameAs )
104
			     || ! is_array( $this->json->entities->{$internal_uri}->sameAs ) ) {
105
				$this->json->entities->{$internal_uri}->sameAs = array();
106
			}
107
108
			// Add the external URI as sameAs.
109
			$this->json->entities->{$internal_uri}->sameAs[] = $external_uri;
110
111
			// Finally remove the external URI.
112
			unset( $this->json->entities->{$external_uri} );
113
		}
114
115
		if ( isset( $this->json->annotations ) ) {
116
			foreach ( $this->json->annotations as $key => $annotation ) {
117
				if ( isset( $annotation->entityMatches ) ) {
118
					foreach ( $annotation->entityMatches as $match ) {
119
						if ( isset( $match->entityId ) && isset( $mappings[ $match->entityId ] ) ) {
120
							$match->entityId = $mappings[ $match->entityId ];
121
						}
122
					}
123
				}
124
			}
125
		}
126
127
		return $this;
128
	}
129
130
	/**
131
	 * Add occurrences by parsing the provided html content.
132
	 *
133
	 * @param string $content The html content with annotations.
134
	 *
135
	 * @return Analysis_Response_Ops The {@link Analysis_Response_Ops} instance.
136
	 *
137
	 * @since 3.23.7 refactor the regex pattern to take into account that there might be css classes between textannotation
138
	 *  and disambiguated.
139
	 *
140
	 * @link https://github.com/insideout10/wordlift-plugin/issues/1001
141
	 */
142
	public function add_occurrences( $content ) {
143
144
		// Try to get all the disambiguated annotations and bail out if an error occurs.
145
		if ( false === preg_match_all(
146
				'|<span\s+id="([^"]+)"\s+class="textannotation\s+(?:\S+\s+)?disambiguated(?=[\s"])[^"]*"\s+itemid="([^"]*)">(.*?)</span>|',
147
				$content,
148
				$matches,
149
				PREG_OFFSET_CAPTURE
150
			) ) {
151
			return $this;
152
		}
153
154
		if ( empty( $matches ) ) {
155
			return $this;
156
		}
157
158
		$parse_data = array_reduce( range( 0, count( $matches[1] ) - 1 ), function ( $carry, $i ) use ( $matches ) {
159
			if ( empty( $matches[0] ) ) {
160
				return $carry;
161
			}
162
163
			$start         = $matches[0][ $i ][1];
164
			$end           = $start + strlen( $matches[0][ $i ][0] );
165
			$annotation_id = $matches[1][ $i ][0];
166
			$item_id       = $matches[2][ $i ][0];
167
			$text          = $matches[3][ $i ][0];
168
169
			$annotation               = new StdClass;
170
			$annotation->annotationId = $annotation_id;
171
			$annotation->start        = $start;
172
			$annotation->end          = $end;
173
			$annotation->text         = $text;
174
175
			$entity_match                = new StdClass;
176
			$entity_match->confidence    = 100;
177
			$entity_match->entityId      = $item_id;
178
			$annotation->entityMatches[] = $entity_match;
179
180
			$carry['annotations'][ $annotation_id ] = $annotation;
181
			$carry['occurrences'][ $item_id ][]     = $annotation_id;
182
183
			return $carry;
184
		}, array( 'annotations' => array(), 'occurrences' => array(), ) );
185
186
		$annotations = $parse_data['annotations'];
187
		$occurrences = $parse_data['occurrences'];
188
189
		foreach ( array_keys( $occurrences ) as $item_id ) {
190
191
			// If the entity isn't there, add it.
192
			if ( ! isset( $this->json->entities->{$item_id} ) ) {
193
				$entity = $this->get_local_entity( $item_id );
194
195
				// Entity not found in the local vocabulary, continue to the next one.
196
				if ( false === $entity ) {
197
					continue;
198
				}
199
200
				$this->json->entities->{$item_id} = $entity;
201
			}
202
		}
203
204
		// Here we're adding back some data structures required by the client-side code.
205
		//
206
		// We're adding:
207
		//  1. the .entities[entity_id].occurrences array with the annotations' ids.
208
		//  2. the .entities[entity_id].annotations[annotation_id] = { id: annotation_id } map.
209
		//
210
		// Before 3.23.0 this was done by the client-side code located in src/coffee/editpost-widget/app.services.AnalysisService.coffee
211
		// function `preselect`, which was called by src/coffee/editpost-widget/app.services.EditorService.coffee in
212
		// `embedAnalysis`.
213
		foreach ( $this->json->entities as $id => $entity ) {
214
			$this->json->entities->{$id}->occurrences = isset( $occurrences[ $id ] ) ? $occurrences[ $id ] : array();;
215
216
			foreach ( $this->json->entities->{$id}->occurrences as $annotation_id ) {
217
				$this->json->entities->{$id}->annotations[ $annotation_id ] = array(
218
					'id' => $annotation_id,
219
				);
220
			}
221
		}
222
223
		// Add the missing annotations. This allows the analysis response to work also if we didn't receive results
224
		// from the analysis API.
225
		foreach ( $annotations as $annotation_id => $annotation ) {
226
227
			if ( ! isset( $this->json->annotations->{$annotation_id} ) ) {
228
				$this->json->annotations->{$annotation_id} = $annotation;
229
			}
230
231
		}
232
233
		return $this;
234
	}
235
236
	private function get_local_entity( $uri ) {
237
238
		$entity = $this->entity_uri_service->get_entity( $uri );
239
240
		if ( null === $entity ) {
241
			return false;
242
		}
243
244
		$type   = $this->entity_type_service->get( $entity->ID );
245
		$images = $this->post_image_storage->get( $entity->ID );
246
247
		return (object) array(
248
			'id'          => $uri,
249
			'label'       => $entity->post_title,
250
			'description' => $entity->post_content,
251
			'sameAs'      => wl_schema_get_value( $entity->ID, 'sameAs' ),
252
			'mainType'    => str_replace( 'wl-', '', $type['css_class'] ),
253
			'types'       => wl_get_entity_rdf_types( $entity->ID ),
254
			'images'      => $images,
255
		);
256
	}
257
258
	/**
259
	 * Return the JSON response.
260
	 *
261
	 * @return mixed The JSON response.
262
	 * @since 3.24.2
263
	 */
264
	public function get_json() {
265
266
		return $this->json;
267
	}
268
269
	/**
270
	 * Get the string representation of the JSON.
271
	 *
272
	 * @return false|string The string representation or false in case of error.
273
	 */
274
	public function to_string() {
275
276
		// Add the `JSON_UNESCAPED_UNICODE` only for PHP 5.4+.
277
		$options = ( version_compare( PHP_VERSION, '5.4', '>=' )
278
			? 256 : 0 );
279
280
		return wp_json_encode( $this->json, $options );
281
	}
282
283
}
284