root/trunk/midcom/midcom.helper.replicator/exporter.php

Revision 17766, 25.4 kB (checked in by rambo, 1 week ago)

forward port r17765

Line 
1 <?php
2 /**
3 * @package midcom.helper.replicator
4 * @author The Midgard Project, http://www.midgard-project.org
5 * @version $Id: viewer.php 3975 2006-09-06 17:36:03Z bergie $
6 * @copyright The Midgard Project, http://www.midgard-project.org
7 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
8 */
9
10 /**
11  * @package midcom.helper.replicator
12  */
13 class midcom_helper_replicator_exporter extends midcom_baseclasses_components_purecode
14 {
15     /**
16      * The subscription object the transporter has been instantiated for
17      *
18      * @var midcom_helper_replicator_subscription_dba
19      * @access protected
20      */
21     var $subscription;
22
23     var $exportability = array();
24
25     var $handled_dependencies = array();
26
27     var $_serialize_rewrite_to_delete = array();
28
29     /**
30      * Initializes the class. The real startup is done by the initialize() call.
31      *
32      * @param midcom_helper_replicator_subscription_dba $subscription Subscription
33      */
34     function __construct($subscription)
35     {
36          $this->_component = 'midcom.helper.replicator';
37
38          $this->subscription = $subscription;
39
40          parent::__construct();
41     }
42
43     /**
44      * This is a static factory method which lets you dynamically create exporter instances.
45      * It takes care of loading the required class files. The returned instances will be created
46      * but not initialized.
47      *
48      * On any error (class not found etc.) the factory method will call generate_error.
49      *
50      * <b>This function must be called statically.</b>
51      *
52      * @param midcom_helper_replicator_subscription_dba $subscription Subscription
53      * @return midcom_helper_replicator_exporter A reference to the newly created exporter instance.
54      * @static
55      */
56     function &create($subscription)
57     {
58         $type = $subscription->exporter;
59         $filename = MIDCOM_ROOT . "/midcom/helper/replicator/exporter/{$type}.php";
60
61         if (!file_exists($filename))
62         {
63             $_MIDCOM->generate_error(MIDCOM_ERRCRIT, "Requested exporter file {$type} is not installed.");
64             // This will exit.
65         }
66         require_once($filename);
67
68         $classname = "midcom_helper_replicator_exporter_{$type}";
69         if (!class_exists($classname))
70         {
71             $_MIDCOM->generate_error(MIDCOM_ERRCRIT, "Requested exporter class {$type} is not installed.");
72             // This will exit.
73         }
74
75         /**
76          * Php 4.4.1 does not allow you to return a reference to an expression.
77          * http://www.php.net/release_4_4_0.php
78          */
79         $class = new $classname($subscription);
80         return $class;
81     }
82
83     /**
84      * Serialize the privileges assigned to given object
85      *
86      * Operates directly on Midgard QB and skips all other checks too
87      */
88     function serialize_privileges(&$object)
89     {
90         debug_push_class(__CLASS__, __FUNCTION__);
91         $serializations = array();
92         $qb = new midgard_query_builder('midcom_core_privilege_db');
93         $qb->add_constraint('objectguid', '=', $object->guid);
94         // Privilege deletes (or changes) do not go through the DBA object, thus do not get exported through the watchers
95         if (method_exists($qb, 'include_deleted'))
96         {
97             $qb->include_deleted();
98         }
99         $privileges = $qb->execute();
100         if ($privileges === false)
101         {
102             // QB error
103             debug_add("Error when listing privileges to object {$object->guid}", MIDCOM_LOG_ERROR);
104             debug_pop();
105             return false;
106         }
107         foreach ($privileges as $privilege)
108         {
109             $privilege_serialized = midcom_helper_replicator_serialize($privilege);
110             if ($privilege_serialized === false)
111             {
112                 unset($privileges, $privilege, $qb);
113                 debug_add("midcom_helper_replicator_serialize returned false for privilege {$privilege->guid}, errstr: " . mgd_errstr(), MIDCOM_LOG_ERROR);
114                 debug_pop();
115                 return false;
116             }
117             $GLOBALS['midcom_helper_replicator_logger']->log_object($privilege, 'Exported');
118             $serializations[$privilege->guid] = $privilege_serialized;
119             unset($privilege_serialized);
120         }
121         unset($privileges, $privilege, $qb);
122
123         debug_pop();
124         return $serializations;
125     }
126
127     /**
128      * Serialize attachment object
129      *
130      * @param midgard_attachment &$attachment The attachment object to serialize
131      * @return array Array of exported attachments metadata and blob
132      */
133     function serialize_attachment(&$attachment)
134     {
135         debug_push_class(__CLASS__, __FUNCTION__);
136         debug_add("called for attachment {$attachment->guid}");
137         $serializations = array();
138
139         // Serialize metadata
140         $attachment_serialized = midcom_helper_replicator_serialize($attachment);
141         if ($attachment_serialized === false)
142         {
143             debug_add("midcom_helper_replicator_serialize returned false for attachment {$attachment->guid}, errstr: " . mgd_errstr(), MIDCOM_LOG_ERROR);
144             debug_pop();
145             return false;
146         }
147
148         // Serialize blob (TODO: Check if we wish to do so)
149         $attachment_blob_serialized = midcom_helper_replicator_serialize_blob($attachment);
150         if ($attachment_blob_serialized === false)
151         {
152             unset($attachment_serialized);
153             debug_add("midcom_helper_replicator_serialize_blob returned false for attachment {$attachment->guid}, errstr: " . mgd_errstr(), MIDCOM_LOG_ERROR);
154             debug_pop();
155             return false;
156         }
157
158         // Check that we can export parameters of the attachment as well before adding it to the serialized list
159         $attachment_parameters = $this->serialize_parameters($attachment);
160         if ($attachment_parameters === false)
161         {
162             unset($attachment_serialized, $attachment_blob_serialized);
163             debug_add("this->serialize_parameters returned false for attachment {$attachment->guid}", MIDCOM_LOG_ERROR);
164             debug_pop();
165             return false;
166         }
167
168         // Check that we can export privileges of the attachment as well before adding it to the serialized list
169         $attachment_privileges = $this->serialize_privileges($attachment);
170         if ($attachment_privileges === false)
171         {
172             unset($attachment_serialized, $attachment_blob_serialized);
173             debug_add("this->serialize_privileges returned false for attachment {$attachment->guid}", MIDCOM_LOG_ERROR);
174             debug_pop();
175             return false;
176         }
177
178         $serializations[$attachment->guid . '_metadata'] = $attachment_serialized;
179         $serializations[$attachment->guid . '_blob'] = $attachment_blob_serialized;
180         $serializations = array_merge($serializations, $attachment_parameters, $attachment_privileges);
181         unset($attachment_serialized, $attachment_blob_serialized, $attachment_parameters, $attachment_privileges);
182
183         debug_pop();
184         return $serializations;
185     }
186
187     /**
188      * Serialize attachments of an object
189      *
190      * @param midgard_object &$object The Object to export attachments of
191      * @return array Array of exported attachments as XML indexed by GUID
192      */
193     function serialize_attachments(&$object)
194     {
195         debug_push_class(__CLASS__, __FUNCTION__);
196         debug_add("called for object {$object->guid}");
197         $serializations = array();
198         $qb = new midgard_query_builder('midgard_attachment');
199         $qb->add_constraint('parentguid', '=', $object->guid);
200         /* PONDER: do we need this ? in theory the deletes do trigger DBA watch
201         if (method_exists($qb, 'include_deleted'))
202         {
203             $qb->include_deleted();
204         }
205         */
206         $attachments = $qb->execute();
207         if ($attachments === false)
208         {
209             // QB error
210             debug_add("Error when listing attachments to object {$object->guid}", MIDCOM_LOG_ERROR);
211             debug_pop();
212             return false;
213         }
214         foreach ($attachments as $attachment)
215         {
216             if (!$this->is_exportable($attachment))
217             {
218                 debug_add("Attachment {$attachment->guid} is not exportable", MIDCOM_LOG_INFO);
219                 continue;
220             }
221
222             $attachment_serialized = $this->serialize_attachment($attachment);
223             if ($attachment_serialized === false)
224             {
225                 unset($attachments, $attachment, $qb);
226                 debug_add("this->serialize_attachment returned false for attachment {$attachment->guid}", MIDCOM_LOG_ERROR);
227                 debug_pop();
228                 return false;
229             }
230             $GLOBALS['midcom_helper_replicator_logger']->log_object($attachment, 'Exported');
231             $serializations = array_merge($serializations, $attachment_serialized);
232             unset($attachment_serialized);
233         }
234         unset($attachments, $attachment, $qb);
235
236         debug_pop();
237         return $serializations;
238     }
239
240     /**
241      * Serialize parameters of an object
242      *
243      * @param midgard_object &$object The Object to export parameters of
244      * @return array Array of exported parameters as XML indexed by GUID
245      */
246     function serialize_parameters(&$object)
247     {
248         debug_push_class(__CLASS__, __FUNCTION__);
249         debug_add("called for object {$object->guid}");
250         $serializations = array();
251         $qb = new midgard_query_builder('midgard_parameter');
252         $qb->add_constraint('parentguid', '=', $object->guid);
253         // Parameter deletes (nor changes) do not go through the DBA object, thus do not get exported through the watchers
254         if (method_exists($qb, 'include_deleted'))
255         {
256             $qb->include_deleted();
257         }
258         $parameters = $qb->execute();
259         if ($parameters === false)
260         {
261             // QB error
262             debug_add("Error when listing parameters to object {$object->guid}", MIDCOM_LOG_ERROR);
263             debug_pop();
264             return false;
265         }
266         foreach ($parameters as $parameter)
267         {
268             if (!$this->is_exportable($parameter))
269             {
270                 debug_add("Parameter {$parameter->guid} is not exportable", MIDCOM_LOG_INFO);
271                 continue;
272             }
273
274             $parameter_serialized = midcom_helper_replicator_serialize($parameter);
275             if ($parameter_serialized === false)
276             {
277                 unset($parameters, $parameter, $qb);
278                 debug_add("midcom_helper_replicator_serialize returned false for parameter {$parameter->guid}, errstr: " . mgd_errstr(), MIDCOM_LOG_ERROR);
279                 debug_pop();
280                 return false;
281             }
282             $GLOBALS['midcom_helper_replicator_logger']->log_object($parameter, 'Exported');
283             $serializations[$parameter->guid] = $parameter_serialized;
284             unset($parameter_serialized);
285         }
286         unset($parameters, $parameter, $qb);
287
288         debug_pop();
289         return $serializations;
290     }
291
292     /**
293      * Serialize an object
294      *
295      * This will also serialize the attachments and parameters of the object.
296      *
297      * @param midgard_object &$object The Object to export parameters of
298      * @return array Array of exported objects as XML indexed by GUID
299      */
300     function serialize_object(&$object, $skip_children = false)
301     {
302         debug_push_class(__CLASS__, __FUNCTION__);
303         $class = get_class($object);
304         debug_add("called for {$class} {$object->guid}");
305         $serializations = array();
306         $GLOBALS['midcom_helper_replicator_logger']->push_prefix('exporter');
307
308         // Special case of midgard_attachment (and classes extending it)
309         if (is_a($object, 'midgard_attachment'))
310         {
311             debug_add('object is attachment, passing control to serialize_attachment()', MIDCOM_LOG_INFO);
312             debug_pop();
313             return $this->serialize_attachment($object);
314         }
315
316         if (   version_compare(mgd_version(), '1.8.2', '>=')
317             && !$skip_children)
318         {
319             // TODO: refactor to separate method(s)
320             // Reflect the object to handle any linked fields
321             $reflector = new midgard_reflection_property(get_class($object));
322             foreach (get_object_vars($object) as $property => $value)
323             {
324                 if (empty($value))
325                 {
326                     // We don't need to deal with empty values
327                     continue;
328                 }
329                 if ($reflector->is_link($property))
330                 {
331                     $linked_class = $reflector->get_link_name($property);
332
333                     if (!array_key_exists($linked_class, $this->handled_dependencies))
334                     {
335                         $this->handled_dependencies[$linked_class] = array();
336                     }
337
338                     if (array_key_exists($value, $this->handled_dependencies[$linked_class]))
339                     {
340                         // Skip this property, we've already exported this dependency
341                         continue;
342                     }
343
344                     // TODO: use midcom dbfactory reflection to get the correct classname right away to avoid the convert below
345                     $linked_object = new $linked_class($value);
346                     if (   is_object($linked_object)
347                         && $linked_object->guid)
348                     {
349                         $linked_dba_object = $_MIDCOM->dbfactory->convert_midgard_to_midcom($linked_object);
350                         if (   is_object($linked_dba_object)
351                             && isset($linked_dba_object->guid)
352                             && $linked_dba_object->guid)
353                         {
354                             $linked_serialization = $this->serialize_object($linked_dba_object, true);
355                             if ($linked_serialization === false)
356                             {
357                                 continue;
358                             }
359                             $serializations = array_merge($serializations, $linked_serialization);
360                             unset($linked_serialization);
361                             $this->handled_dependencies[$linked_class][$value] = true;
362                         }
363                     }
364                 }
365             }
366         }
367
368         // Export object itself
369         $object_serialized = midcom_helper_replicator_serialize($object);
370         if ($object_serialized === false)
371         {
372              $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
373             debug_add("midcom_helper_replicator_serialize returned false for object {$object->guid}, errstr: " . mgd_errstr(), MIDCOM_LOG_ERROR);
374             debug_pop();
375             return false;
376         }
377         if (   isset($this->_serialize_rewrite_to_delete[$object->guid])
378             && $this->_serialize_rewrite_to_delete[$object->guid])
379         {
380             // TODO: Make less simplistic (though this should be accurate enough)
381             // NOTE: If serialization format changes (the slightest bit) this must be changed as well
382             $object_serialized = str_replace("\n      <deleted>0</deleted>\n", "\n      <deleted>1</deleted>\n", $object_serialized);
383             $object_serialized = str_replace(array(' action="updated" ', ' action="created" '), array(' action="deleted" ', ' action="deleted" '), $object_serialized);
384         }
385         $serializations[$object->guid] = $object_serialized;
386         $GLOBALS['midcom_helper_replicator_logger']->log_object($object, 'Exported');
387         unset($object_serialized);
388
389         if (!$skip_children)
390         {
391             // Then object's parameters
392             $object_parameters = $this->serialize_parameters($object);
393             if ($object_parameters === false)
394             {
395                  $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
396                 debug_add("this->serialize_parameters returned false for object {$object->guid}", MIDCOM_LOG_ERROR);
397                 debug_pop();
398                 return false;
399             }
400             $serializations = array_merge($serializations, $object_parameters);
401             unset($object_parameters);
402
403             // And lastly object's attachments
404             $object_attachments = $this->serialize_attachments($object);
405             if ($object_attachments === false)
406             {
407                 $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
408                 debug_add("this->serialize_attachments returned false for object {$object->guid}", MIDCOM_LOG_ERROR);
409                 debug_pop();
410                 return false;
411             }
412             $serializations = array_merge($serializations, $object_attachments);
413             unset($object_attachments);
414         }
415         // Always serialize object's privileges
416         $object_privileges = $this->serialize_privileges($object);
417         if ($object_privileges === false)
418         {
419             $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
420             debug_add("this->serialize_privileges returned false for object {$object->guid}", MIDCOM_LOG_ERROR);
421             debug_pop();
422             return false;
423         }
424         $serializations = array_merge($serializations, $object_privileges);
425         unset($object_privileges);
426
427         $this->process_filters($serializations);
428         $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
429         debug_pop();
430         return $serializations;
431     }
432
433     /**
434      * This is the checkpoint of the exporter. This should be overridden in subclasses for more
435      * contextual handling of dependencies.
436      *
437      * @param midgard_object &$object The Object to export parameters of
438      * @return boolean Whether the object may be exported with this exporter
439      */
440     function is_exportable(&$object)
441     {
442         if (isset($this->exportability[$object->guid]))
443         {
444             // expotability is set, return early
445             return $this->exportability[$object->guid];
446         }
447
448         // Basic export checks (SG limits, limited objects)
449         return $this->is_exportable_baseline($object);
450     }
451
452     function is_exportable_baseline(&$object)
453     {
454         switch (true)
455         {
456             // Never replicate login sessions
457             case (is_a($object, 'midcom_core_login_session_db')):
458             // Never replicate across SG borders
459             case ($object->sitegroup != $this->subscription->sitegroup):
460                 $this->exportability[$object->guid] = false;
461                 break;
462             // Replicate everything else by default
463             default:
464                 $this->exportability[$object->guid] = true;
465                 break;
466         }
467
468         return $this->exportability[$object->guid];
469     }
470
471     /**
472      * This is the main entry point of the exporter. This should be overridden in subclasses for more
473      * contextual handling of dependencies.
474      *
475      * @param midgard_object &$object The Object to export parameters of
476      * @return array Array of exported objects as XML indexed by GUID
477      */
478     function serialize(&$object)
479     {
480         if (!$this->is_exportable($object))
481         {
482             $GLOBALS['midcom_helper_replicator_logger']->push_prefix('exporter');
483             $GLOBALS['midcom_helper_replicator_logger']->log_object($object, 'Is not exportable');
484             $GLOBALS['midcom_helper_replicator_logger']->pop_prefix();
485             return array();
486         }
487
488         $serializations = $this->serialize_object($object);
489         if ($serializations === false)
490         {
491             // TODO: Error reporting
492             return array();
493         }
494
495         return $serializations;
496     }
497
498     function filter_preg_replace(&$serializations, &$pluginargs)
499     {
500         $useargs = $this->_filter_xxx_replace_parseargs($pluginargs);
501         $serializations = preg_replace($useargs['search'], $useargs['replace'], $serializations);
502     }
503
504     function filter_str_replace(&$serializations, &$pluginargs)
505     {
506         $useargs = $this->_filter_xxx_replace_parseargs($pluginargs);
507         $serializations = str_replace($useargs['search'], $useargs['replace'], $serializations);
508     }
509
510     function _filter_xxx_replace_parseargs(&$pluginargs)
511     {
512         debug_push_class(__CLASS__, __FUNCTION__);
513         $ret['search'] = array();
514         $ret['replace'] = array();
515         if (!is_array($pluginargs))
516         {
517             // In fact this should never happen since the spec so
518             debug_add("Subscription #{$this->subscription->id} ({$this->subscription->title}) xxx_replace callback options is not array", MIDCOM_LOG_ERROR);
519             debug_pop();
520             return $ret;