Pastoid

vocabulary.php

The page you are looking at now is at this URL: http://pastoid.com/b0g

This paste was last updated on February 23, 2010 at 4:32 pm.

Pasted Coderaw

<?php
 
/**
 * Vocabulary Class
 *
 * Vocabulary is part of the taxonomy system. A vocabulary holds terms and has features.
 *
 */
 
class Vocabulary extends QueryRecord
{
	/**
	 *
	 * @var Array of strings $features. An array of the features that limit the behaviour of the vocabulary.
	 * Default values can include:
	 *		hierarchical:	The vocabulary's terms exist in a parent child hierarchy
	 *		required:
	 *		multiple:		More than one term in the vocabulary can be associated with an object
	 *		free:			Terms within the vocabulary can have any value
	 */
	public static $features = array('hierarchical', 'required', 'multiple', 'free');
 
	/**
	 * Return the defined database columns for a Vocabulary.
	 * @return array Array of columns in the Vocabulary table
	 **/
	public static function default_fields()
	{
		return array(
			'id' => 0,
			'name' => '',
			'description' => '',
			'features' => array(),
		);
	}
 
	/**
	 * Vocabulary constructor
	 * Creates a Vocabulary instance
	 *
	 * @param array $paramarray an associative array of initial vocabulary values
	 **/
	public function __construct( $paramarray = array() )
	{
		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields
		);
 
		parent::__construct( $paramarray );
 
