00001 <?PHP 00002 00003 # 00004 # FILE: SPT--ItemFactory.php 00005 # 00006 # METHODS PROVIDED: 00007 # ItemFactory() 00008 # - constructor 00009 # Status() 00010 # - return current error status 00011 # GetLastTempItem() 00012 # - retrieve temp item based on user ID and last modified date 00013 # (returns NULL if no temp item found for that ID) 00014 # CleanOutStaleTempItems() 00015 # - clear out any temp items more than one week old 00016 # 00017 # NOTES: 00018 # - for a derived class to use the temp methods the item record in the 00019 # database must include "DateLastModified" and "LastModifiedById" 00020 # fields, and the item object must include a "Delete()" method 00021 # 00022 # AUTHOR: Edward Almasy 00023 # 00024 # Part of the Collection Workflow Integration System 00025 # Copyright 2007 Edward Almasy and Internet Scout 00026 # http://scout.wisc.edu 00027 # 00028 00029 00030 class ItemFactory { 00031 00032 # ---- PUBLIC INTERFACE -------------------------------------------------- 00033 00034 # object constructor 00035 function ItemFactory($ItemClassName, $ItemTableName, $ItemIdFieldName, 00036 $ItemNameFieldName = NULL, $FieldId = NULL, $OrderOpsAllowed = FALSE) 00037 { 00038 # save item access names 00039 $this->ItemClassName = $ItemClassName; 00040 $this->ItemTableName = $ItemTableName; 00041 $this->ItemIdFieldName = $ItemIdFieldName; 00042 $this->ItemNameFieldName = $ItemNameFieldName; 00043 00044 # save field ID (if specified) 00045 if ($FieldId !== NULL) { $this->FieldId = intval($FieldId); } 00046 00047 # save flag indicating whether item type allows ordering operations 00048 $this->OrderOpsAllowed = $OrderOpsAllowed; 00049 if ($OrderOpsAllowed) 00050 { 00051 $this->OrderList = new DoublyLinkedItemList( 00052 $ItemTableName, $ItemIdFieldName); 00053 $this->SetOrderOpsCondition(NULL); 00054 } 00055 00056 # grab our own database handle 00057 $this->DB = new SPTDatabase(); 00058 00059 # assume everything will be okay 00060 $this->ErrorStatus = 0; 00061 } 00062 00063 # return current error status 00064 function Status() { return $this->ErrorStatus; } 00065 00066 # get ID of currently edited item 00067 function GetCurrentEditedItemId() 00068 { 00069 # if ID available in session variable 00070 global $Session; 00071 if ($EditedIds = $Session->Get($this->ItemClassName."EditedIds")) 00072 { 00073 # look up value in session variable 00074 $ItemId = $EditedIds[0]; 00075 } 00076 else 00077 { 00078 # attempt to look up last temp item ID 00079 $ItemId = $this->GetLastTempItemId(); 00080 00081 # store it in session variable 00082 $EditedIds = array($ItemId); 00083 $Session->RegisterVariable($this->ItemClassName."EditedIds", $EditedIds); 00084 } 00085 00086 # return ID (if any) to caller 00087 return $ItemId; 00088 } 00089 00090 # set ID of currently edited item 00091 function SetCurrentEditedItemId($NewId) 00092 { 00093 # if edited ID array already stored for session 00094 global $Session; 00095 if ($EditedIds = $Session->Get($this->ItemClassName."EditedIds")) 00096 { 00097 # prepend new value to array 00098 array_unshift($EditedIds, $NewId); 00099 } 00100 else 00101 { 00102 # start with fresh array 00103 $EditedIds = array($NewId); 00104 } 00105 00106 # save in session variable 00107 $Session->RegisterVariable($this->ItemClassName."EditedIds", $EditedIds); 00108 } 00109 00110 # clear currently edited item ID 00111 function ClearCurrentEditedItemId() 00112 { 00113 # if edited item IDs available in a session variable 00114 global $Session; 00115 $SessionVarName = $this->ItemClassName."EditedIds"; 00116 if ($EditedIds = $Session->Get($SessionVarName)) 00117 { 00118 # remove current item from edited item ID array 00119 array_shift($EditedIds); 00120 00121 # if no further edited items 00122 if (count($EditedIds) < 1) 00123 { 00124 # destroy session variable 00125 $Session->UnregisterVariable($SessionVarName); 00126 } 00127 else 00128 { 00129 # save new shorter edited item ID array to session variable 00130 $Session->RegisterVariable($SessionVarName, $EditedIds); 00131 } 00132 } 00133 } 00134 00135 # clear currently edited item ID and item 00136 function ClearCurrentEditedItem() 00137 { 00138 # if current edited item is temp item 00139 $CurrentEditedItemId = $this->GetCurrentEditedItemId(); 00140 if ($CurrentEditedItemId < 0) 00141 { 00142 # delete temp item from DB 00143 $this->DB->Query("DELETE FROM ".$this->ItemTableName 00144 ." WHERE ".$this->ItemIdFieldName." = ".$CurrentEditedItemId); 00145 } 00146 00147 # clear current edited item ID 00148 $this->ClearCurrentEditedItemId(); 00149 } 00150 00151 # clear out any temp items more than one week old 00152 function CleanOutStaleTempItems() 00153 { 00154 # load array of stale items 00155 $this->DB->Query("SELECT ".$this->ItemIdFieldName." FROM ".$this->ItemTableName 00156 ." WHERE ".$this->ItemIdFieldName." < 0" 00157 ." AND DateLastModified < DATE_SUB(NOW(), INTERVAL 7 DAY)"); 00158 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName); 00159 00160 # delete stale items 00161 foreach ($ItemIds as $ItemId) 00162 { 00163 $Item = new $this->ItemClassName($ItemId); 00164 $Item->Delete(); 00165 } 00166 } 00167 00168 # retrieve most recent temp item ID based on user ID 00169 # (returns NULL if no temp item found for that user ID) 00170 function GetLastTempItemId() 00171 { 00172 # retrieve ID of most recently modified temp item for this user 00173 global $User; 00174 $ItemId = $this->DB->Query("SELECT ".$this->ItemIdFieldName." FROM ".$this->ItemTableName 00175 ." WHERE LastModifiedById = '".$User->Get("UserId")."'" 00176 ." AND ".$this->ItemIdFieldName." < 0" 00177 ." ORDER BY ".$this->ItemIdFieldName." ASC" 00178 ." LIMIT 1", 00179 $this->ItemIdFieldName); 00180 00181 # return item to caller (or NULL if none found) 00182 return $ItemId; 00183 } 00184 00185 # return next item ID 00186 function GetNextItemId() 00187 { 00188 # if no highest item ID found 00189 $HighestItemId = $this->GetHighestItemId(); 00190 if ($HighestItemId <= 0) 00191 { 00192 # start with item ID 1 00193 $ItemId = 1; 00194 } 00195 else 00196 { 00197 # else use next ID available after highest 00198 $ItemId = $HighestItemId + 1; 00199 } 00200 00201 # return next ID to caller 00202 return $ItemId; 00203 } 00204 00205 # return highest item ID ($Condition should not include "WHERE") 00206 function GetHighestItemId($Condition = NULL, $IncludeTempItems = FALSE) 00207 { 00208 # if temp items are supposed to be included 00209 if ($IncludeTempItems) 00210 { 00211 # condition is only as supplied 00212 $ConditionString = ($Condition == NULL) ? "" : " WHERE ".$Condition; 00213 } 00214 else 00215 { 00216 # condition is non-negative IDs plus supplied condition 00217 $ConditionString = " WHERE ".$this->ItemIdFieldName." >= 0" 00218 .(($Condition == NULL) ? "" : " AND ".$Condition); 00219 } 00220 00221 # return highest item ID to caller 00222 return $this->DB->Query("SELECT ".$this->ItemIdFieldName 00223 ." FROM ".$this->ItemTableName 00224 .$ConditionString 00225 ." ORDER BY ".$this->ItemIdFieldName 00226 ." DESC LIMIT 1", 00227 $this->ItemIdFieldName); 00228 } 00229 00230 # return next temp item ID 00231 function GetNextTempItemId() 00232 { 00233 $LowestItemId = $this->DB->Query("SELECT ".$this->ItemIdFieldName 00234 ." FROM ".$this->ItemTableName 00235 ." ORDER BY ".$this->ItemIdFieldName 00236 ." ASC LIMIT 1", 00237 $this->ItemIdFieldName); 00238 if ($LowestItemId > 0) 00239 { 00240 $ItemId = -1; 00241 } 00242 else 00243 { 00244 $ItemId = $LowestItemId - 1; 00245 } 00246 return $ItemId; 00247 } 00248 00249 # return count of items 00250 function GetItemCount($Condition = NULL, $IncludeTempItems = FALSE) 00251 { 00252 # if condition was supplied 00253 if ($Condition != NULL) 00254 { 00255 # use condition 00256 $ConditionString = " WHERE ".$Condition; 00257 } 00258 else 00259 { 00260 # if field ID is available 00261 if (isset($this->FieldId)) 00262 { 00263 # use condition for matching field ID 00264 $ConditionString = " WHERE FieldId = ".intval($this->FieldId); 00265 } 00266 else 00267 { 00268 # use no condition 00269 $ConditionString = ""; 00270 } 00271 } 00272 00273 # if temp items are to be excluded 00274 if (!$IncludeTempItems) 00275 { 00276 # if a condition was previously set 00277 if (strlen($ConditionString)) 00278 { 00279 # add in condition to exclude temp items 00280 $ConditionString .= " AND (".$this->ItemIdFieldName." >= 0)"; 00281 } 00282 else 00283 { 00284 # use condition to exclude temp items 00285 $ConditionString = " WHERE ".$this->ItemIdFieldName." >= 0"; 00286 } 00287 } 00288 00289 # retrieve item count 00290 $Count = $this->DB->Query("SELECT COUNT(*) AS RecordCount" 00291 ." FROM ".$this->ItemTableName 00292 .$ConditionString, 00293 "RecordCount"); 00294 00295 # return count to caller 00296 return $Count; 00297 } 00298 00299 # return array of item IDs ($Condition should not include "WHERE") 00300 function GetItemIds($Condition = NULL, $IncludeTempItems = FALSE) 00301 { 00302 # if temp items are supposed to be included 00303 if ($IncludeTempItems) 00304 { 00305 # condition is only as supplied 00306 $ConditionString = ($Condition == NULL) ? "" : " WHERE ".$Condition; 00307 } 00308 else 00309 { 00310 # condition is non-negative IDs plus supplied condition 00311 $ConditionString = " WHERE ".$this->ItemIdFieldName." >= 0" 00312 .(($Condition == NULL) ? "" : " AND ".$Condition); 00313 } 00314 00315 # get item IDs 00316 $this->DB->Query("SELECT ".$this->ItemIdFieldName 00317 ." FROM ".$this->ItemTableName 00318 .$ConditionString); 00319 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName); 00320 00321 # return IDs to caller 00322 return $ItemIds; 00323 } 00324 00325 # return latest modification date ($Condition should not include "WHERE") 00326 function GetLatestModificationDate($Condition = NULL) 00327 { 00328 # return modification date for item most recently changed 00329 $ConditionString = ($Condition == NULL) ? "" : " WHERE ".$Condition; 00330 return $this->DB->Query("SELECT MAX(DateLastModified) AS LastChangeDate" 00331 ." FROM ".$this->ItemTableName.$ConditionString, 00332 "LastChangeDate"); 00333 } 00334 00335 # retrieve item by item ID 00336 function GetItem($ItemId) 00337 { 00338 return new $this->ItemClassName($ItemId); 00339 } 00340 00341 # retrieve item by name 00342 function GetItemByName($Name, $IgnoreCase = FALSE) 00343 { 00344 # error out if this is an illegal operation for this item type 00345 if ($this->ItemNameFieldName == NULL) 00346 { 00347 exit("<br>SPT - ERROR: attempt to get item by name on item type" 00348 ."(".$this->ItemClassName.") that has no name field specified<br>\n"); 00349 } 00350 00351 # query database for item ID 00352 $Comparison = $IgnoreCase 00353 ? "LOWER(".$this->ItemNameFieldName.") = '" 00354 .addslashes(strtolower($Name))."'" 00355 : $this->ItemNameFieldName." = '" .addslashes($Name)."'"; 00356 $ItemId = $this->DB->Query("SELECT ".$this->ItemIdFieldName 00357 ." FROM ".$this->ItemTableName 00358 ." WHERE ".$Comparison 00359 .(isset($this->FieldId) 00360 ? " AND FieldId = ".$this->FieldId 00361 : ""), 00362 $this->ItemIdFieldName); 00363 00364 # if item ID was not found 00365 if ($ItemId === NULL) 00366 { 00367 # return NULL to caller 00368 $Item = NULL; 00369 } 00370 else 00371 { 00372 # generate new item object 00373 $Item = $this->GetItem($ItemId); 00374 } 00375 00376 # return new object to caller 00377 return $Item; 00378 } 00379 00380 # retrieve array of item names indexed by IDs and sorted by name 00381 function GetItemNames() 00382 { 00383 # error out if this is an illegal operation for this item type 00384 if ($this->ItemNameFieldName == NULL) 00385 { 00386 exit("<br>SPT - ERROR: attempt to get array of item names on item type" 00387 ."(".$this->ItemClassName.") that has no name field specified<br>\n"); 00388 } 00389 00390 # query database for item names 00391 $this->DB->Query("SELECT ".$this->ItemIdFieldName 00392 .", ".$this->ItemNameFieldName 00393 ." FROM ".$this->ItemTableName 00394 .(isset($this->FieldId) 00395 ? " WHERE FieldId = ".$this->FieldId 00396 : "") 00397 ." ORDER BY ".$this->ItemNameFieldName); 00398 $Names = $this->DB->FetchColumn("Name", "Id"); 00399 00400 # return item names to caller 00401 return $Names; 00402 } 00403 00404 # retrieve names of items matching search string (array index is IDs) 00405 # (NOTE: IncludeVariants parameter is NOT YET SUPPORTED!) 00406 function SearchForItemNames($SearchString, $NumberOfResults = 100, 00407 $IncludeVariants = FALSE, $UseBooleanMode = TRUE) 00408 { 00409 # error out if this is an illegal operation for this item type 00410 if ($this->ItemNameFieldName == NULL) 00411 { 00412 exit("<br>SPT - ERROR: attempt to search for item names on item type" 00413 ."(".$this->ItemClassName.") that has no name field specified<br>\n"); 00414 } 00415 00416 # return no results if empty search string passed in 00417 if (!strlen(trim($SearchString))) { return array(); } 00418 00419 # construct SQL query 00420 $QueryString = "SELECT ".$this->ItemIdFieldName.",".$this->ItemNameFieldName 00421 ." FROM ".$this->ItemTableName." WHERE"; 00422 if ($this->FieldId) 00423 { 00424 $QueryString .= " FieldId = ".$this->FieldId." AND"; 00425 } 00426 if ($UseBooleanMode) 00427 { 00428 $SearchString = preg_replace("/[)\(><]+/", "", $SearchString); 00429 $Words = preg_split("/[\s]+/", trim($SearchString)); 00430 $NewSearchString = ""; 00431 $InQuotedString = FALSE; 00432 foreach ($Words as $Word) 00433 { 00434 if ($InQuotedString == FALSE) { $NewSearchString .= "+"; } 00435 if (preg_match("/^\"/", $Word)) { $InQuotedString = TRUE; } 00436 if (preg_match("/\"$/", $Word)) { $InQuotedString = FALSE; } 00437 $NewSearchString .= $Word." "; 00438 } 00439 $QueryString .= " MATCH (".$this->ItemNameFieldName.")" 00440 ." AGAINST ('".addslashes(trim($NewSearchString))."'" 00441 ." IN BOOLEAN MODE)"; 00442 } 00443 else 00444 { 00445 $QueryString .= " MATCH (".$this->ItemNameFieldName.")" 00446 ." AGAINST ('".addslashes(trim($SearchString))."')"; 00447 } 00448 $QueryString .= " LIMIT ".$NumberOfResults; 00449 00450 # perform query and retrieve names and IDs of items found by query 00451 $DB = new SPTDatabase(); 00452 $DB->Query($QueryString); 00453 $Names = $DB->FetchColumn($this->ItemNameFieldName, $this->ItemIdFieldName); 00454 00455 # return names to caller 00456 return $Names; 00457 } 00458 00467 function AddItems($ItemNames, $Qualifier = NULL) 00468 { 00469 # for each supplied item name 00470 $ItemCount = 0; 00471 foreach ($ItemNames as $Name) 00472 { 00473 # if item does not exist with this name 00474 $Name = trim($Name); 00475 if ($this->GetItemByName($Name) === NULL) 00476 { 00477 # add item 00478 $NewItem = new $this->ItemClassName(NULL, $Name, $this->FieldId); 00479 $ItemCount++; 00480 00481 # assign qualifier to item if supplied 00482 if ($Qualifier !== NULL) 00483 { 00484 $NewItem->Qualifier($Qualifier); 00485 } 00486 } 00487 } 00488 00489 # return count of items added to caller 00490 return $ItemCount; 00491 } 00492 00493 # ---- order operations -------------------------------------------------- 00494 00495 # set SQL condition (added to WHERE clause) used to select items for ordering ops 00496 # (use NULL to clear any previous condition) 00497 function SetOrderOpsCondition($Condition) 00498 { 00499 # condition is non-negative IDs (non-temp items) plus supplied condition 00500 $NewCondition = $this->ItemIdFieldName." >= 0" 00501 .(($Condition) ? " AND ".$Condition : ""); 00502 $this->OrderList->SqlCondition($NewCondition); 00503 } 00504 00505 # insert/move item to before specified item 00506 function InsertBefore($SourceItemOrItemId, $TargetItemOrItemId) 00507 { 00508 # error out if ordering operations are not allowed for this item type 00509 if (!$this->OrderOpsAllowed) 00510 { 00511 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00512 ." (InsertBefore()) on item type" 00513 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00514 } 00515 00516 # insert/move item 00517 $this->OrderList->InsertBefore($SourceItemOrItemId, $TargetItemOrItemId); 00518 } 00519 00520 # insert/move item to after specified item 00521 function InsertAfter($SourceItemOrItemId, $TargetItemOrItemId) 00522 { 00523 # error out if ordering operations are not allowed for this item type 00524 if (!$this->OrderOpsAllowed) 00525 { 00526 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00527 ." (InsertAfter()) on item type" 00528 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00529 } 00530 00531 # insert/move item 00532 $this->OrderList->InsertAfter($SourceItemOrItemId, $TargetItemOrItemId); 00533 } 00534 00535 # add/move item to beginning of list 00536 function Prepend($ItemOrItemId) 00537 { 00538 # error out if ordering operations are not allowed for this item type 00539 if (!$this->OrderOpsAllowed) 00540 { 00541 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00542 ." (Prepend()) on item type" 00543 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00544 } 00545 00546 # prepend item 00547 $this->OrderList->Prepend($ItemOrItemId); 00548 } 00549 00550 # add/move item to end of list 00551 function Append($ItemOrItemId) 00552 { 00553 # error out if ordering operations are not allowed for this item type 00554 if (!$this->OrderOpsAllowed) 00555 { 00556 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00557 ." (Append()) on item type" 00558 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00559 } 00560 00561 # add/move item 00562 $this->OrderList->Append($ItemOrItemId); 00563 } 00564 00565 # retrieve list of item IDs in order 00566 function GetItemIdsInOrder($AddStrayItemsToOrder = TRUE) 00567 { 00568 # error out if ordering operations are not allowed for this item type 00569 if (!$this->OrderOpsAllowed) 00570 { 00571 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00572 ." (GetItemIdsInOrder()) on item type" 00573 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00574 } 00575 00576 # retrieve list of IDs 00577 return $this->OrderList->GetIds($AddStrayItemsToOrder); 00578 } 00579 00580 # remove item from existing order 00581 function RemoveItemFromOrder($ItemId) 00582 { 00583 # error out if ordering operations are not allowed for this item type 00584 if (!$this->OrderOpsAllowed) 00585 { 00586 exit("<br>SPT - ERROR: attempt to perform ordering operation" 00587 ." (RemoveItemFromOrder()) on item type" 00588 ."(".$this->ItemClassName.") that does not support ordering<br>\n"); 00589 } 00590 00591 # remove item 00592 $this->OrderList->Remove($ItemId); 00593 } 00594 00595 00596 # ---- PRIVATE INTERFACE ------------------------------------------------- 00597 00598 var $ItemClassName; 00599 var $ItemTableName; 00600 var $ItemIdFieldName; 00601 var $ItemNameFieldName; 00602 var $DB; 00603 var $ErrorStatus; 00604 var $FieldId; 00605 var $OrderOpsAllowed; 00606 var $OrderList; 00607 00608 # get/set ordering values 00609 function GetPreviousItemId($ItemId) 00610 { 00611 return $this->DB->Query("SELECT Previous".$this->ItemIdFieldName 00612 ." FROM ".$this->ItemTableName 00613 ." WHERE ".$this->ItemIdFieldName." = ".intval($ItemId), 00614 "Previous".$this->ItemIdFieldName); 00615 } 00616 function GetNextItemIdInOrder($ItemId) 00617 { 00618 return $this->DB->Query("SELECT Next".$this->ItemIdFieldName 00619 ." FROM ".$this->ItemTableName 00620 ." WHERE ".$this->ItemIdFieldName." = ".intval($ItemId), 00621 "Next".$this->ItemIdFieldName); 00622 } 00623 function SetPreviousItemId($ItemId, $NewValue) 00624 { 00625 $this->DB->Query("UPDATE ".$this->ItemTableName 00626 ." SET Previous".$this->ItemIdFieldName." = ".intval($NewValue) 00627 ." WHERE ".$this->ItemIdFieldName." = ".intval($ItemId)); 00628 } 00629 function SetNextItemId($ItemId, $NewValue) 00630 { 00631 $this->DB->Query("UPDATE ".$this->ItemTableName 00632 ." SET Next".$this->ItemIdFieldName." = ".intval($NewValue) 00633 ." WHERE ".$this->ItemIdFieldName." = ".intval($ItemId)); 00634 } 00635 function SetPreviousAndNextItemIds($ItemId, $NewPreviousId, $NewNextId) 00636 { 00637 $this->DB->Query("UPDATE ".$this->ItemTableName 00638 ." SET Previous".$this->ItemIdFieldName." = ".intval($NewPreviousId) 00639 .", Next".$this->ItemIdFieldName." = ".intval($NewNextId) 00640 ." WHERE ".$this->ItemIdFieldName." = ".intval($ItemId)); 00641 } 00642 } 00643 00644 00645 ?>