Completed
Push — master ( 01c28b...0d4f0a )
by Andreas
17:26
created

midcom_services_indexer::delete_all()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 12
ccs 0
cts 8
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.services
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10
use midcom\events\dbaevent;
11
12
/**
13
 * This class is the main access point into the MidCOM Indexer subsystem.
14
 *
15
 * It allows you to maintain and query the document index.
16
 *
17
 * Do not instantiate this class directly. Instead use the get_service
18
 * method on midcom_application using the service name 'indexer' to obtain
19
 * a running instance.
20
 *
21
 * @see midcom_services_indexer_document
22
 * @see midcom_services_indexer_backend
23
 * @see midcom_services_indexer_filter
24
 *
25
 * @todo Batch indexing support
26
 * @todo Write code examples
27
 * @todo More elaborate class introduction.
28
 * @package midcom.services
29
 */
30
class midcom_services_indexer implements EventSubscriberInterface
31
{
32
    /**
33
     * The backend indexer implementation
34
     *
35
     * @var midcom_services_indexer_backend
36
     */
37
    private $_backend;
38
39
    /**
40
     * Flag for disabled indexing, set by the constructor.
41
     *
42
     * @var boolean
43
     */
44
    private $_disabled = false;
45
46
    /**
47
     * Initialization
48
     *
49
     * The constructor will initialize the indexer backend using the MidCOM
50
     * configuration by default. If you need a different indexer backend, you
51
     * can always explicitly instantiate a backend and pass it to the
52
     * constructor. In that case you have to load the corresponding PHP file
53
     * manually.
54
     *
55
     * @param midcom_services_indexer_backend $backend An explicit indexer to initialize with.
56
     */
57 1
    public function __construct($backend = null)
58
    {
59 1
        if (!midcom::get()->config->get('indexer_backend')) {
60 1
            $this->_disabled = true;
61 1
            return;
62
        }
63
64
        if ($backend === null) {
65
            $class = midcom::get()->config->get('indexer_backend');
66
            if (strpos($class, '_') === false) {
67
                // Built-in backend called using the shorthand notation
68
                $class = "midcom_services_indexer_backend_" . $class;
69
            }
70
71
            $this->_backend = new $class();
72
        } else {
73
            $this->_backend = $backend;
74
        }
75
        midcom::get()->dispatcher->addSubscriber($this);
76
    }
77
78
    public static function getSubscribedEvents()
79
    {
80
        return [dbaevent::DELETE => ['handle_delete']];
81
    }
82
83
    public function handle_delete(dbaevent $event)
84
    {
85
        $this->delete($event->get_object()->guid);
0 ignored issues
show
Bug introduced by
$event->get_object()->guid of type string is incompatible with the type array expected by parameter $RIs of midcom_services_indexer::delete(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

85
        $this->delete(/** @scrutinizer ignore-type */ $event->get_object()->guid);
Loading history...
86
    }
87
88
    /**
89
     * Simple helper, returns true if the indexer service is online, false if it is disabled.
90
     *
91
     * @return boolean Service state.
92
     */
93
    public function enabled() : bool
94
    {
95
        return !$this->_disabled;
96
    }
97
98
    /**
99
     * Adds a document to the index.
100
     *
101
     * A finished document object must be passed to this object. If the index
102
     * already contains a record with the same Resource Identifier, the record
103
     * is replaced.
104
     *
105
     * Support of batch-indexing using an Array of documents instead of a single
106
     * document is possible (and strongly advised for performance reasons).
107
     *
108
     * @param mixed $documents One or more documents to be indexed, so this is either a
109
     *           midcom_services_indexer_document or an Array of these objects.
110
     * @return boolean Indicating success.
111
     */
112 6
    public function index($documents) : bool
113
    {
114 6
        if ($this->_disabled) {
115 6
            return true;
116
        }
117
118
        $batch = is_array($documents);
119
        if (!$batch) {
120
            $documents = [$documents];
121
        }
122
        if (empty($documents)) {
123
            // Nothing to do.
124
            return true;
125
        }
126
127
        foreach ($documents as $value) {
128
            $value->members_to_fields();
129
        }
130
131
        try {
132
            $this->_backend->index($documents);
133
            return true;
134
        } catch (Exception $e) {
135
            if ($batch) {
136
                throw $e;
137
            }
138
            debug_add("Indexing error: " . $e->getMessage(), MIDCOM_LOG_ERROR);
139
            return false;
140
        }
141
    }
142
143
    /**
144
     * Removes the document(s) with the given resource identifier(s) from the index.
145
     * Using GUIDs instead of RIs will delete all language versions
146
     *
147
     * @param array $RIs The resource identifier(s) of the document(s) that should be deleted.
148
     * @return boolean Indicating success.
149
     */
150
    public function delete($RIs) : bool
151
    {
152
        if ($this->_disabled) {
153
            return true;
154
        }
155
        $RIs = (array) $RIs;
156
        if (empty($RIs)) {
157
            // Nothing to do.
158
            return true;
159
        }
160
        try {
161
            $this->_backend->delete($RIs);
162
            return true;
163
        } catch (Exception $e) {
164
            debug_add("Deleting error: " . $e->getMessage(), MIDCOM_LOG_ERROR);
165
            return false;
166
        }
167
    }
168
169
    /**
170
     * Clear the index completely.
171
     *
172
     * This will drop the current index.
173
     *
174
     * @return boolean Indicating success.
175
     */
176
    public function delete_all($constraint = '') : bool
177
    {
178
        if ($this->_disabled) {
179
            return true;
180
        }
181
182
        try {
183
            $this->_backend->delete_all($constraint);
184
            return true;
185
        } catch (Exception $e) {
186
            debug_add("Deleting error: " . $e->getMessage(), MIDCOM_LOG_ERROR);
187
            return false;
188
        }
189
    }
190
191
    /**
192
     * Query the index and, if set, restrict the query by a given filter.
193
     *
194
     * The filter argument is optional and may be a subclass of indexer_filter.
195
     * The backend determines what filters are supported and how they are
196
     * treated.
197
     *
198
     * The query syntax is also dependent on the backend. Refer to its documentation
199
     * how queries should be built.
200
     *
201
     * @param string $query The query, which must suit the backends query syntax. It is assumed to be in the site charset.
202
     * @param midcom_services_indexer_filter $filter An optional filter used to restrict the query.
203
     * @param array $options Options that are passed straight to the backend
204
     * @return midcom_services_indexer_document[] An array of documents matching the query
205
     * @todo Refactor into multiple methods
206
     */
207 3
    public function query($query, midcom_services_indexer_filter $filter = null, array $options = []) : array
208
    {
209 3
        $result = [];
210 3
        if ($this->_disabled) {
211 3
            return $result;
212
        }
213
214
        // Do charset translations
215
        $query = midcom::get()->i18n->convert_to_utf8($query);
216
217
        try {
218
            $result_raw = $this->_backend->query($query, $filter, $options);
219
        } catch (Exception $e) {
220
            debug_add("Query error: " . $e->getMessage(), MIDCOM_LOG_ERROR);
221
            return $result;
222
        }
223
224
        foreach ($result_raw as $document) {
225
            $document->fields_to_members();
226
            /**
227
             * FIXME: Rethink program flow and especially take into account that not all documents are
228
             * created by midcom or even served by midgard
229
             */
230
231
            // midgard:read verification, we simply try to create an object instance
232
            // In the case, we distinguish between MidCOM documents, where we can check
233
            // the RI identified object directly, and pure documents, where we use the
234
            // topic instead.
235
236
            // Try to check topic only if the guid is actually set
237
            if (!empty($document->topic_guid)) {
238
                try {
239
                    midcom_db_topic::get_cached($document->topic_guid);
240
                } catch (midcom_error $e) {
241
                    // Skip document, the object is hidden.
242
                    debug_add("Skipping the generic document {$document->title}, its topic seems to be invisible, we cannot proceed.");
243
                    continue;
244
                }
245
            }
246
247
            // this checks acls!
248
            if ($document->is_a('midcom')) {
249
                // Try to retrieve object:
250
                // Strip language code from end of RI if it looks like "<GUID>_<LANG>"
251
                try {
252
                    midcom::get()->dbfactory->get_object_by_guid(preg_replace('/^([0-9a-f]{32,80})_[a-z]{2}$/', '\\1', $document->RI));
253
                } catch (midcom_error $e) {
254
                    // Skip document, the object is hidden, deleted or otherwise unavailable.
255
                    //@todo Maybe nonexistent objects should be removed from index?
256
                    continue;
257
                }
258
            }
259
            $result[] = $document;
260
        }
261
        return $result;
262
    }
263
264
    /**
265
     * Try to instantiate the most specific document class for the object given in the parameter.
266
     *
267
     * This class will not return empty document base class instances if nothing
268
     * specific can be found. If you are in this situation, you need to instantiate
269
     * an appropriate document manually and populate it.
270
     *
271
     * The checking sequence is like this right now:
272
     *
273
     * 1. If a datamanager instance is passed, it is transformed into a datamanager document.
274
     * 2. If a Metadata object is passed, it is transformed into a midcom_services_indexer_document_midcom.
275
     * 3. Next, the method tries to retrieve a MidCOM Metadata object using the parameter directly. If successful,
276
     *    again, a midcom_services_indexer_document_midcom is returned.
277
     *
278
     * This factory method will work even if the indexer is disabled. You can check this
279
     * with the enabled() method of this class.
280
     *
281
     * @todo Move to a full factory pattern here to save document php file parsings where possible.
282
     *     This means that all document creations will in the future be handled by this method.
283
     *
284
     * @param object $object The object for which a document instance is required
285
     * @return midcom_services_indexer_document A valid document class as specific as possible. Returns
286
     *     false on error or if no specific class match could be found.
287
     */
288 2
    function new_document($object)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
289
    {
290
        // Scan for datamanager instances.
291 2
        if (is_a($object, 'midcom\datamanager\datamanager')) {
292 2
            debug_add('This is a datamanager document');
293 2
            return new midcom\datamanager\indexer\document($object);
294
        }
295
        if (is_a($object, 'midcom_helper_datamanager2_datamanager')) {
296
            debug_add('This is a datamanager2 document');
297
            return new midcom_helper_datamanager2_indexer_document($object);
0 ignored issues
show
Bug introduced by
The type midcom_helper_datamanager2_indexer_document was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
298
        }
299
300
        // Maybe we have a metadata object...
301
        if (is_a($object, midcom_helper_metadata::class)) {
302
            debug_add('This is a metadata document, built from a metadata object.');
303
            return new midcom_services_indexer_document_midcom($object);
304
        }
305
306
        // Try to get a metadata object for the argument passed
307
        // This should catch all DBA objects as well.
308
        if ($metadata = midcom_helper_metadata::retrieve($object)) {
309
            debug_add('Successfully fetched a Metadata object for the argument.');
310
            return new midcom_services_indexer_document_midcom($metadata);
311
        }
312
313
        // No specific match found.
314
        debug_print_r('No match found for this type:', $object);
315
        return false;
316
    }
317
}
318