| // +---------------------------------------------------------------------+ // // $Id$ require_once 'Config.php'; /** * Interface for Config containers * * @author Bertrand Mansion * @package Config */ class Config_Container { /** * Container object type * Ex: section, directive, comment, blank * @var string */ var $type; /** * Container object name * @var string */ var $name = ''; /** * Container object content * @var string */ var $content = ''; /** * Container object children * @var array */ var $children = array(); /** * Reference to container object's parent * @var object */ var $parent; /** * Array of attributes for this item * @var array */ var $attributes; /** * Unique id to differenciate nodes * * This is used to compare nodes * Will not be needed anymore when this class will use ZendEngine 2 * * @var int */ var $_id; /** * Constructor * * @param string $type Type of container object * @param string $name Name of container object * @param string $content Content of container object * @param array $attributes Array of attributes for container object */ function Config_Container($type = 'section', $name = '', $content = '', $attributes = null) { $this->type = $type; $this->name = $name; $this->content = $content; $this->attributes = $attributes; $this->parent = null; if (version_compare(PHP_VERSION, '5.0.0', 'gt')) { $this->_id = uniqid($name.$type, true); } else { $this->_id = uniqid(substr($name.$type, 0, 114), true); } } // end constructor /** * Create a child for this item. * @param string $type type of item: directive, section, comment, blank... * @param mixed $item item name * @param string $content item content * @param array $attributes item attributes * @param string $where choose a position 'bottom', 'top', 'after', 'before' * @param object $target needed if you choose 'before' or 'after' for where * @return object reference to new item or Pear_Error */ function &createItem($type, $name, $content, $attributes = null, $where = 'bottom', $target = null) { $item =& new Config_Container($type, $name, $content, $attributes); $result =& $this->addItem($item, $where, $target); return $result; } // end func &createItem /** * Adds an item to this item. * @param object $item a container object * @param string $where choose a position 'bottom', 'top', 'after', 'before' * @param object $target needed if you choose 'before' or 'after' in $where * @return mixed reference to added container on success, Pear_Error on error */ function &addItem(&$item, $where = 'bottom', $target = null) { if ($this->type != 'section') { return PEAR::raiseError('Config_Container::addItem must be called on a section type object.', null, PEAR_ERROR_RETURN); } if (is_null($target)) { $target =& $this; } if (strtolower(get_class($target)) != 'config_container') { return PEAR::raiseError('Target must be a Config_Container object in Config_Container::addItem.', null, PEAR_ERROR_RETURN); } switch ($where) { case 'before': $index = $target->getItemIndex(); break; case 'after': $index = $target->getItemIndex()+1; break; case 'top': $index = 0; break; case 'bottom': $index = -1; break; default: return PEAR::raiseError('Use only top, bottom, before or after in Config_Container::addItem.', null, PEAR_ERROR_RETURN); } if (isset($index) && $index >= 0) { array_splice($this->children, $index, 0, 'tmp'); } else { $index = count($this->children); } $this->children[$index] =& $item; $this->children[$index]->parent =& $this; return $item; } // end func addItem /** * Adds a comment to this item. * This is a helper method that calls createItem * * @param string $content Object content * @param string $where Position : 'top', 'bottom', 'before', 'after' * @param object $target Needed when $where is 'before' or 'after' * @return object reference to new item or Pear_Error */ function &createComment($content = '', $where = 'bottom', $target = null) { return $this->createItem('comment', null, $content, null, $where, $target); } // end func &createComment /** * Adds a blank line to this item. * This is a helper method that calls createItem * * @return object reference to new item or Pear_Error */ function &createBlank($where = 'bottom', $target = null) { return $this->createItem('blank', null, null, null, $where, $target); } // end func &createBlank /** * Adds a directive to this item. * This is a helper method that calls createItem * * @param string $name Name of new directive * @param string $content Content of new directive * @param mixed $attributes Directive attributes * @param string $where Position : 'top', 'bottom', 'before', 'after' * @param object $target Needed when $where is 'before' or 'after' * @return object reference to new item or Pear_Error */ function &createDirective($name, $content, $attributes = null, $where = 'bottom', $target = null) { return $this->createItem('directive', $name, $content, $attributes, $where, $target); } // end func &createDirective /** * Adds a section to this item. * * This is a helper method that calls createItem * If the section already exists, it won't create a new one. * It will return reference to existing item. * * @param string $name Name of new section * @param array $attributes Section attributes * @param string $where Position : 'top', 'bottom', 'before', 'after' * @param object $target Needed when $where is 'before' or 'after' * @return object reference to new item or Pear_Error */ function &createSection($name, $attributes = null, $where = 'bottom', $target = null) { return $this->createItem('section', $name, null, $attributes, $where, $target); } // end func &createSection /** * Tries to find the specified item(s) and returns the objects. * * Examples: * $directives =& $obj->getItem('directive'); * $directive_bar_4 =& $obj->getItem('directive', 'bar', null, 4); * $section_foo =& $obj->getItem('section', 'foo'); * * This method can only be called on an object of type 'section'. * Note that root is a section. * This method is not recursive and tries to keep the current structure. * For a deeper search, use searchPath() * * @param string $type Type of item: directive, section, comment, blank... * @param mixed $name Item name * @param mixed $content Find item with this content * @param array $attributes Find item with attribute set to the given value * @param int $index Index of the item in the returned object list. If it is not set, will try to return the last item with this name. * @return mixed reference to item found or false when not found * @see &searchPath() */ function &getItem($type = null, $name = null, $content = null, $attributes = null, $index = -1) { if ($this->type != 'section') { return PEAR::raiseError('Config_Container::getItem must be called on a section type object.', null, PEAR_ERROR_RETURN); } if (!is_null($type)) { $testFields[] = 'type'; } if (!is_null($name)) { $testFields[] = 'name'; } if (!is_null($content)) { $testFields[] = 'content'; } if (!is_null($attributes) && is_array($attributes)) { $testFields[] = 'attributes'; } $itemsArr = array(); $fieldsToMatch = count($testFields); for ($i = 0, $count = count($this->children); $i < $count; $i++) { $match = 0; reset($testFields); foreach ($testFields as $field) { if ($field != 'attributes') { if ($this->children[$i]->$field == ${$field}) { $match++; } } else { // Look for attributes in array $attrToMatch = count($attributes); $attrMatch = 0; foreach ($attributes as $key => $value) { if (isset($this->children[$i]->attributes[$key]) && $this->children[$i]->attributes[$key] == $value) { $attrMatch++; } } if ($attrMatch == $attrToMatch) { $match++; } } } if ($match == $fieldsToMatch) { $itemsArr[] =& $this->children[$i]; } } if ($index >= 0) { if (isset($itemsArr[$index])) { return $itemsArr[$index]; } else { $return = false; return $return; } } else { if ($count = count($itemsArr)) { return $itemsArr[$count-1]; } else { $return = false; return $return; } } } // end func &getItem /** * Finds a node using XPATH like format. * * The search format is an array: * array(item1, item2, item3, ...) * * Each item can be defined as the following: * item = 'string' : will match the container named 'string' * item = array('string', array('name' => 'xyz')) * will match the container name 'string' whose attribute name is equal to "xyz" * For example : * * @param mixed Search path and attributes * * @return mixed Config_Container object, array of Config_Container objects or false on failure. * @access public */ function &searchPath($args) { if ($this->type != 'section') { return PEAR::raiseError('Config_Container::searchPath must be called on a section type object.', null, PEAR_ERROR_RETURN); } $arg = array_shift($args); if (is_array($arg)) { $name = $arg[0]; $attributes = $arg[1]; } else { $name = $arg; $attributes = null; } // find all the matches for first.. $match =& $this->getItem(null, $name, null, $attributes); if (!$match) { $return = false; return $return; } if (!empty($args)) { return $match->searchPath($args); } return $match; } // end func &searchPath /** * Return a child directive's content. * * This method can use two different search approach, depending on * the parameter it is given. If the parameter is an array, it will use * the {@link Config_Container::searchPath()} method. If it is a string, * it will use the {@link Config_Container::getItem()} method. * * Example: * * require_once 'Config.php'; * $ini = new Config(); * $conf =& $ini->parseConfig('/path/to/config.ini', 'inicommented'); * * // Will return the value found at : * // [Database] * // host=localhost * echo $conf->directiveContent(array('Database', 'host'))); * * // Will return the value found at : * // date="dec-2004" * echo $conf->directiveContent('date'); * * * * @param mixed Search path and attributes or a directive name * @param int Index of the item in the returned directive list. * Eventually used if args is a string. * * @return mixed Content of directive or false if not found. * @access public */ function directiveContent($args, $index = -1) { if (is_array($args)) { $item =& $this->searchPath($args); } else { $item =& $this->getItem('directive', $args, null, null, $index); } if ($item) { return $item->getContent(); } return false; } // end func getDirectiveContent /** * Returns how many children this container has * * @param string $type type of children counted * @param string $name name of children counted * @return int number of children found */ function countChildren($type = null, $name = null) { if (is_null($type) && is_null($name)) { return count($this->children); } $count = 0; if (isset($name) && isset($type)) { for ($i = 0, $children = count($this->children); $i < $children; $i++) { if ($this->children[$i]->name === $name && $this->children[$i]->type == $type) { $count++; } } return $count; } if (isset($type)) { for ($i = 0, $children = count($this->children); $i < $children; $i++) { if ($this->children[$i]->type == $type) { $count++; } } return $count; } if (isset($name)) { // Some directives can have the same name for ($i = 0, $children = count($this->children); $i < $children; $i++) { if ($this->children[$i]->name === $name) { $count++; } } return $count; } } // end func &countChildren /** * Deletes an item (section, directive, comment...) from the current object * TODO: recursive remove in sub-sections * @return mixed true if object was removed, false if not, or PEAR_Error if root */ function removeItem() { if ($this->isRoot()) { return PEAR::raiseError('Cannot remove root item in Config_Container::removeItem.', null, PEAR_ERROR_RETURN); } $index = $this->getItemIndex(); if (!is_null($index)) { array_splice($this->parent->children, $index, 1); return true; } return false; } // end func removeItem /** * Returns the item index in its parent children array. * @return int returns int or null if root object */ function getItemIndex() { if (is_object($this->parent)) { // This will be optimized with Zend Engine 2 $pchildren =& $this->parent->children; for ($i = 0, $count = count($pchildren); $i < $count; $i++) { if ($pchildren[$i]->_id == $this->_id) { return $i; } } } return; } // end func getItemIndex /** * Returns the item rank in its parent children array * according to other items with same type and name. * @param bool count items differently by type * @return int returns int or null if root object */ function getItemPosition($byType = true) { if (is_object($this->parent)) { $pchildren =& $this->parent->children; for ($i = 0, $count = count($pchildren); $i < $count; $i++) { if ($pchildren[$i]->name == $this->name) { if ($byType == true) { if ($pchildren[$i]->type == $this->type) { $obj[] =& $pchildren[$i]; } } else { $obj[] =& $pchildren[$i]; } } } for ($i = 0, $count = count($obj); $i < $count; $i++) { if ($obj[$i]->_id == $this->_id) { return $i; } } } return; } // end func getItemPosition /** * Returns the item parent object. * @return object returns reference to parent object or null if root object */ function &getParent() { return $this->parent; } // end func &getParent /** * Returns the item parent object. * @return mixed returns reference to child object or false if child does not exist */ function &getChild($index = 0) { if (!empty($this->children[$index])) { return $this->children[$index]; } else { return false; } } // end func &getChild /** * Set this item's name. * @return void */ function setName($name) { $this->name = $name; } // end func setName /** * Get this item's name. * @return string item's name */ function getName() { return $this->name; } // end func getName /** * Set this item's content. * @return void */ function setContent($content) { $this->content = $content; } // end func setContent /** * Get this item's content. * @return string item's content */ function getContent() { return $this->content; } // end func getContent /** * Set this item's type. * @return void */ function setType($type) { $this->type = $type; } // end func setType /** * Get this item's type. * @return string item's type */ function getType() { return $this->type; } // end func getType /** * Set this item's attributes. * @param array $attributes Array of attributes * @return void */ function setAttributes($attributes) { $this->attributes = $attributes; } // end func setAttributes /** * Set this item's attributes. * @param array $attributes Array of attributes * @return void */ function updateAttributes($attributes) { if (is_array($attributes)) { foreach ($attributes as $key => $value) { $this->attributes[$key] = $value; } } } // end func updateAttributes /** * Get this item's attributes. * @return array item's attributes */ function getAttributes() { return $this->attributes; } // end func getAttributes /** * Get one attribute value of this item * @param string $attribute Attribute key * @return mixed item's attribute value */ function getAttribute($attribute) { if (isset($this->attributes[$attribute])) { return $this->attributes[$attribute]; } return null; } // end func getAttribute /** * Set a children directive content. * This is an helper method calling getItem and addItem or setContent for you. * If the directive does not exist, it will be created at the bottom. * * @param string $name Name of the directive to look for * @param mixed $content New content * @param int $index Index of the directive to set, * in case there are more than one directive * with the same name * @return object newly set directive */ function &setDirective($name, $content, $index = -1) { $item =& $this->getItem('directive', $name, null, null, $index); if ($item === false || PEAR::isError($item)) { // Directive does not exist, will create one unset($item); return $this->createDirective($name, $content, null); } else { // Change existing directive value $item->setContent($content); return $item; } } // end func setDirective /** * Is this item root, in a config container object * @return bool true if item is root */ function isRoot() { if (is_null($this->parent)) { return true; } return false; } // end func isRoot /** * Call the toString methods in the container plugin * @param string $configType Type of configuration used to generate the string * @param array $options Specify special options used by the parser * @return mixed true on success or PEAR_ERROR */ function toString($configType, $options = array()) { $configType = strtolower($configType); if (!isset($GLOBALS['CONFIG_TYPES'][$configType])) { return PEAR::raiseError("Configuration type '$configType' is not registered in Config_Container::toString.", null, PEAR_ERROR_RETURN); } $includeFile = $GLOBALS['CONFIG_TYPES'][$configType][0]; $className = $GLOBALS['CONFIG_TYPES'][$configType][1]; include_once($includeFile); $renderer = new $className($options); return $renderer->toString($this); } // end func toString /** * Returns a key/value pair array of the container and its children. * * Format : section[directive][index] = value * If the container has attributes, it will use '@' and '#' * index is here because multiple directives can have the same name. * * @param bool $useAttr Whether to return the attributes too * @return array */ function toArray($useAttr = true) { $array[$this->name] = array(); switch ($this->type) { case 'directive': if ($useAttr && count($this->attributes) > 0) { $array[$this->name]['#'] = $this->content; $array[$this->name]['@'] = $this->attributes; } else { $array[$this->name] = $this->content; } break; case 'section': if ($useAttr && count($this->attributes) > 0) { $array[$this->name]['@'] = $this->attributes; } if ($count = count($this->children)) { for ($i = 0; $i < $count; $i++) { $newArr = $this->children[$i]->toArray($useAttr); if (!is_null($newArr)) { foreach ($newArr as $key => $value) { if (isset($array[$this->name][$key])) { // duplicate name/type if (!is_array($array[$this->name][$key]) || !isset($array[$this->name][$key][0])) { $old = $array[$this->name][$key]; unset($array[$this->name][$key]); $array[$this->name][$key][0] = $old; } $array[$this->name][$key][] = $value; } else { $array[$this->name][$key] = $value; } } } } } break; default: return null; } return $array; } // end func toArray /** * Writes the configuration to a file * * @param mixed $datasrc Info on datasource such as path to the configuraton file or dsn... * @param string $configType Type of configuration * @param array $options Options for writer * @access public * @return mixed true on success or PEAR_ERROR */ function writeDatasrc($datasrc, $configType, $options = array()) { $configType = strtolower($configType); if (!isset($GLOBALS['CONFIG_TYPES'][$configType])) { return PEAR::raiseError("Configuration type '$configType' is not registered in Config_Container::writeDatasrc.", null, PEAR_ERROR_RETURN); } $includeFile = $GLOBALS['CONFIG_TYPES'][$configType][0]; $className = $GLOBALS['CONFIG_TYPES'][$configType][1]; include_once($includeFile); $writeMethodName = (version_compare(phpversion(), '5', '<')) ? 'writedatasrc' : 'writeDatasrc'; if (in_array($writeMethodName, get_class_methods($className))) { $writer = new $className($options); return $writer->writeDatasrc($datasrc, $this); } // Default behaviour $fp = @fopen($datasrc, 'w'); if ($fp) { $string = $this->toString($configType, $options); $len = strlen($string); @flock($fp, LOCK_EX); @fwrite($fp, $string, $len); @flock($fp, LOCK_UN); @fclose($fp); return true; } else { return PEAR::raiseError('Cannot open datasource for writing.', 1, PEAR_ERROR_RETURN); } } // end func writeDatasrc } // end class Config_Container ?>