Skip to content
Extraits de code Groupes Projets
DataTable.php 41,5 ko
Newer Older
  • Learn to ignore specific revisions
  • 	 * The keys of the array are very important as they are used to define the DataTable
    
    	 * IMPORTANT: The main table (level 0, parent of all tables) will always be indexed by 0
    	 * 	even it was created after some other tables.
    
    	 * 	It also means that all the parent tables (level 0) will be indexed with 0 in their respective
    	 *  serialized arrays. You should never lookup a parent table using the getTable( $id = 0) as it
    
    	 * @throws Exception if an infinite recursion is found (a table row's has a subtable that is one of its parent table)
    
    	 * @param int    $maximumRowsInDataTable          If not null, defines the number of rows maximum of the serialized dataTable
    	 * @param int    $maximumRowsInSubDataTable       If not null, defines the number of rows maximum of the serialized subDataTable
    	 * @param string $columnToSortByBeforeTruncation  Column to sort by before truncation
    	 * @return array  Serialized arrays
    
    	 * 					0 => 'eghuighahgaueytae78yaet7yaetae',
    	 *
    
    	 * 					// first Datatable level1
    	 * 					1 => 'gaegae gh gwrh guiwh uigwhuige',
    
    	 *
    	 * 					//second Datatable level1
    	 * 					2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE',
    	 *
    
    	 * 					//first Datatable level3 (child of second Datatable level1 for example)
     	 *					3 => 'eghuighahgaueytae78yaet7yaetaeGRQWUBGUIQGH&QE',
    	 * 					);
    	 */
    
    	public function getSerialized(	$maximumRowsInDataTable = null, 
    									$maximumRowsInSubDataTable = null,
    									$columnToSortByBeforeTruncation = null )
    
    		if($depth > self::$maximumDepthLevelAllowed)
    
    			throw new Exception("Maximum recursion level of ".self::$maximumDepthLevelAllowed. " reached. Maybe you have set a DataTable_Row with an associated DataTable belonging already to one of its parent tables?");
    
    mattpiwik's avatar
    mattpiwik a validé
    			$this->filter('AddSummaryRow', 
    
    							array(	$maximumRowsInDataTable - 1, 
    									Piwik_DataTable::LABEL_SUMMARY_ROW, 
    									$columnToSortByBeforeTruncation)
    					);
    
    		}
    		
    		// For each row, get the serialized row
    		// If it is associated to a sub table, get the serialized table recursively ;
    
    		// but returns all serialized tables and subtable in an array of 1 dimension
    
    		$aSerializedDataTable = array();
    		foreach($this->rows as $row)
    		{
    			if(($idSubTable = $row->getIdSubDataTable()) !== null)
    			{
    				$subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
    				$depth++;
    
    				$aSerializedDataTable = $aSerializedDataTable + $subTable->getSerialized( $maximumRowsInSubDataTable, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation );
    
    				$depth--;
    			}
    		}
    		// we load the current Id of the DataTable
    		$forcedId = $this->getId();
    		
    		// if the datatable is the parent we force the Id at 0 (this is part of the specification)
    		if($depth == 0)
    		{
    			$forcedId = 0;
    		}
    		
    		// we then serialize the rows and store them in the serialized dataTable
    
    		$addToRows = array( self::ID_SUMMARY_ROW => $this->summaryRow );
    
    		if ($this->parents && Piwik_Config::getInstance()->General['enable_archive_parents_of_datatable'])
    
    		{
    			$addToRows[self::ID_PARENTS] = $this->parents;
    		}
    		$aSerializedDataTable[$forcedId] = serialize($this->rows + $addToRows);
    
    		foreach($this->rows as &$row)
    		{
    			$row->cleanPostSerialize();
    		}
    		
    
    	/**
    	 * Load a serialized string of a datatable.
    	 *
    	 * Does not load recursively all the sub DataTable.
    	 * They will be loaded only when requesting them specifically.
    	 *
    	 * The function creates all the necessary DataTable_Row
    	 *
    
    	 * @param string  $stringSerialized  string of serialized datatable
    
    	 * @throws Exception
    	 */
    
    	public function addRowsFromSerializedArray( $stringSerialized )
    
    	{
    		$serialized = unserialize($stringSerialized);
    		if($serialized === false)
    		{
    			throw new Exception("The unserialization has failed!");
    		}
    
    		$this->addRowsFromArray($serialized);
    
    	}
    
    	/**
    	 * Loads the DataTable from a PHP array data structure
    	 * 
    
    	 * @param array  $array  Array with the following structure
    	 *                       array(
     	 *                             // row1
    	 *                             array(
    	 *                                   Piwik_DataTable_Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...),
    	 *                                   Piwik_DataTable_Row::METADATA => array( metadata1_name => value1,  ...), // see Piwik_DataTable_Row
    	 *                             ),
    	 *                             // row2
    	 *                             array( ... ),
    	 *                       )
    
    	public function addRowsFromArray( $array )
    
    			if($id == self::ID_PARENTS)
    			{
    				$this->parents = $row;
    				continue;
    			}
    						
    
    			if(is_array($row))
    			{
    				$row = new Piwik_DataTable_Row($row);
    			}
    			if($id == self::ID_SUMMARY_ROW)
    			{
    				$this->summaryRow = $row;
    			}
    			else 
    			{
    				$this->addRow($row);
    			}
    		}
    	}
    
    	/**
    	 * Loads the data from a simple php array.
    	 * Basically maps a simple multidimensional php array to a DataTable.
    	 * Not recursive (if a row contains a php array itself, it won't be loaded)
    
    	 * @param array  $array  Array with the simple structure:
    	 *                       array(
    	 *                             array( col1_name => valueA, col2_name => valueC, ...),
    	 *                             array( col1_name => valueB, col2_name => valueD, ...),
    	 *                       )
    
    	 * @throws Exception
    	 * @return
    
    	public function addRowsFromSimpleArray( $array )
    
    	{
    		if(count($array) === 0)
    		{
    			return;
    		}
    		
    		// we define an exception we may throw if at one point we notice that we cannot handle the data structure
    		$e = new Exception(" Data structure returned is not convertible in the requested format.".
    						" Try to call this method with the parameters '&format=original&serialize=1'".
    						"; you will get the original php data structure serialized.".
    						" The data structure looks like this: \n \$data = " . var_export($array, true) . "; ");
    				
    		
    		// first pass to see if the array has the structure
    		// array(col1_name => val1, col2_name => val2, etc.)
    		// with val* that are never arrays (only strings/numbers/bool/etc.)
    		// if we detect such a "simple" data structure we convert it to a row with the correct columns' names
    		$thisIsNotThatSimple = false;
    		
    
    Fabian Becker's avatar
    Fabian Becker a validé
    		foreach(array_values($array) as $columnValue )
    
    		{
    			if(is_array($columnValue) || is_object($columnValue)) 
    			{
    				$thisIsNotThatSimple = true;
    				break;
    			}
    		}
    		if($thisIsNotThatSimple === false)
    		{
    			// case when the array is indexed by the default numeric index
    			if( array_keys($array) == array_keys(array_fill(0, count($array), true)) )
    			{
    				foreach($array as $row)
    				{
    					$this->addRow( new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => array($row) ) ) );					
    				}
    			}
    			else
    			{
    				$this->addRow( new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => $array ) ) );
    			}
    			// we have converted our simple array to one single row
    			// => we exit the method as the job is now finished 
    			return;
    		}
    		
    		foreach($array as $key => $row)
    		{
    			// stuff that looks like a line
    			if(is_array($row))
    			{
    				/**
    				 * We make sure we can convert this PHP array without losing information.
    				 * We are able to convert only simple php array (no strings keys, no sub arrays, etc.)
    				 * 
    				 */
    				
    				// if the key is a string it means that some information was contained in this key. 
    				// it cannot be lost during the conversion. Because we are not able to handle properly
    				// this key, we throw an explicit exception.
    				if(is_string($key))
    				{
    					throw $e;
    				}
    				// if any of the sub elements of row is an array we cannot handle this data structure...
    				foreach($row as $subRow)
    				{
    					if(is_array($subRow))
    					{
    
    					}
    				}
    				$row = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => $row ) );		
    			}
    			// other (string, numbers...) => we build a line from this value
    			else
    			{
    				$row = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => array($key => $row)) );
    			}				
    			$this->addRow($row);
    		}
    	}
    
    	/**
    	 * Rewrites the input $array 
    	 * array (
    	 * 	 LABEL => array(col1 => X, col2 => Y),
    	 * 	 LABEL2 => array(col1 => X, col2 => Y),
    	 * )
    	 * to the structure 
    	 * array (
    	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y)),
    	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)),
    	 * )
    	 * 
    
    	 * It also works with array having only one value per row, eg.
    	 * array (
    	 * 	 LABEL => X,
    	 * 	 LABEL2 => Y,
    	 * )
    	 * would be converted to the structure 
    	 * array (
    	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL, 'value' => X)),
    	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL2, 'value' => Y)),
    	 * )
    	 * 
    
    	 * The optional parameter $subtablePerLabel is an array of subTable associated to the rows of the $array
    	 * For example if $subtablePerLabel is given
    	 * array(
    	 * 		LABEL => #Piwik_DataTable_ForLABEL,
    	 * 		LABEL2 => #Piwik_DataTable_ForLABEL2,
    	 * )
    	 * 
    	 * the $array would become 
    	 * array (
    	 * 	 array( 	Piwik_DataTable_Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y),
    	 * 				Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #ID DataTable For LABEL
    	 * 		),
    	 * 	 array( 	Piwik_DataTable_Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)
    	 * 				Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #ID2 DataTable For LABEL2
    	 * 		),
    	 * )
    	 * 
    
    	 * @param array       $array             See method description
    	 * @param array|null  $subtablePerLabel  See method description
    
    	public function addRowsFromArrayWithIndexLabel( $array, $subtablePerLabel = null)
    
    	{
    		$cleanRow = array();
    		foreach($array as $label => $row)
    		{
    
    			if(!is_array($row))
    			{
    				$row = array('value' => $row);
    			}
    
    			$cleanRow[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = null;
    
    			// we put the 'label' column first as it looks prettier in API results
    
    			$cleanRow[Piwik_DataTable_Row::COLUMNS] = array('label' => $label) + $row;
    			if(!is_null($subtablePerLabel)
    				// some rows of this table don't have subtables 
    
    				// (for example case of campaigns without keywords)
    
    				&& isset($subtablePerLabel[$label]) 
    			)
    			{
    				$cleanRow[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $subtablePerLabel[$label];
    			}
    			$this->addRow( new Piwik_DataTable_Row($cleanRow) );
    		}
    	}
    
    	 */
    	public function setParents($parents)
    	{
    		$this->parents = $parents;
    	}
    	
    	/**
    	 * Get parents
    
    	 *
    	 * @return array  of all parents, root level first
    
    	 */
    	public function getParents() {
    		if ($this->parents == null)
    		{
    			return array();
    		}
    		return $this->parents;
    	}
    	
    
    	/**
    	 * Sets the maximum nesting level to at least a certain value. If the current value is
    	 * greater than the supplied level, the maximum nesting level is not changed.
    	 * 
    
    	 */
    	static public function setMaximumDepthLevelAllowedAtLeast( $atLeastLevel )
    	{
    		self::$maximumDepthLevelAllowed = max($atLeastLevel, self::$maximumDepthLevelAllowed);
    
    		if(self::$maximumDepthLevelAllowed < 1) {
    			self::$maximumDepthLevelAllowed = 1;
    		}
    
    	
    	/**
    	 * Returns all table metadata.
    	 * 
    	 * @return array
    	 */
    	public function getAllTableMetadata()
    	{
    		return $this->metadata;
    	}
    	
    	/**
    	 * Returns metadata by name.
    	 * 
    	 * @param string $name The metadata name.
    	 * @return mixed
    	 */
    	public function getMetadata( $name )
    	{
    
    		return $this->metadata[$name];
    	}
    	
    	/**
    	 * Sets a metadata value by name.
    	 * 
    	 * @param string $name The metadata name.
    	 * @param mixed $value
    	 */
    	public function setMetadata( $name, $value )
    	{
    		$this->metadata[$name] = $value;
    	}
    
    	
    	/**
    	 * Sets the maximum number of rows allowed in this datatable (including the summary
    	 * row). If adding more then the allowed number of rows is attempted, the extra
    	 * rows are added to the summary row.
    	 * 
    	 * @param int|null $maximumAllowedRows
    	 */
    	public function setMaximumAllowedRows( $maximumAllowedRows )
    	{
    		$this->maximumAllowedRows = $maximumAllowedRows;
    	}
    	
    	/**
    	 * Traverses a DataTable tree using an array of labels and returns the row
    	 * it finds or false if it cannot find one, and the number of segments of
    	 * the path successfully walked.
    	 * 
    	 * If $missingRowColumns is supplied, the specified path is created. When
    	 * a subtable is encountered w/o the queried label, a new row is created
    	 * with the label, and a subtable is added to the row.
    	 * 
    	 * @param array $path The path to walk. An array of label values.
    	 * @param array|false $missingRowColumns
    	 *						The default columns to use when creating new arrays.
    	 * 						If this parameter is supplied, new rows will be
    	 * 						created if labels cannot be found.
    	 * @param int $maxSubtableRows The maximum number of allowed rows in new
    	 *                             subtables.
    	 * @return array First element is the found row or false. Second element is
    	 *               the number of path segments walked. If a row is found, this
    	 *               will be == to count($path). Otherwise, it will be the index
    	 *               of the path segment that we could not find.
    	 */
    	public function walkPath( $path, $missingRowColumns = false, $maxSubtableRows = 0 )
    	{
    		$pathLength = count($path);
    		
    		$table = $this;
    		$next = false;
    		for ($i = 0; $i < $pathLength; ++$i)
    		{
    			$segment = $path[$i];
    			
    			$next = $table->getRowFromLabel($segment);
    			if ($next === false)
    			{
    				// if there is no table to advance to, and we're not adding missing rows, return false
    				if ($missingRowColumns === false)
    				{
    					return array(false, $i);
    				}
    				else // if we're adding missing rows, add a new row
    				{
    					$row = new Piwik_DataTable_Row_DataTableSummary();
    					$row->setColumns(array('label' => $segment) + $missingRowColumns);
    					
    					$next = $table->addRow($row);
    
    mattpiwik's avatar
     
    mattpiwik a validé
    
    
    					if ($next !== $row) // if the row wasn't added, the table is full
    					{
    
    mattpiwik's avatar
     
    mattpiwik a validé
    						// Summary row, has no metadata
    						$next->deleteMetadata();
    
    						return array($next, $i);
    					}
    				}
    			}
    			
    			$table = $next->getSubtable();
    			if ($table === false)
    			{
    				// if the row has no table (and thus no child rows), and we're not adding
    				// missing rows, return false
    				if ($missingRowColumns === false)
    				{
    					return array(false, $i);
    				}
    				else if ($i != $pathLength - 1) // create subtable if missing, but only if not on the last segment
    				{
    					$table = new Piwik_DataTable();
    					$table->setMaximumAllowedRows($maxSubtableRows);
    					$next->setSubtable($table);
    
    mattpiwik's avatar
     
    mattpiwik a validé
    					// Summary row, has no metadata
    					$next->deleteMetadata();
    
    
    	/**
    	 * Returns a new DataTable that contains the rows of each of this table's
    	 * subtables.
    	 * 
    
    	 * @param string|false $labelColumn If supplied the label of the parent row will be
    	 *                                  added to a new column in each subtable row. If set to,
    	 *                                  'label' each subtable row's label will be prepended w/
    	 *                                  the parent row's label.
    	 * @param bool $useMetadataColumn If true and if $labelColumn is supplied, the parent row's
    	 *                                label will be added as metadata.
    
    	public function mergeSubtables( $labelColumn = false, $useMetadataColumn = false )
    
    	{
    		$result = new Piwik_DataTable();
    		foreach ($this->getRows() as $row)
    		{
    			$subtable = $row->getSubtable();
    			if ($subtable !== false)
    			{
    
    				$parentLabel = $row->getColumn('label');
    				
    				// add a copy of each subtable row to the new datatable
    				foreach ($subtable->getRows() as $id => $subRow)
    				{
    					$copy = clone $subRow;
    					
    					// if the summary row, add it to the existing summary row (or add a new one)
    					if ($id == self::ID_SUMMARY_ROW)
    					{
    						$existing = $result->getRowFromId(self::ID_SUMMARY_ROW);
    						if ($existing === false)
    						{
    							$result->addSummaryRow($copy);
    						}
    						else
    						{
    							$existing->sumRow($copy);
    						}
    					}
    					else
    					{
    						if ($labelColumn !== false)
    						{
    							// if we're modifying the subtable's rows' label column, then we make
    							// sure to prepend the existing label w/ the parent row's label. otherwise
    							// we're just adding the parent row's label as a new column/metadata.
    							$newLabel = $parentLabel;
    							if ($labelColumn == 'label')
    							{
    								$newLabel .= ' - '.$copy->getColumn('label');
    							}
    					
    							// modify the child row's label or add new column/metadata
    							if ($useMetadataColumn)
    							{
    								$copy->setMetadata($labelColumn, $newLabel);
    							}
    							else
    							{
    								$copy->setColumn($labelColumn, $newLabel);
    							}
    						}
    					
    						$result->addRow($copy);
    					}
    				}
    
    	
    	/**
    	 * Returns a new DataTable created with data from a 'simple' array.
    	 * 
    	 * @param array $array
    	 * @return Piwik_DataTable
    	 */
    	public static function makeFromSimpleArray( $array )
    	{
    		$dataTable = new Piwik_DataTable();
    		$dataTable->addRowsFromSimpleArray($array);
    		return $dataTable;
    	}