4 # FILE: ItemFactory.php
7 # - for a derived class to use the temp methods the item record in the
8 # database must include "DateLastModified" and "LastModifiedById"
9 # fields, and the item object must include a "Delete()" method
11 # Copyright 2007-2010 Edward Almasy and Internet Scout
12 # http://scout.wisc.edu
17 # ---- PUBLIC INTERFACE --------------------------------------------------
20 function ItemFactory($ItemClassName, $ItemTableName, $ItemIdFieldName,
21 $ItemNameFieldName = NULL,
$FieldId = NULL, $OrderOpsAllowed = FALSE)
23 # save item access names
24 $this->ItemClassName = $ItemClassName;
25 $this->ItemTableName = $ItemTableName;
26 $this->ItemIdFieldName = $ItemIdFieldName;
27 $this->ItemNameFieldName = $ItemNameFieldName;
29 # save field ID (if specified)
32 # save flag indicating whether item type allows ordering operations
33 $this->OrderOpsAllowed = $OrderOpsAllowed;
37 $ItemTableName, $ItemIdFieldName);
41 # grab our own database handle
44 # assume everything will be okay
45 $this->ErrorStatus = 0;
48 # return current error status
49 function Status() {
return $this->ErrorStatus; }
51 # get ID of currently edited item
54 # if ID available in session variable
56 if ($EditedIds = $Session->Get($this->ItemClassName.
"EditedIds"))
58 # look up value in session variable
59 $ItemId = $EditedIds[0];
63 # attempt to look up last temp item ID
66 # store it in session variable
67 $EditedIds = array($ItemId);
68 $Session->RegisterVariable($this->ItemClassName.
"EditedIds", $EditedIds);
71 # return ID (if any) to caller
75 # set ID of currently edited item
78 # if edited ID array already stored for session
80 if ($EditedIds = $Session->Get($this->ItemClassName.
"EditedIds"))
82 # prepend new value to array
83 array_unshift($EditedIds, $NewId);
87 # start with fresh array
88 $EditedIds = array($NewId);
91 # save in session variable
92 $Session->RegisterVariable($this->ItemClassName.
"EditedIds", $EditedIds);
95 # clear currently edited item ID
98 # if edited item IDs available in a session variable
100 $SessionVarName = $this->ItemClassName.
"EditedIds";
101 if ($EditedIds = $Session->Get($SessionVarName))
103 # remove current item from edited item ID array
104 array_shift($EditedIds);
106 # if no further edited items
107 if (count($EditedIds) < 1)
109 # destroy session variable
110 $Session->UnregisterVariable($SessionVarName);
114 # save new shorter edited item ID array to session variable
115 $Session->RegisterVariable($SessionVarName, $EditedIds);
120 # clear currently edited item ID and item
123 # if current edited item is temp item
125 if ($CurrentEditedItemId < 0)
127 # delete temp item from DB
128 $this->DB->Query(
"DELETE FROM ".$this->ItemTableName
129 .
" WHERE ".$this->ItemIdFieldName.
" = ".$CurrentEditedItemId);
132 # clear current edited item ID
145 # load array of stale items
146 $MinutesUntilStale = max($MinutesUntilStale, 1);
147 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" FROM ".$this->ItemTableName
148 .
" WHERE ".$this->ItemIdFieldName.
" < 0"
149 .
" AND DateLastModified < DATE_SUB(NOW(), "
150 .
" INTERVAL ".intval($MinutesUntilStale).
" MINUTE)");
151 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName);
154 foreach ($ItemIds as $ItemId)
156 $Item =
new $this->ItemClassName($ItemId);
160 # report number of items deleted to caller
161 return count($ItemIds);
164 # retrieve most recent temp item ID based on user ID
165 # (returns NULL if no temp item found for that user ID)
168 # retrieve ID of most recently modified temp item for this user
170 $ItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" FROM ".$this->ItemTableName
171 .
" WHERE LastModifiedById = '".$User->Get(
"UserId").
"'"
172 .
" AND ".$this->ItemIdFieldName.
" < 0"
173 .
" ORDER BY ".$this->ItemIdFieldName.
" ASC"
175 $this->ItemIdFieldName);
177 # return item to caller (or NULL if none found)
181 # return next item ID
184 # if no highest item ID found
186 if ($HighestItemId <= 0)
188 # start with item ID 1
193 # else use next ID available after highest
194 $ItemId = $HighestItemId + 1;
197 # return next ID to caller
201 # return highest item ID ($Condition should not include "WHERE")
204 # if temp items are supposed to be included
205 if ($IncludeTempItems)
207 # condition is only as supplied
208 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
212 # condition is non-negative IDs plus supplied condition
213 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0"
214 .(($Condition == NULL) ?
"" :
" AND ".$Condition);
217 # return highest item ID to caller
218 return $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
219 .
" FROM ".$this->ItemTableName
221 .
" ORDER BY ".$this->ItemIdFieldName
223 $this->ItemIdFieldName);
226 # return next temp item ID
229 $LowestItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
230 .
" FROM ".$this->ItemTableName
231 .
" ORDER BY ".$this->ItemIdFieldName
233 $this->ItemIdFieldName);
234 if ($LowestItemId > 0)
240 $ItemId = $LowestItemId - 1;
245 # return count of items
248 # if condition was supplied
249 if ($Condition != NULL)
252 $ConditionString =
" WHERE ".$Condition;
256 # if field ID is available
257 if (isset($this->FieldId))
259 # use condition for matching field ID
260 $ConditionString =
" WHERE FieldId = ".intval($this->FieldId);
265 $ConditionString =
"";
269 # if temp items are to be excluded
270 if (!$IncludeTempItems)
272 # if a condition was previously set
273 if (strlen($ConditionString))
275 # add in condition to exclude temp items
276 $ConditionString .=
" AND (".$this->ItemIdFieldName.
" >= 0)";
280 # use condition to exclude temp items
281 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0";
285 # retrieve item count
286 $Count = $this->DB->Query(
"SELECT COUNT(*) AS RecordCount"
287 .
" FROM ".$this->ItemTableName
291 # return count to caller
295 # return array of item IDs ($Condition should not include "WHERE")
296 function GetItemIds($Condition = NULL, $IncludeTempItems = FALSE)
298 # if temp items are supposed to be included
299 if ($IncludeTempItems)
301 # condition is only as supplied
302 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
306 # condition is non-negative IDs plus supplied condition
307 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0"
308 .(($Condition == NULL) ?
"" :
" AND ".$Condition);
312 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
313 .
" FROM ".$this->ItemTableName
315 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName);
317 # return IDs to caller
321 # return latest modification date ($Condition should not include "WHERE")
324 # return modification date for item most recently changed
325 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
326 return $this->DB->Query(
"SELECT MAX(DateLastModified) AS LastChangeDate"
327 .
" FROM ".$this->ItemTableName.$ConditionString,
331 # retrieve item by item ID
334 return new $this->ItemClassName($ItemId);
343 return $this->DB->Query(
"SELECT COUNT(*) AS ItemCount"
344 .
" FROM ".$this->ItemTableName
345 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId),
"ItemCount")
349 # retrieve item by name
352 # error out if this is an illegal operation for this item type
353 if ($this->ItemNameFieldName == NULL)
355 exit(
"<br>ERROR: attempt to get item by name on item type"
356 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
359 # query database for item ID
360 $Comparison = $IgnoreCase
361 ?
"LOWER(".$this->ItemNameFieldName.
") = '"
362 .addslashes(strtolower($Name)).
"'"
363 : $this->ItemNameFieldName.
" = '" .addslashes($Name).
"'";
364 $ItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
365 .
" FROM ".$this->ItemTableName
366 .
" WHERE ".$Comparison
367 .(isset($this->FieldId)
368 ?
" AND FieldId = ".$this->FieldId
370 $this->ItemIdFieldName);
372 # if item ID was not found
373 if ($ItemId === NULL)
375 # return NULL to caller
380 # generate new item object
381 $Item = $this->
GetItem($ItemId);
384 # return new object to caller
395 # error out if this is an illegal operation for this item type
396 if ($this->ItemNameFieldName == NULL)
398 exit(
"<br>ERROR: attempt to get array of item names on item type"
399 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
402 # query database for item names
404 if ($this->FieldId || $SqlCondition)
406 $Condition =
"WHERE ";
408 $Condition .=
"FieldId = ".intval($this->FieldId);
409 if ($this->FieldId && $SqlCondition)
410 $Condition .=
" AND ";
412 $Condition .= $SqlCondition;
414 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
415 .
", ".$this->ItemNameFieldName
416 .
" FROM ".$this->ItemTableName.
" "
418 .
" ORDER BY ".$this->ItemNameFieldName);
419 $Names = $this->DB->FetchColumn(
420 $this->ItemNameFieldName, $this->ItemIdFieldName);
422 # return item names to caller
435 foreach ($Names as $Id => $Name)
457 $SqlCondition = NULL, $DisplaySize = 1, $SubmitOnChange = FALSE)
459 # retrieve requested fields
462 # if multiple selections are allowed
463 if ($DisplaySize > 1)
465 # begin multi-selection HTML option list
466 $Html =
"<select name=\"".htmlspecialchars($OptionListName).
"[]\""
467 .($SubmitOnChange ?
" onChange=\"submit()\"" :
"")
468 .
" multiple=\"multiple\" size=\"".$DisplaySize.
"\">\n";
472 # begin single-selection HTML option list
473 $Html =
"<select name=\"".htmlspecialchars($OptionListName).
"\""
474 .($SubmitOnChange ?
" onChange=\"submit()\"" :
"")
476 $Html .=
"<option value=\"-1\">--</option>\n";
479 # for each metadata field
480 foreach ($ItemNames as $Id => $Name)
482 # add entry for field to option list
483 $Html .=
"<option value=\"".$Id.
"\"";
484 if (($Id == $SelectedItemId)
485 || (is_array($SelectedItemId) && in_array($Id, $SelectedItemId)))
487 $Html .=
" selected";
489 $Html .=
">".htmlspecialchars($Name).
"</option>\n";
492 # end HTML option list
493 $Html .=
"</select>\n";
495 # return constructed HTML to caller
506 $Condition = $IgnoreCase
507 ?
"LOWER(".$this->ItemNameFieldName.
")"
508 .
" = '".addslashes(strtolower($Name)).
"'"
509 : $this->ItemNameFieldName.
" = '".addslashes($Name).
"'";
510 $NameCount = $this->DB->Query(
"SELECT COUNT(*) AS RecordCount FROM "
511 .$this->ItemTableName.
" WHERE ".$Condition,
"RecordCount");
512 return ($NameCount > 0) ? TRUE : FALSE;
515 # retrieve names of items matching search string (array index is IDs)
516 # (NOTE: IncludeVariants parameter is NOT YET SUPPORTED!)
518 $IncludeVariants = FALSE, $UseBooleanMode = TRUE, $Offset=0)
520 # error out if this is an illegal operation for this item type
521 if ($this->ItemNameFieldName == NULL)
523 exit(
"<br>ERROR: attempt to search for item names on item type"
524 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
527 # return no results if empty search string passed in
528 if (!strlen(trim($SearchString))) {
return array(); }
530 # construct SQL query
532 $QueryString =
"SELECT ".$this->ItemIdFieldName.
",".$this->ItemNameFieldName
533 .
" FROM ".$this->ItemTableName.
" WHERE";
536 $QueryString .=
" FieldId = ".$this->FieldId.
" AND";
540 $SearchString = preg_replace(
"/[)\(><]+/",
"", $SearchString);
541 $Words = preg_split(
"/[\s]+/", trim($SearchString));
542 $NewSearchString =
"";
543 $InQuotedString = FALSE;
545 $StopWordList = $SqlVarObj->GetStopWords();
546 $MinWordLen = $SqlVarObj->Get(
"ft_min_word_len");
547 foreach ($Words as $Word)
549 # remove any query-specific terms, punctuation, etc.
550 $JustTheWord = preg_replace(
"/[^a-zA-Z-]/",
"", $Word);
552 # require (boolean AND) certain words
553 if ($InQuotedString == FALSE
554 && !in_array($JustTheWord, $StopWordList)
555 && strlen($JustTheWord) >= $MinWordLen
559 $NewSearchString .=
"+";
562 if (preg_match(
"/^\"/", $Word)) { $InQuotedString = TRUE; }
563 if (preg_match(
"/\"$/", $Word)) { $InQuotedString = FALSE; }
564 $NewSearchString .= $Word.
" ";
567 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
568 .
" AGAINST ('".addslashes(trim($NewSearchString)).
"'"
569 .
" IN BOOLEAN MODE)";
573 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
574 .
" AGAINST ('".addslashes(trim($SearchString)).
"')";
576 $QueryString .=
" LIMIT ".intval($NumberOfResults).
" OFFSET "
579 # perform query and retrieve names and IDs of items found by query
580 $DB->Query($QueryString);
581 $Names =
$DB->FetchColumn($this->ItemNameFieldName, $this->ItemIdFieldName);
585 foreach ($Words as $Word)
587 $TgtWord = preg_replace(
"/[^a-zA-Z]/",
"", $Word);
588 if ($Word{0} ==
"-" && strlen($TgtWord) < $MinWordLen)
591 foreach ($Names as $Id => $Name)
593 if (! preg_match(
'/\b'.$TgtWord.
'/i', $Name))
595 $NewNames[$Id] = $Name;
603 # return names to caller
607 # retrieve the count of names of items matching search string (array index
608 # is IDs) (NOTE: IncludeVariants parameter is NOT YET SUPPORTED!)
610 $UseBooleanMode = TRUE)
612 # return no results if empty search string passed in
613 if (!strlen(trim($SearchString))) {
return 0; }
615 # construct SQL query
617 $QueryString =
"SELECT COUNT(*) as ItemCount FROM "
618 .$this->ItemTableName.
" WHERE";
621 $QueryString .=
" FieldId = ".$this->FieldId.
" AND";
625 $SearchString = preg_replace(
"/[)\(><]+/",
"", $SearchString);
626 $Words = preg_split(
"/[\s]+/", trim($SearchString));
627 $NewSearchString =
"";
628 $InQuotedString = FALSE;
630 $StopWordList = $SqlVarObj->GetStopWords();
631 $MinWordLen = $SqlVarObj->Get(
"ft_min_word_len");
632 foreach ($Words as $Word)
634 # remove any query-specific terms, punctuation, etc.
635 $JustTheWord = preg_replace(
"/[^a-zA-Z-]/",
"", $Word);
637 # require (boolean AND) certain words
638 if ($InQuotedString == FALSE
639 && !in_array($JustTheWord, $StopWordList)
640 && strlen($JustTheWord) >= $MinWordLen
644 $NewSearchString .=
"+";
647 if (preg_match(
"/^\"/", $Word)) { $InQuotedString = TRUE; }
648 if (preg_match(
"/\"$/", $Word)) { $InQuotedString = FALSE; }
649 $NewSearchString .= $Word.
" ";
652 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
653 .
" AGAINST ('".addslashes(trim($NewSearchString)).
"'"
654 .
" IN BOOLEAN MODE)";
658 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
659 .
" AGAINST ('".addslashes(trim($SearchString)).
"')";
662 # perform query and retrieve names and IDs of items found by query
663 $DB->Query($QueryString);
664 return intval(
$DB->FetchField(
"ItemCount"));
677 # for each supplied item name
679 foreach ($ItemNames as $Name)
681 # if item does not exist with this name
686 $NewItem =
new $this->ItemClassName(NULL, $Name, $this->FieldId);
689 # assign qualifier to item if supplied
690 if ($Qualifier !== NULL)
692 $NewItem->Qualifier($Qualifier);
697 # return count of items added to caller
709 function AddItem($ItemName, $AdditionalValues = NULL)
711 # build initial database query for adding item
712 $Query =
"INSERT INTO ".$this->ItemTableName.
" SET `"
713 .$this->ItemNameFieldName.
"` = '".addslashes($ItemName).
"'";
715 # add any additional values to query
716 if ($AdditionalValues)
718 foreach ($AdditionalValues as $FieldName => $Value)
720 $Query .=
", `".$FieldName.
"` = '".addslashes($Value).
"'";
724 # add item to database
725 $this->DB->Query($Query);
727 # retrieve ID of new item
728 $Id = $this->DB->LastInsertId($this->ItemTableName);
730 # return ID to caller
740 # delete item from database
741 $this->DB->Query(
"DELETE FROM ".$this->ItemTableName
742 .
" WHERE ".$this->ItemIdFieldName.
" = '".addslashes($ItemId).
"'");
746 # ---- order operations --------------------------------------------------
748 # set SQL condition (added to WHERE clause) used to select items for ordering ops
749 # (use NULL to clear any previous condition)
752 # condition is non-negative IDs (non-temp items) plus supplied condition
753 $NewCondition = $this->ItemIdFieldName.
" >= 0"
754 .(($Condition) ?
" AND ".$Condition :
"");
755 $this->OrderList->SqlCondition($NewCondition);
758 # insert/move item to before specified item
761 # error out if ordering operations are not allowed for this item type
762 if (!$this->OrderOpsAllowed)
764 exit(
"<br>ERROR: attempt to perform ordering operation"
765 .
" (InsertBefore()) on item type"
766 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
770 $this->OrderList->InsertBefore($SourceItemOrItemId, $TargetItemOrItemId);
773 # insert/move item to after specified item
776 # error out if ordering operations are not allowed for this item type
777 if (!$this->OrderOpsAllowed)
779 exit(
"<br>ERROR: attempt to perform ordering operation"
780 .
" (InsertAfter()) on item type"
781 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
785 $this->OrderList->InsertAfter($SourceItemOrItemId, $TargetItemOrItemId);
788 # add/move item to beginning of list
791 # error out if ordering operations are not allowed for this item type
792 if (!$this->OrderOpsAllowed)
794 exit(
"<br>ERROR: attempt to perform ordering operation"
795 .
" (Prepend()) on item type"
796 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
800 $this->OrderList->Prepend($ItemOrItemId);
803 # add/move item to end of list
806 # error out if ordering operations are not allowed for this item type
807 if (!$this->OrderOpsAllowed)
809 exit(
"<br>ERROR: attempt to perform ordering operation"
810 .
" (Append()) on item type"
811 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
815 $this->OrderList->Append($ItemOrItemId);
818 # retrieve list of item IDs in order
821 # error out if ordering operations are not allowed for this item type
822 if (!$this->OrderOpsAllowed)
824 exit(
"<br>ERROR: attempt to perform ordering operation"
825 .
" (GetItemIdsInOrder()) on item type"
826 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
829 # retrieve list of IDs
830 return $this->OrderList->GetIds($AddStrayItemsToOrder);
833 # remove item from existing order
836 # error out if ordering operations are not allowed for this item type
837 if (!$this->OrderOpsAllowed)
839 exit(
"<br>ERROR: attempt to perform ordering operation"
840 .
" (RemoveItemFromOrder()) on item type"
841 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
845 $this->OrderList->Remove($ItemId);
849 # ---- PRIVATE INTERFACE -------------------------------------------------
854 private $ItemClassName;
855 private $ItemTableName;
856 private $ItemIdFieldName;
857 private $ItemNameFieldName;
858 private $ErrorStatus;
859 private $OrderOpsAllowed;
862 # get/set ordering values
863 private function GetPreviousItemId($ItemId)
865 return $this->DB->Query(
"SELECT Previous".$this->ItemIdFieldName
866 .
" FROM ".$this->ItemTableName
867 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId),
868 "Previous".$this->ItemIdFieldName);
870 private function GetNextItemIdInOrder($ItemId)
872 return $this->DB->Query(
"SELECT Next".$this->ItemIdFieldName
873 .
" FROM ".$this->ItemTableName
874 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId),
875 "Next".$this->ItemIdFieldName);
877 private function SetPreviousItemId($ItemId, $NewValue)
879 $this->DB->Query(
"UPDATE ".$this->ItemTableName
880 .
" SET Previous".$this->ItemIdFieldName.
" = ".intval($NewValue)
881 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId));
883 private function SetNextItemId($ItemId, $NewValue)
885 $this->DB->Query(
"UPDATE ".$this->ItemTableName
886 .
" SET Next".$this->ItemIdFieldName.
" = ".intval($NewValue)
887 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId));
889 private function SetPreviousAndNextItemIds($ItemId, $NewPreviousId, $NewNextId)
891 $this->DB->Query(
"UPDATE ".$this->ItemTableName
892 .
" SET Previous".$this->ItemIdFieldName.
" = ".intval($NewPreviousId)
893 .
", Next".$this->ItemIdFieldName.
" = ".intval($NewNextId)
894 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId));