/** * Copy an object * * @param mixed &$source MgdSchema object for reading the parameters * @param mixed &$parent MgdSchema parent object * @param array $defaults * @return boolean Indicating success */ public function copy_object(&$source, &$parent = null, $defaults = array()) { // Resolve the source object self::resolve_object($source); // Duplicate the object $class_name = get_class($source); $target = new $class_name(); $properties = $this->get_object_properties($source); // Copy the object properties foreach ($properties as $property) { // Skip certain fields if (preg_match('/^(_|metadata|guid|id)/', $property)) { continue; } $target->{$property} = $source->{$property}; } // Override requested root object properties if (isset($this->target->guid) && $target->guid === $this->target->guid) { foreach ($this->root_object_values as $name => $value) { $target->{$name} = $value; } } // Override with defaults if ($defaults) { foreach ($defaults as $name => $value) { $target->{$name} = $value; } } $parent_property = $this->get_parent_property($source); // Copy the link to parent if ($parent) { self::resolve_object($parent); if (!$parent || !$parent->guid) { return false; } // @TODO: Is there a sure way to determine if the parent is // GUID or is it ID? If so, please change it here. if (is_string($source->{$parent_property})) { $parent_key = 'guid'; } else { $parent_key = 'id'; } $target->{$parent_property} = $parent->{$parent_key}; } else { if (is_string($source->{$parent_property})) { $target->{$parent_property} = ''; } else { $target->{$parent_property} = 0; } } $name_property = midcom_helper_reflector::get_name_property($target); $resolver = new midcom_helper_reflector_nameresolver($target); if (!empty($name_property) && !$resolver->name_is_safe_or_empty($name_property)) { debug_add('Source object ' . get_class($source) . " {$source->guid} has unsafe name, rewriting to safe form for the target", MIDCOM_LOG_WARN); $name_property = midcom_helper_reflector::get_name_property($target); if (empty($name_property)) { $this->errors[] = sprintf($this->_l10n->get('cannot fix unsafe name for source object %s, skipping'), get_class($source) . " {$source->guid}"); return false; } $name_parts = explode('.', $target->{$name_property}, 2); if (isset($name_parts[1])) { $target->{$name_property} = midcom_helper_misc::generate_urlname_from_string($name_parts[0]) . ".{$name_parts[1]}"; // Doublecheck safety and fall back if needed if (!$resolver->name_is_safe_or_empty()) { $target->{$name_property} = midcom_helper_misc::generate_urlname_from_string($target->{$name_property}); } } else { $target->{$name_property} = midcom_helper_misc::generate_urlname_from_string($target->{$name_property}); } unset($name_parts, $name_property); } if ($this->allow_name_catenate && $name_property) { $name = $resolver->generate_unique_name(); if ($name !== $target->{$name_property}) { $target->{$name_property} = $name; } } // This needs to be here, otherwise it will be overridden $target->allow_name_catenate = true; if (!$target->create()) { $this->errors[] = $this->_l10n->get('failed to create object: ' . mgd_errstr()); return false; } // Store for later use - if ever needed $this->new_objects[] = $target; unset($name_property); // Copy parameters if (!$this->copy_parameters($source, $target) && $this->halt_on_errors) { $this->errors[] = $this->_l10n->get('failed to copy parameters'); return false; } // Copy metadata if (!$this->copy_metadata($source, $target) && $this->halt_on_errors) { $this->errors[] = $this->_l10n->get('failed to copy metadata'); return false; } // Copy attachments if (!$this->copy_attachments($source, $target) && $this->halt_on_errors) { $this->errors[] = $this->_l10n->get('failed to copy attachments'); return false; } // Copy privileges if (!$this->copy_privileges($source, $target) && $this->halt_on_errors) { $this->errors[] = $this->_l10n->get('failed to copy privileges'); return false; } return $target; }
/** * @dataProvider providerGet_name_property */ public function testGet_name_property($classname, $property) { $object = new $classname(); $reflector = new midcom_helper_reflector($classname); $this->assertEquals($property, $reflector->get_name_property($object)); }
/** * Helper method to call in the _xxx_pre_checks, handles the API * level checks and automatic operations as specified in ticket #809 * * @see http://trac.midgard-project.org/ticket/809 * Quoting the ticket API-level section: * <pre> * 1. Checks will be done in the pre-flight check phase (ie just after _on_creating/_on_updating) * 2. If name is not unique false is returned for pre-flight check, preventing create/update * 2.2 UNLESS a property in the object ('allow_name_catenate') is set to true in which case unique one is generated by catenating an incrementing number to the name. * 3. if name is empty unique name is generated from title property (unless title is empty too) * 4. if name is not URL-safe false is returned * </pre> * * @param midcom_core_dbaobject $object The DBA object we're working on * @return boolean indicating whether from our point of view everything is ok * * @see midcom_helper_reflector_nameresolver::name_is_safe_or_empty() * @see midcom_helper_reflector_nameresolver::name_is_unique_or_empty() * @see midcom_helper_reflector_nameresolver::generate_unique_name() */ private static function _pre_check_name(midcom_core_dbaobject $object) { // Make sure name is empty of unique if the object has such property $name_property = midcom_helper_reflector::get_name_property($object); if (empty($name_property)) { // This object has no name property, return early return true; } $resolver = new midcom_helper_reflector_nameresolver($object); /** * If name is empty, try to generate new, unique one * * @see http://trac.midgard-project.org/ticket/809 */ if (empty($object->{$name_property})) { // name is empty, try to generate $new_name = $resolver->generate_unique_name(); if (!empty($new_name)) { $object->{$name_property} = $new_name; } unset($new_name); } /** * Enforce URL-safe (or empty) names * * @see http://trac.midgard-project.org/ticket/809 */ if (!$resolver->name_is_safe_or_empty()) { midcom_connection::set_error(MGD_ERR_INVALID_NAME); return false; } /** * Enforce unique (or empty) names * * @see http://trac.midgard-project.org/ticket/809 */ if (!$resolver->name_is_unique_or_empty()) { if ($object->allow_name_catenate) { // Transparent catenation allowed, let's try again. $new_name = $resolver->generate_unique_name(); if (!empty($new_name)) { $object->{$name_property} = $new_name; return true; } else { debug_add('allow_name_catenate was set but midcom_helper_reflector_nameresolver::generate_unique_name() returned empty value, falling through', MIDCOM_LOG_WARN); } } midcom_connection::set_error(MGD_ERR_OBJECT_NAME_EXISTS); return false; } // All checks ok, we're fine. return true; }
private function _add_linked_field($key) { $linked_type = $this->_reflector->get_link_name($key); $linked_type_reflector = midcom_helper_reflector::get($linked_type); $field_type = $this->_reflector->get_midgard_type($key); if ($key == 'up') { $field_label = sprintf($this->_l10n->get('under %s'), midgard_admin_asgard_plugin::get_type_label($linked_type)); } else { $type_label = midgard_admin_asgard_plugin::get_type_label($linked_type); if (substr($type_label, 0, strlen($key)) == $key) { // Handle abbreviations like "lang" for "language" $field_label = $type_label; } else { if ($key == $type_label) { $field_label = $key; } else { $ref = midcom_helper_reflector::get($this->_object); $component_l10n = $ref->get_component_l10n(); $field_label = sprintf($this->_l10n->get('%s (%s)'), $component_l10n->get($key), $type_label); } } } // Get the chooser widgets switch ($field_type) { case MGD_TYPE_UINT: case MGD_TYPE_STRING: case MGD_TYPE_GUID: $class = midcom::get('dbclassloader')->get_midcom_class_name_for_mgdschema_object($linked_type); if (!$class) { break; } $component = midcom::get('dbclassloader')->get_component_for_class($linked_type); $this->_schemadb['object']->append_field($key, array('title' => $field_label, 'storage' => $key, 'type' => 'select', 'type_config' => array('require_corresponding_option' => false, 'options' => array(), 'allow_other' => true, 'allow_multiple' => false), 'widget' => 'chooser', 'widget_config' => array('class' => $class, 'component' => $component, 'titlefield' => $linked_type_reflector->get_label_property(), 'id_field' => $this->_reflector->get_link_target($key), 'searchfields' => $linked_type_reflector->get_search_properties(), 'result_headers' => $this->_get_result_headers($linked_type_reflector), 'orders' => array(), 'creation_mode_enabled' => true, 'creation_handler' => midcom_connection::get_url('self') . "__mfa/asgard/object/create/chooser/{$linked_type}/", 'creation_default_key' => $linked_type_reflector->get_label_property(), 'generate_path_for' => midcom_helper_reflector::get_name_property($this->_object)))); break; } }
<?php /** * @package midcom.helper.reflector * @author The Midgard Project, http://www.midgard-project.org * @copyright The Midgard Project, http://www.midgard-project.org * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License */ // FIXME: make generic $article = new midcom_db_article(); $article->topic = 5; $article->title = 'Duplicate name test with (with allow_catenate)'; $article->name = 'gathering-09'; $article->allow_name_catenate = true; $reflected_name_property = midcom_helper_reflector::get_name_property($article); $resolver = new midcom_helper_reflector_nameresolver($article); $reflected_name = $resolver->get_object_name(); echo "Reflector thinks name is '{$reflected_name}' (from property '{$reflected_name_property}')<br>\n"; if ($resolver->name_is_safe()) { echo "OK: '{$reflected_name}' is considered URL-safe<br>\n"; } else { echo "ERROR: '{$reflected_name}' is NOT considered URL-safe<br>\n"; } if ($resolver->name_is_clean()) { echo "OK: '{$reflected_name}' is considered 'clean'<br>\n"; } else { echo "WARN: '{$reflected_name}' is NOT considered 'clean'<br>\n"; } if ($resolver->name_is_unique()) { echo "OK: '{$reflected_name}' is unique (among siblings)<br>\n"; } else {
/** * Helper to resolve the base value for the incrementing suffix and for the name. * * @see midcom_helper_reflector_nameresolver::generate_unique_name() * @param string $current_name the "current name" of the object (might not be the actual name value see the title logic in generate_unique_name()) * @param string $extension The file extension, when working with attachments * @return array first key is the resolved $i second is the $base_name, which is $current_name without numeric suffix */ private function _generate_unique_name_resolve_i($current_name, $extension) { if (preg_match('/(.*?)-([0-9]{3,})' . $extension . '$/', $current_name, $name_matches)) { // Name already has i and base parts, split them. $i = (int) $name_matches[2]; $base_name = (string) $name_matches[1]; unset($name_matches); } else { // Defaults $i = 1; $base_name = $current_name; } // Look for siblings with similar names and see if they have higher i. midcom::get('auth')->request_sudo('midcom.helper.reflector'); $parent = midcom_helper_reflector_tree::get_parent($this->_object); // TODO: Refactor to reduce duplicate code with _name_is_unique_check_siblings if ($parent && !empty($parent->guid)) { // We have parent, check siblings $parent_resolver = new midcom_helper_reflector_tree($parent); $sibling_classes = $parent_resolver->get_child_classes(); if (!in_array('midgard_attachment', $sibling_classes)) { $sibling_classes[] = 'midgard_attachment'; } foreach ($sibling_classes as $schema_type) { $dummy = new $schema_type(); $child_name_property = midcom_helper_reflector::get_name_property($dummy); unset($dummy); if (empty($child_name_property)) { // This sibling class does not use names continue; } $resolver = midcom_helper_reflector_tree::get($schema_type); $qb =& $resolver->_child_objects_type_qb($schema_type, $parent, false); if (!is_object($qb)) { continue; } $qb->add_constraint($child_name_property, 'LIKE', "{$base_name}-%" . $extension); // Do not include current object in results, this is the easiest way if (!empty($this->_object->guid)) { $qb->add_constraint('guid', '<>', $this->_object->guid); } $qb->add_order('name', 'DESC'); // One result should be enough $qb->set_limit(1); $siblings = $qb->execute(); if (empty($siblings)) { // we don't care about fatal qb errors here continue; } $sibling = $siblings[0]; $sibling_name = $sibling->{$child_name_property}; if (preg_match('/(.*?)-([0-9]{3,})' . $extension . '$/', $sibling_name, $name_matches)) { // Name already has i and base parts, split them. $sibling_i = (int) $name_matches[2]; if ($sibling_i >= $i) { $i = $sibling_i + 1; } unset($sibling_i, $name_matches); } } unset($parent, $parent_resolver, $sibling_classes, $schema_type, $child_name_property, $sibling, $sibling_name); } else { unset($parent); // No parent, we might be a root level class $is_root_class = false; $root_classes = midcom_helper_reflector_tree::get_root_classes(); foreach ($root_classes as $schema_type) { if (midcom::get('dbfactory')->is_a($this->_object, $schema_type)) { $is_root_class = true; } } if (!$is_root_class) { // This should not happen, logging error and returning true (even though it's potentially dangerous) midcom::get('auth')->drop_sudo(); debug_add("Object #{$this->_object->guid} has no valid parent but is not listed in the root classes, don't know what to do, letting higher level decide", MIDCOM_LOG_ERROR); unset($root_classes, $is_root_class); return array($i, $base_name); } else { // TODO: Refactor to reduce duplicate code with _name_is_unique_check_roots foreach ($root_classes as $schema_type) { $dummy = new $schema_type(); $child_name_property = midcom_helper_reflector::get_name_property($dummy); unset($dummy); if (empty($child_name_property)) { // This sibling class does not use names continue; } $resolver =& midcom_helper_reflector_tree::get($schema_type); $deleted = false; $qb =& $resolver->_root_objects_qb($deleted); if (!$qb) { continue; } unset($deleted); $qb->add_constraint($child_name_property, 'LIKE', "{$base_name}-%" . $extension); // Do not include current object in results, this is the easiest way if (!empty($this->_object->guid)) { $qb->add_constraint('guid', '<>', $this->_object->guid); } $qb->add_order($child_name_property, 'DESC'); // One result should be enough $qb->set_limit(1); $siblings = $qb->execute(); if (empty($siblings)) { // we dont' care about fatal qb errors here continue; } $sibling = $siblings[0]; $sibling_name = $sibling->{$child_name_property}; if (preg_match('/(.*?)-([0-9]{3,})' . $extension . '$/', $sibling_name, $name_matches)) { // Name already has i and base parts, split them. $sibling_i = (int) $name_matches[2]; if ($sibling_i >= $i) { $i = $sibling_i + 1; } unset($sibling_i, $name_matches); } } unset($root_classes, $schema_type, $child_name_property, $sibling, $sibling_name); } } midcom::get('auth')->drop_sudo(); return array($i, $base_name); }