Skip to content
Extraits de code Groupes Projets
Row.php 15,9 ko
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /**
     * Piwik - Open source web analytics
     * 
     * @link http://piwik.org
    
    robocoder's avatar
    robocoder a validé
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
    
    robocoder's avatar
    robocoder a validé
     * @category Piwik
     * @package Piwik
    
     */
    
    /**
     * A DataTable is composed of rows.
     * 
     * A row is composed of:
     * - columns often at least a 'label' column containing the description 
     * 		of the row, and some numeric values ('nb_visits', etc.)
     * - metadata: other information never to be shown as 'columns'
     * - idSubtable: a row can be linked to a SubTable
     * 
     * IMPORTANT: Make sure that the column named 'label' contains at least one non-numeric character.
    
     *            Otherwise the method addDataTable() or sumRow() would fail because they would consider
     *            the 'label' as being a numeric column to sum.
    
     * PERFORMANCE: Do *not* add new fields except if necessary in this object. New fields will be 
     *              serialized and recorded in the DB millions of times. This object size is critical and must be under control.
     *              
    
    robocoder's avatar
    robocoder a validé
     * @package Piwik
     * @subpackage Piwik_DataTable
    
     */
    class Piwik_DataTable_Row
    {
    	/**
    	 * This array contains the row information:
    	 * - array indexed by self::COLUMNS contains the columns, pairs of (column names, value) 
    	 * - (optional) array indexed by self::METADATA contains the metadata,  pairs of (metadata name, value)
    	 * - (optional) integer indexed by self::DATATABLE_ASSOCIATED contains the ID of the Piwik_DataTable associated to this row. 
    	 *   This ID can be used to read the DataTable from the DataTable_Manager.
    	 * 
    	 * @var array
    	 * @see constructor for more information
    	 */
    	public $c = array();
    	
    	const COLUMNS = 0;
    	const METADATA = 1;
    	const DATATABLE_ASSOCIATED = 3;
    
    
    	/**
    	 * Efficient load of the Row structure from a well structured php array
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param array  $row  The row array has the structure
    	 *                     array(
    	 *                           Piwik_DataTable_Row::COLUMNS => array(
    	 *                                                                 'label' => 'Piwik',
    	 *                                                                 'column1' => 42,
    	 *                                                                 'visits' => 657,
    	 *                                                                 'time_spent' => 155744,
    	 *                                                                 ),
    	 *                            Piwik_DataTable_Row::METADATA => array(
    	 *                                                                  'logo' => 'test.png'
    	 *                                                                  ),
    	 *                            Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #Piwik_DataTable object
    	 *                                                                         (but in the row only the ID will be stored)
    	 *                           )
    
    	 */
    	public function __construct( $row = array() )
    	{
    		$this->c[self::COLUMNS] = array();
    		$this->c[self::METADATA] = array();
    		$this->c[self::DATATABLE_ASSOCIATED] = null;
    		
    		if(isset($row[self::COLUMNS]))
    		{
    			$this->c[self::COLUMNS] = $row[self::COLUMNS];
    		}
    		if(isset($row[self::METADATA]))
    		{
    			$this->c[self::METADATA] = $row[self::METADATA];
    		}
    		if(isset($row[self::DATATABLE_ASSOCIATED])
    			&& $row[self::DATATABLE_ASSOCIATED] instanceof Piwik_DataTable)
    		{
    
    			$this->setSubtable($row[self::DATATABLE_ASSOCIATED]);
    
    		}
    	}
    	
    	/**
    	 * Because $this->c[self::DATATABLE_ASSOCIATED] is negative when the table is in memory,
    	 * we must prior to serialize() call, make sure the ID is saved as positive integer
    	 */
    	public function __sleep()
    	{
    		if(!empty($this->c[self::DATATABLE_ASSOCIATED]) && $this->c[self::DATATABLE_ASSOCIATED] < 0)
    		{
    			$this->c[self::DATATABLE_ASSOCIATED] = abs($this->c[self::DATATABLE_ASSOCIATED]);
    		}
    		return array('c');
    
    	}
    	
    	/**
    	 * When destroyed, a row destroys its associated subTable if there is one
    	 */
    	public function __destruct()
    	{
    
    			Piwik_DataTable_Manager::getInstance()->deleteTable( $this->getIdSubDataTable() );
    
    mattpiwik's avatar
    mattpiwik a validé
    			$this->c[self::DATATABLE_ASSOCIATED] = null;
    
    sgiehl's avatar
    sgiehl a validé
    	 * Applies a basic rendering to the Row and returns the output
    
    	 *
    	 * @return string characterizing the row. Example: - 1 ['label' => 'piwik', 'nb_uniq_visitors' => 1685, 'nb_visits' => 1861, 'nb_actions' => 2271, 'max_actions' => 13, 'sum_visit_length' => 920131, 'bounce_count' => 1599] [] [idsubtable = 1375]
    	 */
    	public function __toString()
    	{
    		$columns = array();
    		foreach($this->getColumns() as $column => $value)
    		{
    			if(is_string($value)) $value = "'$value'";
    
    			elseif(is_array($value)) $value = var_export($value, true);
    
    			$columns[] = "'$column' => $value";
    		}
    		$columns = implode(", ", $columns);
    		$metadata = array();
    		foreach($this->getMetadata() as $name => $value)
    		{
    
    			if(is_string($value)) $value = "'$value'";
    			elseif(is_array($value)) $value = var_export($value, true);
    
    			$metadata[] = "'$name' => $value";
    		}
    		$metadata = implode(", ", $metadata);
    
    		$output = "# [".$columns."] [".$metadata."] [idsubtable = " . $this->getIdSubDataTable()."]<br />\n";
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name  Column name
    	 * @return bool  True on success, false if the column didn't exist
    
    	 */
    	public function deleteColumn( $name )
    	{
    		if(!isset($this->c[self::COLUMNS][$name]))
    		{
    			return false;
    		}
    		unset($this->c[self::COLUMNS][$name]);
    		return true;
    	}
    
    sgiehl's avatar
    sgiehl a validé
    
    	/**
    	 * Renames the given column
    	 *
    	 * @param string  $oldName
    	 * @param string  $newName
    	 */
    
    	public function renameColumn($oldName, $newName)
    	{
    		if(isset($this->c[self::COLUMNS][$oldName]))
    		{
    			$this->c[self::COLUMNS][$newName] = $this->c[self::COLUMNS][$oldName];
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    		// outside the if() since we want to delete nulled columns
    		unset($this->c[self::COLUMNS][$oldName]);
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name  Column name
    	 * @return mixed|false  The column value
    
    	 */
    	public function getColumn( $name )
    	{
    		if(!isset($this->c[self::COLUMNS][$name]))
    		{
    			return false;
    		}
    		return $this->c[self::COLUMNS][$name];
    	}
    	
    	/**
    	 * Returns the array of all metadata,
    	 * or the specified metadata  
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name  Metadata name
    
    	 * @return mixed|array|false 
    	 */
    	public function getMetadata( $name = null )
    	{
    		if(is_null($name))
    		{
    			return $this->c[self::METADATA];
    		}
    		if(!isset($this->c[self::METADATA][$name]))
    		{
    			return false;
    		}
    		return $this->c[self::METADATA][$name];
    	}
    	
    	/**
    	 * Returns the array containing all the columns
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @return array  Example: array(
    	 *                              'column1'   => VALUE,
    	 *                              'label'     => 'www.php.net'
    	 *                              'nb_visits' => 15894,
    	 *                              )
    
    	 */
    	public function getColumns()
    	{
    		return $this->c[self::COLUMNS];
    	}
    	
    	/**
    	 * Returns the ID of the subDataTable. 
    	 * If there is no such a table, returns null.
    	 * 
    	 * @return int|null
    	 */
    	public function getIdSubDataTable()
    	{
    
    		return !is_null($this->c[self::DATATABLE_ASSOCIATED])
    				// abs() is to ensure we return a positive int, @see isSubtableLoaded()
    				? abs($this->c[self::DATATABLE_ASSOCIATED])
    				: null;
    
    	}
    	
    	/**
    	 * Sums a DataTable to this row subDataTable.
    	 * If this row doesn't have a SubDataTable yet, we create a new one.
    	 * Then we add the values of the given DataTable to this row's DataTable.
    	 * 	 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param Piwik_DataTable  $subTable  Table to sum to this row's subDatatable
    
    	 * @see Piwik_DataTable::addDataTable() for the algorithm used for the sum 
    	 */
    	public function sumSubtable(Piwik_DataTable $subTable)
    	{
    
    			$thisSubTable = Piwik_DataTable_Manager::getInstance()->getTable( $this->getIdSubDataTable() );
    
    			$thisSubTable = new Piwik_DataTable();
    			$this->addSubtable($thisSubTable);
    
    		}
    		$thisSubTable->addDataTable($subTable);
    	}
    	
    	
    	/**
    	 * Set a DataTable to be associated to this row.
    	 * If the row already has a DataTable associated to it, throws an Exception.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param Piwik_DataTable  $subTable  DataTable to associate to this row
    
    	 * @throws Exception 
    	 * 
    	 */
    	public function addSubtable(Piwik_DataTable $subTable)
    	{
    		if(!is_null($this->c[self::DATATABLE_ASSOCIATED]))
    		{
    			throw new Exception("Adding a subtable to the row, but it already has a subtable associated.");
    		}
    
    		// Hacking -1 to ensure value is negative, so we know the table was loaded
    		// @see isSubtableLoaded()
    		$this->c[self::DATATABLE_ASSOCIATED] = -1 * $subTable->getId();
    
    	}
    	
    	/**
    	 * Set a DataTable to this row. If there is already 
    	 * a DataTable associated, it is simply overwritten.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param Piwik_DataTable  $subTable  DataTable to associate to this row
    
    	 */
    	public function setSubtable(Piwik_DataTable $subTable)
    	{
    
    		// Hacking -1 to ensure value is negative, so we know the table was loaded
    		// @see isSubtableLoaded()
    		$this->c[self::DATATABLE_ASSOCIATED] = -1 * $subTable->getId();
    
    	/**
    	 * Returns true if the subtable is currently loaded in memory via DataTable_Manager
    	 * 
    	 * 
    	 * @return bool
    	 */
    	public function isSubtableLoaded()
    	{
    		// self::DATATABLE_ASSOCIATED are set as negative values, 
    		// as a flag to signify that the subtable is loaded in memory
    
    mattpiwik's avatar
    mattpiwik a validé
    		return !is_null($this->c[self::DATATABLE_ASSOCIATED])
    	  			&& $this->c[self::DATATABLE_ASSOCIATED] < 0;
    
    	/**
    	 * Remove the sub table reference
    	 */
    	public function removeSubtable()
    	{
    		$this->c[self::DATATABLE_ASSOCIATED] = null;
    	}
    	
    
    	/**
    	 * Set all the columns at once. Overwrites previously set columns.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param array  array(
    	 *                    'label'       => 'www.php.net'
    	 *                    'nb_visits'   => 15894,
    	 *                    )
    
    	 */
    	public function setColumns( $columns )
    	{
    		$this->c[self::COLUMNS] = $columns;
    	}
    
    	/**
    	 * Set the value $value to the column called $name.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name   name of the column to set
    	 * @param mixed   $value  value of the column to set
    
    	/**
    	 * Set the value $value to the metadata called $name.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name   name of the metadata to set
    	 * @param mixed   $value  value of the metadata to set
    
    	 */
    	public function setMetadata($name, $value)
    	{
    		$this->c[self::METADATA][$name] = $value;
    	}
    
    	 * Deletes the given metadata
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param bool|string  $name  Meta column name (omit to delete entire metadata)
    	 * @return bool  True on success, false if the column didn't exist
    
    	 */
    	public function deleteMetadata($name = false)
    	{
    		if($name === false)
    		{
    			$this->c[self::METADATA] = array();
    			return true;
    		}
    		if(!isset($this->c[self::METADATA][$name]))
    		{
    			return false;
    		}
    		unset($this->c[self::METADATA][$name]);
    		return true;
    	}
    	
    
    	/**
    	 * Add a new column to the row. If the column already exists, throws an exception
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name   name of the column to add
    	 * @param mixed   $value  value of the column to set
    
    	 * @throws Exception
    	 */
    	public function addColumn($name, $value)
    	{
    		if(isset($this->c[self::COLUMNS][$name]))
    		{
    
    mattpiwik's avatar
    mattpiwik a validé
    //			debug_print_backtrace();
    
    			throw new Exception("Column $name already in the array!");
    		}
    		$this->c[self::COLUMNS][$name] = $value;
    	}
    	
    
    mattpiwik's avatar
    mattpiwik a validé
    	/**
    	 * Add columns to the row
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param array  $columns  Name/Value pairs, e.g., array( name => value , ...)
    
    mattpiwik's avatar
    mattpiwik a validé
    	 * @return void
    	 */
    	public function addColumns($columns)
    	{
    		foreach($columns as $name => $value)
    		{
    
    			try {
    				$this->addColumn($name, $value); 
    			} catch(Exception $e) {
    			}
    		}
    		
    		if(!empty($e)) {
    			throw $e;
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    	}
    
    	
    	/**
    	 * Add a new metadata to the row. If the column already exists, throws an exception.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param string  $name   name of the metadata to add
    	 * @param mixed   $value  value of the metadata to set
    
    	 * @throws Exception
    	 */
    	public function addMetadata($name, $value)
    	{
    		if(isset($this->c[self::METADATA][$name]))
    		{
    			throw new Exception("Metadata $name already in the array!");
    		}
    		$this->c[self::METADATA][$name] = $value;
    	}
    
    	/**
    	 * Sums the given $row columns values to the existing row' columns values.
    	 * It will sum only the int or float values of $row.
    	 * It will not sum the column 'label' even if it has a numeric value.
    
    	 * If a given column doesn't exist in $this then it is added with the value of $row.
    	 * If the column already exists in $this then we have
    
    	 *         this.columns[idThisCol] += $row.columns[idThisCol]
    
    sgiehl's avatar
    sgiehl a validé
    	 *
    	 * @param Piwik_DataTable_Row  $rowToSum
    
    	 */
    	public function sumRow( Piwik_DataTable_Row $rowToSum )
    	{
    
    		foreach($rowToSum->getColumns() as $columnToSumName => $columnToSumValue)
    
    			if($columnToSumName != 'label')
    
    				$thisColumnValue = $this->getColumn($columnToSumName);
    
    				
    				// Max operation
    				if($columnToSumName == Piwik_Archive::INDEX_MAX_ACTIONS )
    				{
    					$newValue = max($thisColumnValue, $columnToSumValue);
    				}
    				else
    				{
    					$newValue = $this->sumRowArray($thisColumnValue, $columnToSumValue);
    				}
    
    				$this->setColumn( $columnToSumName, $newValue);
    			}
    		}
    	}
    
    sgiehl's avatar
    sgiehl a validé
    
    	/**
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param number|bool   $thisColumnValue
    	 * @param number|array  $columnToSumValue
    	 * @return array|int
    	 */
    
    	protected function sumRowArray( $thisColumnValue, $columnToSumValue )
    	{
    
    		{
    			if($thisColumnValue === false)
    			{
    				$thisColumnValue = 0;
    			}
    
    		{
    			if($thisColumnValue == false)
    			{
    
    			$newValue = $thisColumnValue;
    			foreach($columnToSumValue as $arrayIndex => $arrayValue)
    
    				$newValue[$arrayIndex] = $this->sumRowArray($newValue[$arrayIndex], $arrayValue);
    
    		
    		if (is_string($columnToSumValue))
    		{
    			if ($thisColumnValue === false)
    			{
    				return $columnToSumValue;
    			}
    			else if ($columnToSumValue === false)
    			{
    				return $thisColumnValue;
    			}
    			else
    			{
    
    				throw new Exception("Trying to add two strings values in DataTable_Row::sumRowArray: "
    					. "'$thisColumnValue' + '$columnToSumValue'");
    
    robocoder's avatar
    robocoder a validé
    
    	/**
    	 * Helper function to compare array elements
    	 *
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param mixed  $elem1
    	 * @param mixed  $elem2
    
    robocoder's avatar
    robocoder a validé
    	 * @return bool
    	 */
    	static public function compareElements($elem1, $elem2)
    	{
    		if (is_array($elem1)) {
    			if (is_array($elem2))
    			{
    				return strcmp(serialize($elem1), serialize($elem2));
    			}
    			return 1;
    		}
    		if (is_array($elem2))
    			return -1;
    
    		if ((string)$elem1 === (string)$elem2)
    			return 0;
    
    		return ((string)$elem1 > (string)$elem2) ? 1 : -1;
    	}
    
    
    	/**
    	 * Helper function to test if two rows are equal.
    	 * 
    	 * Two rows are equal 
    	 * - if they have exactly the same columns / metadata
    	 * - if they have a subDataTable associated, then we check that both of them are the same.
    	 * 
    
    sgiehl's avatar
    sgiehl a validé
    	 * @param Piwik_DataTable_Row  $row1  first to compare
    	 * @param Piwik_DataTable_Row  $row2  second to compare
    
    	 * @return bool
    	 */
    	static public function isEqual( Piwik_DataTable_Row $row1, Piwik_DataTable_Row $row2 )
    	{		
    		//same columns
    		$cols1 = $row1->getColumns();
    		$cols2 = $row2->getColumns();
    
    robocoder's avatar
    robocoder a validé
    
    		$diff1 = array_udiff($cols1, $cols2, array(__CLASS__, 'compareElements'));
    		$diff2 = array_udiff($cols2, $cols1, array(__CLASS__, 'compareElements'));
    
    		if($diff1 != $diff2)
    
    		{
    			return false;
    		}
    		
    		$dets1 = $row1->getMetadata();
    		$dets2 = $row2->getMetadata();
    		
    		ksort($dets1);
    		ksort($dets2);
    		
    		if($dets1 != $dets2)
    		{
    			return false;
    		}
    		
    		// either both are null
    		// or both have a value
    		if( !(is_null($row1->getIdSubDataTable()) 
    				&& is_null($row2->getIdSubDataTable())
    			)
    		)
    		{
    			$subtable1 = Piwik_DataTable_Manager::getInstance()->getTable($row1->getIdSubDataTable());
    			$subtable2 = Piwik_DataTable_Manager::getInstance()->getTable($row2->getIdSubDataTable());
    			if(!Piwik_DataTable::isEqual($subtable1, $subtable2))
    			{
    				return false;
    			}
    		}
    		return true;
    	}
    }