Passed
Push — master ( 60bba1...80f1a5 )
by Andreas
28:42
created

midcom_services_indexer::new_document()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.7414

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 1
dl 0
loc 17
ccs 3
cts 9
cp 0.3333
crap 8.7414
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
     * @var midcom_services_indexer_backend
34
     */
35
    private $_backend;
36
37
    /**
38
     * @var boolean
39
     */
40
    private $_disabled;
41
42
    /**
43
     * Initialization
44
     */
45 1
    public function __construct(midcom_services_indexer_backend $backend = null)
46
    {
47 1
        $this->_backend = $backend;
48 1
        $this->_disabled = $this->_backend === null;
49 1
    }
50
51
    public static function getSubscribedEvents()
52
    {
53
        return [dbaevent::DELETE => ['handle_delete']];
54
    }
55
56
    public function handle_delete(dbaevent $event)
57
    {
58
        $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

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