Passed
Push — master ( 80f1a5...adc7a1 )
by Andreas
26:12
created

midcom_services_indexer::new_document()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 7.9062

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 14
ccs 3
cts 8
cp 0.375
crap 7.9062
rs 10
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 factory method will work even if the indexer is disabled. You can check this
236
     * with the enabled() method of this class.
237
     *
238
     * @todo Move to a full factory pattern here to save document php file parsings where possible.
239
     *     This means that all document creations will in the future be handled by this method.
240
     */
241 2
    public function new_document(object $object) : midcom_services_indexer_document
242
    {
243
        // Scan for datamanager instances.
244 2
        if (is_a($object, 'midcom\datamanager\datamanager')) {
245 2
            return new midcom\datamanager\indexer\document($object);
246
        }
247
        if (is_a($object, 'midcom_helper_datamanager2_datamanager')) {
248
            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...
249
        }
250
251
        if ($object instanceof midcom_core_dbaobject) {
252
            return new midcom_services_indexer_document_midcom($object);
253
        }
254
        throw new midcom_error('Unsupported object type');
255
    }
256
}
257