		$this->exclude_fields( 'id' );
	}
 
	/**
	 * function __get
	 * Overrides QueryRecord __get to implement custom object properties
	 * @param string Name of property to return
	 * @return mixed The requested field value
	 **/
	public function __get( $name )
	{
		$out = parent::__get( $name );
		switch($name) {
			case 'features':
				if ( ! is_array( $out ) ) {
					$out = unserialize( $out );
				}
				break;
		}
		if ( is_null( $out ) ) {
			$out = in_array( $name, $this->features );
		}
		return $out;
	}
 
	/**
	 * Return a Vocabulary by name.
	 * @return Vocabulary The requested vocabulary
	 **/
	public static function get($name)
	{
		return DB::get_row( 'SELECT * FROM {vocabularies} WHERE name=?', array($name), 'Vocabulary' );
	}
 
	/**
	 * Return a Vocabulary by id
	 *
	 * @param integer $id The id of the vocabulary
	 * @return Vocabulary The object requested
	 */
	public static function get_by_id($id)
	{
		return DB::get_row( 'SELECT * FROM {vocabularies} WHERE id=?', array($id), 'Vocabulary' );
	}
 
	/**
	 * Return all vocabularies as Vocabulary objects
	 *
	 * @return array An array of Vocabulary objects
	 */
	public static function get_all()
	{
		return DB::get_results( 'SELECT * FROM {vocabularies}', array(), 'Vocabulary' );
	}
 
	/**
	 * Rename a Vocabulary.
	 * @return boolean true if the Vocabulary was renamed, false otherwise
	 **/
	public static function rename($name, $newname)
	{
		$vocab = Vocabulary::get($name);
		$vocab->name = $newname;
		$result = $vocab->update();
 
		return $result;
	}
 
	/**
	 * Return the names of all vocabularies
	 * @return array Array of Vocabulary names
	 **/
	public static function names()
	{
		$names = array();
		$vocabs = DB::get_results( 'SELECT name FROM {vocabularies}' );
		foreach ( $vocabs as $vocab ) {
			$names[] = $vocab->name;
		}
		return $names;
	}
 
	/**
	 * Return the Term objects associated to that type of object with that id in any vocabulary.
	 * For example, return all terms associated with a particular post, from all vocabularies.
	 *
	 * @return array Array of Vocabulary names
	 **/
	public static function get_all_object_terms($object_type, $id)
	{
		$results = DB::get_results(
			'SELECT id, term, term_display, vocabulary_id, mptt_left, mptt_right FROM {terms}
			JOIN {object_terms} ON {terms}.id = {object_terms}.term_id
			WHERE {object_terms}.object_type_id = ?
				AND {object_terms}.object_id = ?',
			array( self::object_type_id( $object_type ), $id ),
			'Term'
		);
 
		return $results;
	}
 
	/**
	 * Determine whether a vocabulary exists
	 * @param string $name a vocabulary name
	 * @return bool whether the vocabulary exists or not
	**/
	public static function exists( $name )
	{
		return ( (int) DB::get_value( "SELECT COUNT(id) FROM {vocabularies} WHERE name=?", array( $name ) ) > 0 );
	}
 
	/**
	 * function insert
	 * Saves a new vocabulary to the vocabularies table
	 */
	public function insert()
	{
		// Don't allow duplicate vocabularies
		if ( Vocabulary::exists( $this->fields['name'] ) ) {
			return false;
		}
 
		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'vocabulary_insert_allow', $allow, $this );
		if ( !$allow ) {
			return false;
		}
		Plugins::act( 'vocabulary_insert_before', $this );
 
		// Serialize features before they are stored
		if ( isset( $this->newfields['features'] ) ) {
			$this->newfields['features'] = serialize( $this->newfields['features'] );
		}
		if ( isset( $this->fields['features'] ) ) {
			$this->fields['features'] = serialize( $this->fields['features'] );
		}
		$result = parent::insertRecord( DB::table( 'vocabularies' ) );
 
		// Make sure the id is set in the vocabulary object to match the row id
		$this->newfields['id'] = DB::last_insert_id();
 
		// Update the vocabulary's fields with anything that changed
		$this->fields = array_merge( $this->fields, $this->newfields );
		// And unserialize the features
		if ( isset( $this->fields['features'] ) ) {
			$this->fields['features'] = unserialize( $this->fields['features'] );
		}
 
		// We've inserted the vocabulary, reset newfields
		$this->newfields = array();
 
		EventLog::log( sprintf( _t( 'New vocabulary %1$s (%2$s)' ), $this->id, $this->name ), 'info', 'content', 'habari' );
 
		// Let plugins act after we write to the database
		Plugins::act( 'vocabulary_insert_after', $this );
 
		return $result;
	}
 
	/**
	 * function update
	 * Updates an existing vocabulary in the vocabularies table
	 */
	public function update()
	{
		// Don't allow duplicate vocabularies
		if ( isset( $this->newfields['name'] ) && Vocabulary::exists( $this->newfields['name'] ) ) {
			return false;
		}
 
		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'vocabulary_update_allow', $allow, $this );
		if ( !$allow ) {
			return;
		}
		Plugins::act( 'vocabulary_update_before', $this );
 
		if ( isset( $this->newfields['features'] ) ) {
			$this->newfields['features'] = serialize( $this->newfields['features'] );
		}
		if ( isset( $this->fields['features'] ) ) {
			$this->fields['features'] = serialize( $this->fields['features'] );
		}
		$result = parent::updateRecord( '{vocabularies}', array( 'id' => $this->id ) );
 
		$this->fields = array_merge( $this->fields, $this->newfields );
		$this->newfields = array();
 
		if ( isset( $this->fields['features'] ) ) {
			$this->fields['features'] = unserialize( $this->fields['features'] );
		}
 
		// Let plugins act after we write to the database
		Plugins::act( 'vocabulary_update_after', $this );
 
		return $result;
	}
 
	/**
	 * Delete an existing vocabulary
	 */
	public function delete()
	{
		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'vocabulary_delete_allow', $allow, $this );
		if ( !$allow ) {
			return;
		}
		Plugins::act( 'vocabulary_delete_before', $this );
 
		// Get the ids for all this vocabulary's terms
		$ids = DB::get_column('SELECt id FROM {terms} WHERE vocabulary_id = ?', array( $this->id ) );
		// Delete the records from object_terms for those ids
		$placeholder = Utils::placeholder_string( count( $ids ) );
		DB::query("DELETE FROM {object_terms} WHERE term_id IN ($placeholder)", $ids );
 
		// Delete this vocabulary's terms
		DB::delete( '{terms}', array( 'vocabulary_id' => $this->id ) );
 
		// Finally, delete the vocabulary
		$result = parent::deleteRecord( '{vocabularies}', array( 'id'=>$this->id ) );
		EventLog::log( sprintf(_t('Vocabulary %1$s (%2$s) deleted.'), $this->id, $this->name), 'info', 'content', 'habari' );
 
		// Let plugins act after we write to the database
		Plugins::act( 'vocabulary_delete_after', $this );
		return $result;
	}
 
	/**
	 * Adds a term to the vocabulary. Returns a Term object. null parameters append the term to the end of any hierarchies.
	 * @return Term The Term object added
	 **/
	public function add_term($term, $parent_term = null, $before_term = null)
	{
		$new_term = $term;
		if ( is_string($term) ) {
			$new_term = new Term(array('term_display' => $term));
		}
 
		$new_term->vocabulary_id = $this->id;
 
		$ref = 0;
		DB::begin_transaction();
 
		// If there are terms in the vocabulary, work out the reference point
		if ( !$this->is_empty() ) {
 
			if ( $this->hierarchical ) {
				// If no parent is specified, put the new term after the last term
				if ( null == $parent_term ) {
					$ref = DB::get_value( 'SELECT mptt_right FROM {terms} WHERE vocabulary_id=? ORDER BY mptt_right DESC LIMIT 1', array($this->id) );
				}
				else {
					if ( null == $before_term ) {
						$ref = $parent_term->mptt_right - 1;
					}
					else {
						$ref = $before_term->mptt_left - 1;
					}
				}
			}
			else {
				// If no before_term is specified, put the new term after the last term
				if ( null == $before_term ) {
					$ref = DB::get_value( 'SELECT mptt_right FROM {terms} WHERE vocabulary_id=? ORDER BY mptt_right DESC LIMIT 1', array($this->id) );
				}
				else {
					$ref = $before_term->mptt_left - 1;
				}
			}
 
			// Make space for the new node
			$params = array($this->id, $ref);
			$res = DB::query('UPDATE {terms} SET mptt_right=mptt_right+2 WHERE vocabulary_id=? AND mptt_right>?', $params);
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
			$res = DB::query('UPDATE {terms} SET mptt_left=mptt_left+2 WHERE vocabulary_id=? AND mptt_left>?', $params);
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
 
		}
 
		// Set the right and left appropriately
		$new_term->mptt_left = $ref + 1;
		$new_term->mptt_right = $ref + 2;
 
		// Insert the new node
		$result = $new_term->insert();
		if ( $result ) {
			DB::commit(); 
			return $new_term;
		}
		else {
			DB::rollback();
			return FALSE;
		}
 
	}
 
	public function append_to( $term, $parent_term = null )
	{
		return move_term( $term, $parent_term ); // actually probably should have before_term in here too, or is that not an append?
	}
 
	/**
	 * Moves a term within the vocabulary. Returns a Term object. null parameters append the term to the end of any hierarchies.
	 * @return Term The Term object added
	 **/
	public function move_term( $term, $target_term = null, $before = FALSE )
	{
		// We assume that the arguments passed are valid terms. Check them before calling this.
 
		// If there are terms in the vocabulary, work out the reference point
		if ( !$this->is_empty() ) {
 
			$source_left = $term->mptt_left;
			$source_right = $term->mptt_right;
 
			$nodes_moving = ( $source_right - $source_left + 1 ) / 2; // parent and descendants
 
			// we also need the destination right.
 
			DB::begin_transaction();
 
			if ( $this->hierarchical ) {
				// Check first if this is to be inserted to the left of a term
				if ( FALSE != $before ) {
					$destination_right = $target_term->mptt_left - 1;
				}
				else {
					// If a target is specified, put the new term after the last child of it
					if ( null != $target_term ) {
						$params = array( $this->id, $source_left, $source_right );
 
						$parent_term = DB::get_results( 'SELECT * FROM {terms} WHERE vocabulary_id=? AND mptt_left < ? AND mptt_right > ? ORDER BY mptt_left DESC LIMIT 1', $params );
 
						$params = array( $this->id, $parent_term->mptt_right, $parent_term->mptt_left );
						$destination_right = DB::get_value( 'SELECT mptt_right FROM terms WHERE vocabulary_id=? AND mptt_right = ?-1 AND mptt_left > ?', $params );
					}
					else {
						// move_term( $term, null, TRUE ) shouldn't happen
					}
				}
			} 
			else {
				// vocabulary is not hierarchical
				if ( FALSE != $before ) {
					$destination_right = $target_term->mptt_left - 1;
				}
				else {
					// send it all the way to the right.
					$destination_right = DB::get_value( 'SELECT mptt_right FROM {terms} WHERE vocabulary_id=? ORDER BY mptt_right DESC LIMIT 1', array($this->id) );
// /* would this be faster? */ 		$destination_right = DB::get_value( 'SELECT MAX(mptt_right) FROM {terms} WHERE vocabulary_id=?', array($this->id) );
				}
			}
 
			// Move the existing nodes out of the way by making mptt_left and mptt_right negative
			// Cannot finish before redoing this to bring the terms positive again.
			$res = displace_term( $term, ( -1 - $source_right ) ); 
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
 
			// move the nodes between source and end of the vocabulary
			$params = array( $node_moving, $this->id, $source_left );
			$res = DB::query( 'UPDATE {terms} SET mptt_left=mptt_left-? WHERE vocabulary_id=? AND mptt_left > ?', $params );
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
			$params = array( $nodes_moving, $this->id, $source_right );
			$res = DB::query( 'UPDATE {terms} SET mptt_right=mptt_right-? WHERE vocabulary_id=? AND mptt_right > ?', $params );
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
 
			// make room for the displaced nodes
			$params = array( $nodes_moving, $this->id, $destination_right - ( $source_right - $source_left ) );
			$res = DB::query( 'UPDATE {terms} SET mptt_left=mptt_left+? WHERE vocabulary_id=? AND mptt_left >= ?', $params );
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
			$params = array( $nodes_moving, $this->id, $destination_right );
			$res = DB::query( 'UPDATE {terms} SET mptt_right=mptt_right+? WHERE vocabulary_id=? AND mptt_right >= ?', $params );
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
 
 
			// Move the existing nodes back into the space left for them
			// Cannot finish before redoing this to bring the terms positive again.
			$res = displace_term( $term, $destination_right + 1 ); 
			if( ! $res ) {
				DB::rollback();
				return FALSE;
			}
 
			// Success!
			DB::commit();
			return SOMETHING; // need to return something, but what? The term in its new place?
 
		}
		return FALSE; // probably should put this in an else.
	}
 
	/**
	 * Gets the term object by id. No parameter returns the root Term object.
	 * @param integer $term_id The id of the term to fetch, or null for the root node
	 * @return Term The Term object requested
	 **/
	public function get_term($term_id = null)
	{
		return Term::get($this->id, $term_id);
	}
 
	/**
	 * Gets the Term objects associated to that type of object with that id in this vocabulary
	 * For example, return all terms in this vocabulary that are associated with a particular post
	 *
	 * @param String the name of the object type
	 * @param integer The id of the object for which you want the terms
	 * @return Array The Term objects requested
	 **/
	public function get_object_terms($object_type, $id)
	{
		$results = DB::get_results(
			'SELECT id, term, term_display, vocabulary_id, mptt_left, mptt_right FROM {terms}
			JOIN {object_terms} ON {terms}.id = {object_terms}.term_id
			WHERE {terms}.vocabulary_id = ?
				AND {object_terms}.object_type_id = ?
				AND {object_terms}.object_id = ?',
			array( $this->id, self::object_type_id( $object_type ), $id ),
			'Term'
		);
 
		return $results;
	}
 
	/**
	 * Sets the Term objects associated to that type of object with that id in this vocabulary
	 *
	 * @param String the name of the object type
	 * @param Integer The id of the object for which you want the terms
	 * @param Array. The names of the terms to associate
	 *
	 * @return boolean. Whether the associations were successful or not
	 **/
	public function set_object_terms( $object_type, $id, $terms )
	{
		// no terms? then let's get out'a'here
		if (count($terms) == 0) {
			Plugins::act( 'term_detach_all_from_object_before', $this->id );
 
			$results = $this->get_object_terms( $object_type, $this->id );
			foreach ( $results as $term ) {
				$term->dissociate( $term->id, $id );
			}
 
			Plugins::act( 'term_detach_all_from_object_after', $this->id );
			return TRUE;
		}
		/*
		 * First, let's clean the incoming tag text array, ensuring we have
		 * a unique set of tag texts and slugs.
		 */
		$term_ids_to_object = $clean_terms = array();
		foreach ( ( array ) $terms as $term )
			if ( ! in_array( $term, array_keys( $clean_terms ) ) )
				if ( ! in_array( $slug = Utils::slugify( $term ), array_values( $clean_terms ) ) )
					$clean_terms[$term] = $slug;
 
		/* Now, let's insert any *new* term display text or slugs into the terms table */
		$placeholders = Utils::placeholder_string( count( $clean_terms ) );
		$sql_terms_exist = "SELECT id, term_display, term
			FROM {terms}
			WHERE term_display IN ({$placeholders})
			OR term IN ({$placeholders})
			AND vocabulary_id = ?";
		$params = array_merge( array_keys( $clean_terms ), array_values( $clean_terms ), (array)$this->id );
		$existing_terms = DB::get_results( $sql_terms_exist, $params, 'Term' );
		if ( count( $existing_terms ) > 0 ) {
			/* Terms exist which match the term text or the term */
			foreach ( $existing_terms as $existing_term ) {
				/*
				 * Term exists.
				 * Attach object to term, then remove the term from creation list.
				 */
				$existing_term->associate( $object_type, $id );
				$term_ids_to_object[] = $existing_term->id;
 
				/*
				 * We remove it from the clean_terms collection as we only
				 * want to add to the terms table those terms which don't already exist
				 */
				if ( in_array( $existing_term->term_display, array_keys( $clean_terms ) ) ) {
					unset( $clean_terms[$existing_term->term_display] );
				}
				if ( in_array( $existing_term->term, array_values( $clean_terms ) ) ) {
					foreach ( $clean_terms as $text => $slug ) {
						if ( $slug == $existing_term->term ) {
							unset( $clean_terms[$text] );
							break;
						}
					}
				}
			}
		}
 
		/*
		 * $clean_terms now contains an associative array of terms
		 * we need to add to the main terms table, so add them
		 *
		 */
		foreach ( $clean_terms as $new_term_text => $new_term_slug ) {
			$term = new Term( array( 'term_display' => $new_term_text, 'term' => $new_term_slug ) );
			$this->add_term( $term );
			$term->associate( $object_type, $id );
			$term_ids_to_object[] = $term->id;
		}
 
		/*
		 * Finally, remove the terms which are no longer associated with the object.
		 */
		$repeat_questions = Utils::placeholder_string( count( $term_ids_to_object ) );
		$sql_delete = "SELECT term_id FROM {object_terms}
			JOIN {terms} ON term_id = {terms}.id
			WHERE object_id = ? AND term_id NOT IN ({$repeat_questions}) AND object_type_id = ?
			AND {terms}.vocabulary_id = ?";
		$params = array_merge( (array) $id, array_values( $term_ids_to_object ), (array)Vocabulary::object_type_id( $object_type ), (array)$this->id );
 
		$result = DB::get_column( $sql_delete, $params );
 
		foreach ( $result as $t ) {
			$term = $this->get_term( $t );
			$term->dissociate( $object_type, $id );
		}
 
		return TRUE;
	}
 
	 /**
	  * Temporarily set a term and its descendants aside from the rest of a vocabulary.
	  * If this is not done a second time, corruption will result.
	  *
	  **/
	private function displace_term( $term, $displacement )
	{
		// took this from delete_term below
		if ( is_string( $term ) ) { 
			$term = $this->get_term( $term );
		}
 
		$params = array( $displacement, $displacement, $this->id, $term->mptt_left, $term->mptt_right );
		return DB::query( 'UPDATE {terms} SET mptt_left = mptt_left + ?, mptt_right = mptt_right + ? WHERE vocabulary_id = ? AND mptt_left BETWEEN ? AND ?', $params );
	}
 
	/**
	 * Remove the term from the vocabulary.Convenience method to ->get_term('foo')->delete().
	 *
	 **/
	public function delete_term( $term )
	{
		if ( is_string($term) ) {
			$term = $this->get_term($term);
		}
 
		// TODO How should we handle deletion of a term with descendants?
		// Perhaps a $keep_children flag to move descendants to be descendants of
		// the deleted term's parent? Terms should not change the left and right
		// values of other terms, and thus their deletion should only occur through
		// the vocabulary to which they belong. Is it feasible to restrict this?
		// For the moment, just delete the descendants
		$params = array($this->id, $term->mptt_left, $term->mptt_right);
		DB::query('DELETE from {terms} WHERE vocabulary_id=? AND mptt_left>? AND mptt_right<?', $params);
 
		// Fix mptt_left and mptt_right values for other nodes in the vocabulary
		$offset = $term->mptt_right - $term->mptt_left + 1;
		$ref = $this->mptt_left;
		$params = array($offset, $this->id, $term->mptt_left);
 
		// Delete the term
		$term->delete();
 
		// Renumber left and right values of other nodes appropriately
		DB::query('UPDATE {terms} SET mptt_right=mptt_right-? WHERE vocabulary_id=? AND mptt_right>?', $params);
		DB::query('UPDATE {terms} SET mptt_left=mptt_left-? WHERE vocabulary_id=? AND mptt_left>?', $params);
 
	}
 
	/**
	 * Check if this vocabulary is empty.
	 *
	 **/
	public function is_empty()
	{
		return ( (int) DB::get_value( "SELECT COUNT(id) FROM {terms} WHERE vocabulary_id=?", array( $this->id ) ) == 0 );
	}
 
 
	/**
	 * Retrieve the vocabulary
	 * @return Array The Term objects in the vocabulary, in tree order
	 **/
	public function get_tree($orderby = 'mptt_left ASC')
	{
		return DB::get_results( "SELECT * FROM {terms} WHERE vocabulary_id=? ORDER BY $orderby", array($this->id), 'Term' );
	}
 
	/**
	 * Get all root elements in this vocabulary
	 * @return Array The root Term objects in the vocabulary
	 */
	public function get_root_terms()
	{
		/**
		 * If we INNER JOIN the terms table with itself on ALL the descendants,
		 * then descendants one level down are listed once, two levels down are listed twice,
		 * etc. If we return only those terms which appear once, we get root elements.
		 * ORDER BY NULL to avoid the MySQL filesort.
		 */
		$query = <<<SQL
SELECT child.term as term,
	child.term_display as term_display,
	child.mptt_left as mptt_left,
	child.mptt_right as mptt_right,
	child.vocabulary_id as vocabulary_id
FROM {terms} as parent
INNER JOIN {terms} as child
	ON child.mptt_left BETWEEN parent.mptt_left AND parent.mptt_right
	AND child.vocabulary_id = parent.vocabulary_id
WHERE parent.vocabulary_id = ?
GROUP BY child.term
HAVING COUNT(child.term)=1
ORDER BY NULL
SQL;
		return DB::get_results( $query, array($this->id), 'Term' );
	}
 
	/**
	 * inserts a new object type into the database, if it doesn't exist
	 * @param string The name of the new post type
	 * @param bool Whether the new post type is active or not
	 * @return none
	**/
	public static function add_object_type( $type )
	{
		$params = array( 'name' => $type );
		if ( ! DB::exists( "{object_types}", $params ) ) {
			DB::insert( "{object_types}", $params );
		}
	}
 
	/**
	 * Return the object type id for a named object, such as a post
	 *
	 * @param string $name The type of object
	 * @return integer The id of the object type
	*/
	public static function object_type_id( $type )
	{
		$id = (int)DB::get_value( "SELECT id FROM {object_types} WHERE name = ?", array( $type ) );
		return $id;
 
	}
}
 
?>

Toggle wordwrap

Referring DomainHits
Unknown Referer 127
pastoid.com 2
Is this paste spam?
<Hide