<?php
/*
********************************************************
Name: hdb_content.php
Author: Rob Hunter
Date 1/3/11
Description: Defines the HDB_Content object. This object must be able to:
* Create new content
* Easily access content attributes
* Modify existing attributes
* Add new attributes (without a lot of explicit commands)
* Save content with one command
********************************************************
*/
class HDB_Content{
/*
Constructor for HDB_Content. This can be called two ways - by a developer,
like God intended:
$newContent = new HDB_Content($title, $type, $description, $myArray)
where $myArray = {key => value, key => value}
This will create new content that will be saved to the database on
$newContent->save()
HDB_Query also creates content objects when puts a Content Store row
together with a Shape:
$queriedContent = new HDB_Content($objArray)
This is more of a backend content creation; the important distinction is
that an id is supplied, so the system knows to save modifications to the
queried content as opposed to create new content.
*/
function __construct($objArrayOrTitle, $type=FALSE, $description=FALSE, $myArray=FALSE){
if (is_array($objArrayOrTitle)){
//We're creating this content from a Content Store row -
//just populate all the right fields and call it a day
$this->id = $objArrayOrTitle['id'];
$this->createdDate = $objArrayOrTitle['createdDate'];
$this->updatedDate = $objArrayOrTitle['updatedDate'];
$this->type = $objArrayOrTitle['type'];
$this->title = $objArrayOrTitle['title'];
$this->description = $objArrayOrTitle['description'];
$this->contributorName= $objArrayOrTitle['contributorName'];
//iterate over nested myArray
foreach ($objArrayOrTitle['my'] as $key => $value){
$this->my->$key = $value;
}
//return object
return $this;
}
elseif(!empty($type)){
//Type is present - we're making a new object
//Don't let people submit dangerous characters in types (only devs
//will see the type anyway)
$cleanType = preg_replace('/[^a-zA-Z0-9]/', '', $type);
if ($cleanType == $type){
//Populate top level attributes
$this->type = $cleanType;
$this->title = $objArrayOrTitle;
$this->description = $description;
//If a reasonable myArray was supplied, populate my attributes
if (is_array($myArray)){
foreach ($myArray as $key => $value){
$this->my->$key = $value;
}
}
return $this;
}else{
die("HDB_Content ERROR: type must be alphanumeric<br>");
}
}
else{
die("HDB_Content ERROR: You must provide a content type<br>");
}
}
//Return debugging output, encased in <pre> tags.
//use: echo $content->debugHTML();
function debugHTML(){
//Don't judge me
$tab = " ";
$tab2 = $tab.$tab;
//Build the output for the top level attributes
$output = "<pre>";
$output .= "title: " . $this->title . "<br>";
$output .= $tab . "type: " . $this->type . "<br>";
$output .= $tab . "id: " . $this->id . "<br>";
$output .= $tab . "createdDate: " . $this->createdDate . "<br>";
$output .= $tab . "updatedDate: " . $this->updatedDate . "<br>";
$output .= $tab . "description: " . $this->description . "<br>";
$output .= $tab . "contributorName: " . $this->contributorName . "<br>";
$output .= $tab . "my: " . "<br>";
//Add the developer attributes
foreach (get_object_vars($this->my) as $attribute => $value){
if (!empty($value)){
$output .= $tab2 . "$attribute: " . $this->my->$attribute . "<br>";
}
}
$output .= "</pre>";
//return HTML
return $output;
}
//The save function commits any changes made (or the new object) to the
//Content Store table. In the event that this is a new shape, we have
//to update the Shapes table as well.
function save(){
global $shapes;
if (!empty($this->id)){
//id defined: update existing content
$deserialized = $this->_deserialize();
$this->_update($deserialized);
}elseif(!empty($shapes[$this->type])){
//id undefined, but shape defined: insert with the expectation
//of a shape
$deserialized = $this->_deserialize();
$this->_insert($deserialized);
}else{
//id and shape undefined: need to create new shape and insert
if ($this->_createShape()){
$deserialized = $this->_deserialize();
$this->_insert($deserialized);
}
}
}
//Internal function called if we're trying to save Content to a new shape
//or modify an existing shape (because we added a new attribute). It's
//here mostly as a tideover from before Shapes had their own, proper class
function _createShape($action = 'create'){
global $shapes;
global $mySQLTypeMappings;
global $numRange, $strRange, $txtRange, $shapesTable;
$newShape;
$curNum = $numRange['start'];
$curStr = $strRange['start'];
$curTxt = $txtRange['start'];
$type = $this->type;
if(!empty($type)){
//make sure that the type is reasonable (this was already done in
//the constructor, but in theory the type could be changed after
//construction
$cleanType = preg_replace('/[^a-zA-Z0-9]/', '', $type);
if ($cleanType == $type){
//set up the new shape and grab the existing shape (if there
//is one)
$newShape['type'] = $cleanType;
$existingShape = $shapes[$cleanType];
//For each of num, str, and txt, we need to find the first
//available slot for them.
for ($i = $numRange['start']; $i <= $numRange['end']; $i++){
$myString = 'my'.$curNum;
$testNum = $existingShape->$myString;
if (empty($testNum)){
$i += 1000;
}
else{
$curNum ++;
}
}
for ($i = $strRange['start']; $i <= $strRange['end']; $i++){
$myString = 'my'.$curStr;
$testStr = $existingShape->$myString;
if (empty($testStr)){
$i += 1000;
}
else{
$curStr ++;
}
}
for ($i = $txtRange['start']; $i <= $txtRange['end']; $i++){
$myString = 'my'.$curTxt;
$testNum = $existingShape->$myString;
if (empty($testNum)){
$i += 1000;
}
else{
$curTxt ++;
}
}
//The fun part: iterate through all of the my attributes
//in the content, checking all of them against the existing
//shape. If it's not there, we need to add it to the new shape
foreach (get_object_vars($this->my) as $attribute => $value){
$shapeValue = $existingShape->reverse->$attribute;
if (empty($shapeValue)){
if (is_numeric($value)){
if ( ($curNum >= $numRange['start']) &&
($curNum <= $numRange['end']) ){
$newShape['my'.$curNum] = $attribute;
$curNum ++;
}else{
die('_createShape error: too many numeric my attributes');
}
}elseif(strlen($value) < 255){
if ( ($curStr >= $strRange['start']) &&
($curStr <= $strRange['end']) ){
$newShape['my'.$curStr] = $attribute;
$curStr ++;
}else{
die('_createShape error: too many string my attributes');
}
}else{
if ( ($curTxt >= $txtRange['start']) &&
($curTxt <= $txtRange['end']) ){
$newShape['my'.$curTxt] = $attribute;
$curTxt ++;
}else{
die('_createShape error: too many text my attributes');
}
}
}
}
//generate sql from shape array
if ($action == 'create'){
$newShapeObj = new HDB_Shape($newShape);
$keys = implode(', ', array_keys($newShape));
$values = implode('", "', $newShape);
$sql = 'INSERT into '.$shapesTable.' ('.$keys.') VALUES ("'.$values.'")';
$shapeInsert = mysql_query($sql)
or die('INSERT FAILED: '.mysql_error());
$newShape = $this->_buildReverseArray($newShape);
$shapes[$newShape['type']] = $newShapeObj;
return TRUE;
}elseif($action == 'update'){
$sql = 'UPDATE '.$shapesTable.' SET ';
$existingShape = $shapes[$cleanType];
foreach ($newShape as $key => $value){
$setSql[] = $key.' = "'.$value.'"';
$existingShape->$key = $value;
$existingShape->reverse->$value = $key;
}
$shapes[$cleanType] = $existingShape;
$setSql = implode(', ', $setSql);
$sql .= $setSql;
$sql .= ' WHERE type = "'.$newShape['type'].'"';
$shapeInsert = mysql_query($sql)
or die('UPDATE FAILED: '.mysql_error());
return TRUE;
}
}
else{
die('_createShape: Bad type');
}
}
else{
die('_createShape: No type associated with shape');
}
}
//runs update sql (in the event that modifications to content are saved)
function _update($deserializedArray){
global $link, $shapes, $dbName, $contentTable;
//we don't let you change any of these - because that'd be stupid
//Also, you can't have a my attribute with any of these names
$protectedArray = array( 'id',
'createdDate',
'contributorName',
'type',
'updatedDate',
);
//Build SQL
mysql_select_db($dbName, $link) ;
$sql = 'UPDATE '.$contentTable.' ';
$sql .= 'SET updatedDate = '.time();
//iterate over set statements
foreach ($deserializedArray as $key => $value){
if ((!empty($value)) && (!in_array($key, $protectedArray))){
$value = mysql_real_escape_string($value, $link);
if (is_numeric($value)){
$sql .= ', '.$key.' = '.$value;
}else{
$sql .= ', '.$key.' = "'.$value.'"';
}
}
}
//set where
$sql .= ' WHERE id = '.$deserializedArray['id'];
//Execute SQL
mysql_query($sql)
or die('UPDATE FAILED: '.mysql_error());
}
//runs update sql
function _insert($deserializedArray){
global $link, $shapes, $dbName, $contentTable;
//You can't use any of these protected terms as my attributes
$protectedArray = array( 'id',
'createdDate',
'contributorName',
'type',
'updatedDate',
);
//Build SQL
mysql_select_db($dbName, $link) ;
$sql = 'INSERT INTO '.$contentTable.' ';
$columns = '(createdDate, updatedDate, contributorName, type';
$values = 'VALUES ('.time().', '.time().', "", "'.$deserializedArray['type'].'"';
//iterate over set statements
foreach ($deserializedArray as $key => $value){
if ((!empty($value)) && (!in_array($key, $protectedArray))){
$value = mysql_real_escape_string($value, $link);
$key = mysql_real_escape_string($key, $link);
$columns .= ', '.$key;
if (is_numeric($value)){
$values .= ', '.$value;
}else{
$values .= ', "'.$value.'"';
}
}
}
$columns .= ') ';
$values .= ')';
$sql .= $columns . $values;
//Execute SQL
mysql_query($sql)
or die('INSERT FAILED: '.mysql_error());
}
//returns an array fit to be inserted into SQL table
function _deserialize(){
$returnArray;
$shape = $this->_getShape();
//This shouldn't ever happen - it's really only there as a non-script
//breaking warning
if (empty($shape)){
echo "empty shape!!<br><Br>";
}
else{
//run verify shape to make sure the shape doesn't need any
//modification before deserialization
$this->_verifyShape($shape);
//populate top level object attributes
$returnArray['id'] = $this->id;
$returnArray['createdDate'] = $this->createdDate;
$returnArray['updatedDate'] = $this->updatedDate;
$returnArray['type'] = $this->type;
$returnArray['title'] = $this->title;
$returnArray['description'] = $this->description;
$returnArray['contributorName'] = $this->contributorName;
//iterate over my attributes
foreach (get_object_vars($this->my) as $attribute => $value){
//we're putting this into the database - so we need to get the
//correct database column name from the shape
$returnArray[$shape->reverse->$attribute] = $value;
}
}
return $returnArray;
}
//returns the shape associated with this object type
function _getShape(){
global $shapes;
$shape = $shapes[$this->type];
if (!empty($shape)){
return $shape;
}
}
//iterates over each my attribute of the content, and if the shape
//doesn't support any attribute, we update the shape
function _verifyShape($shape=FALSE){
//sometimes you're too lazy to get the shape first. Who are you to judge?
if (empty($shape)){
$shape = $this->_getShape();
}
//Check each my attribute - if it's not in the shape, we need to fix the shape
$updateFlag = 0;
foreach (get_object_vars($this->my) as $attribute => $value){
if (empty($shape->reverse->$attribute)){
$updateFlag = 1;
}
}
//we said we needed to update the shape - what better place then here?
//what better time than now?
if ($updateFlag == 1){
$this->_createShape('update');
}
}
//function to build a reverse array and add it to a shapes array
function _buildReverseArray($shape){
//iterate over shape array
foreach ($shape as $key => $value){
//I think this is to clear out createdDate and updatedDate. Also
//clears out any unset my attributes
if ((is_numeric($key)) || (empty($value))){
unset($shape[$key]);
}else{
//We only care about mappings to my attributes. Build the
//reverse array from these.
if (substr($key, 0, 2) == 'my'){
$shape['reverse'][$value] = $key;
}
}
}
return $shape;
}
}
/*
Copyright 2011 Oversee.net. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY OVERSEE.NET ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OVERSEE.NET OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Oversee.net.
*/
?>