4 # FILE: ItemFactory.php
6 # Part of the Collection Workflow Integration System (CWIS)
7 # Copyright 2007-2013 Edward Almasy and Internet Scout Research Group
8 # http://scout.wisc.edu/cwis/
20 # ---- PUBLIC INTERFACE --------------------------------------------------
23 function ItemFactory($ItemClassName, $ItemTableName, $ItemIdFieldName,
24 $ItemNameFieldName = NULL,
$FieldId = NULL, $OrderOpsAllowed = FALSE)
26 # save item access names
27 $this->ItemClassName = $ItemClassName;
28 $this->ItemTableName = $ItemTableName;
29 $this->ItemIdFieldName = $ItemIdFieldName;
30 $this->ItemNameFieldName = $ItemNameFieldName;
32 # save field ID (if specified)
35 # save flag indicating whether item type allows ordering operations
36 $this->OrderOpsAllowed = $OrderOpsAllowed;
40 $ItemTableName, $ItemIdFieldName);
44 # grab our own database handle
47 # assume everything will be okay
48 $this->ErrorStatus = 0;
51 # return current error status
52 function Status() {
return $this->ErrorStatus; }
54 # get ID of currently edited item
57 # if ID available in session variable
59 if ($EditedIds = $Session->Get($this->ItemClassName.
"EditedIds"))
61 # look up value in session variable
62 $ItemId = $EditedIds[0];
66 # attempt to look up last temp item ID
69 # store it in session variable
70 $EditedIds = array($ItemId);
71 $Session->RegisterVariable($this->ItemClassName.
"EditedIds", $EditedIds);
74 # return ID (if any) to caller
78 # set ID of currently edited item
81 # if edited ID array already stored for session
83 if ($EditedIds = $Session->Get($this->ItemClassName.
"EditedIds"))
85 # prepend new value to array
86 array_unshift($EditedIds, $NewId);
90 # start with fresh array
91 $EditedIds = array($NewId);
94 # save in session variable
95 $Session->RegisterVariable($this->ItemClassName.
"EditedIds", $EditedIds);
98 # clear currently edited item ID
101 # if edited item IDs available in a session variable
103 $SessionVarName = $this->ItemClassName.
"EditedIds";
104 if ($EditedIds = $Session->Get($SessionVarName))
106 # remove current item from edited item ID array
107 array_shift($EditedIds);
109 # if no further edited items
110 if (count($EditedIds) < 1)
112 # destroy session variable
113 $Session->UnregisterVariable($SessionVarName);
117 # save new shorter edited item ID array to session variable
118 $Session->RegisterVariable($SessionVarName, $EditedIds);
123 # clear currently edited item ID and item
126 # if current edited item is temp item
128 if ($CurrentEditedItemId < 0)
130 # delete temp item from DB
131 $this->DB->Query(
"DELETE FROM ".$this->ItemTableName
132 .
" WHERE ".$this->ItemIdFieldName.
" = ".$CurrentEditedItemId);
135 # clear current edited item ID
148 # load array of stale items
149 $MinutesUntilStale = max($MinutesUntilStale, 1);
150 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" FROM ".$this->ItemTableName
151 .
" WHERE ".$this->ItemIdFieldName.
" < 0"
152 .
" AND DateLastModified < DATE_SUB(NOW(), "
153 .
" INTERVAL ".intval($MinutesUntilStale).
" MINUTE)");
154 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName);
157 foreach ($ItemIds as $ItemId)
159 $Item =
new $this->ItemClassName($ItemId);
163 # report number of items deleted to caller
164 return count($ItemIds);
167 # retrieve most recent temp item ID based on user ID
168 # (returns NULL if no temp item found for that user ID)
171 # retrieve ID of most recently modified temp item for this user
173 $ItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" FROM ".$this->ItemTableName
174 .
" WHERE LastModifiedById = '".$User->Get(
"UserId").
"'"
175 .
" AND ".$this->ItemIdFieldName.
" < 0"
176 .
" ORDER BY ".$this->ItemIdFieldName.
" ASC"
178 $this->ItemIdFieldName);
180 # return item to caller (or NULL if none found)
184 # return next item ID
187 # if no highest item ID found
189 if ($HighestItemId <= 0)
191 # start with item ID 1
196 # else use next ID available after highest
197 $ItemId = $HighestItemId + 1;
200 # return next ID to caller
204 # return highest item ID ($Condition should not include "WHERE")
207 # if temp items are supposed to be included
208 if ($IncludeTempItems)
210 # condition is only as supplied
211 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
215 # condition is non-negative IDs plus supplied condition
216 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0"
217 .(($Condition == NULL) ?
"" :
" AND ".$Condition);
220 # return highest item ID to caller
221 return $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
222 .
" FROM ".$this->ItemTableName
224 .
" ORDER BY ".$this->ItemIdFieldName
226 $this->ItemIdFieldName);
229 # return next temp item ID
232 $LowestItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
233 .
" FROM ".$this->ItemTableName
234 .
" ORDER BY ".$this->ItemIdFieldName
236 $this->ItemIdFieldName);
237 if ($LowestItemId > 0)
243 $ItemId = $LowestItemId - 1;
248 # return count of items
251 # if condition was supplied
252 if ($Condition != NULL)
255 $ConditionString =
" WHERE ".$Condition;
259 # if field ID is available
260 if (isset($this->FieldId))
262 # use condition for matching field ID
263 $ConditionString =
" WHERE FieldId = ".intval($this->FieldId);
268 $ConditionString =
"";
272 # if temp items are to be excluded
273 if (!$IncludeTempItems)
275 # if a condition was previously set
276 if (strlen($ConditionString))
278 # add in condition to exclude temp items
279 $ConditionString .=
" AND (".$this->ItemIdFieldName.
" >= 0)";
283 # use condition to exclude temp items
284 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0";
288 # retrieve item count
289 $Count = $this->DB->Query(
"SELECT COUNT(*) AS RecordCount"
290 .
" FROM ".$this->ItemTableName
294 # return count to caller
298 # return array of item IDs ($Condition should not include "WHERE")
299 function GetItemIds($Condition = NULL, $IncludeTempItems = FALSE)
301 # if temp items are supposed to be included
302 if ($IncludeTempItems)
304 # condition is only as supplied
305 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
309 # condition is non-negative IDs plus supplied condition
310 $ConditionString =
" WHERE ".$this->ItemIdFieldName.
" >= 0"
311 .(($Condition == NULL) ?
"" :
" AND ".$Condition);
315 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
316 .
" FROM ".$this->ItemTableName
318 $ItemIds = $this->DB->FetchColumn($this->ItemIdFieldName);
320 # return IDs to caller
324 # return latest modification date ($Condition should not include "WHERE")
327 # return modification date for item most recently changed
328 $ConditionString = ($Condition == NULL) ?
"" :
" WHERE ".$Condition;
329 return $this->DB->Query(
"SELECT MAX(DateLastModified) AS LastChangeDate"
330 .
" FROM ".$this->ItemTableName.$ConditionString,
334 # retrieve item by item ID
337 return new $this->ItemClassName($ItemId);
346 return $this->DB->Query(
"SELECT COUNT(*) AS ItemCount"
347 .
" FROM ".$this->ItemTableName
348 .
" WHERE ".$this->ItemIdFieldName.
" = ".intval($ItemId),
"ItemCount")
352 # retrieve item by name
355 # error out if this is an illegal operation for this item type
356 if ($this->ItemNameFieldName == NULL)
358 exit(
"<br>ERROR: attempt to get item by name on item type"
359 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
362 # query database for item ID
363 $Comparison = $IgnoreCase
364 ?
"LOWER(".$this->ItemNameFieldName.
") = '"
365 .addslashes(strtolower($Name)).
"'"
366 : $this->ItemNameFieldName.
" = '" .addslashes($Name).
"'";
367 $ItemId = $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
368 .
" FROM ".$this->ItemTableName
369 .
" WHERE ".$Comparison
370 .(isset($this->FieldId)
371 ?
" AND FieldId = ".$this->FieldId
373 $this->ItemIdFieldName);
375 # if item ID was not found
376 if ($ItemId === NULL)
378 # return NULL to caller
383 # generate new item object
384 $Item = $this->
GetItem($ItemId);
387 # return new object to caller
398 # error out if this is an illegal operation for this item type
399 if ($this->ItemNameFieldName == NULL)
401 exit(
"<br>ERROR: attempt to get array of item names on item type"
402 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
405 # query database for item names
407 if ($this->FieldId || $SqlCondition)
409 $Condition =
"WHERE ";
411 $Condition .=
"FieldId = ".intval($this->FieldId);
412 if ($this->FieldId && $SqlCondition)
413 $Condition .=
" AND ";
415 $Condition .= $SqlCondition;
417 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
418 .
", ".$this->ItemNameFieldName
419 .
" FROM ".$this->ItemTableName.
" "
421 .
" ORDER BY ".$this->ItemNameFieldName);
422 $Names = $this->DB->FetchColumn(
423 $this->ItemNameFieldName, $this->ItemIdFieldName);
425 # return item names to caller
438 foreach ($Names as $Id => $Name)
460 $SqlCondition = NULL, $DisplaySize = 1, $SubmitOnChange = FALSE)
462 # retrieve requested fields
465 # if multiple selections are allowed
466 if ($DisplaySize > 1)
468 # begin multi-selection HTML option list
469 $Html =
"<select name=\"".htmlspecialchars($OptionListName).
"[]\""
470 .($SubmitOnChange ?
" onChange=\"submit()\"" :
"")
471 .
" multiple=\"multiple\" size=\"".$DisplaySize.
"\">\n";
475 # begin single-selection HTML option list
476 $Html =
"<select name=\"".htmlspecialchars($OptionListName).
"\""
477 .($SubmitOnChange ?
" onChange=\"submit()\"" :
"")
479 $Html .=
"<option value=\"-1\">--</option>\n";
482 # for each metadata field
483 foreach ($ItemNames as $Id => $Name)
485 # add entry for field to option list
486 $Html .=
"<option value=\"".$Id.
"\"";
487 if (($Id == $SelectedItemId)
488 || (is_array($SelectedItemId) && in_array($Id, $SelectedItemId)))
490 $Html .=
" selected";
492 $Html .=
">".htmlspecialchars($Name).
"</option>\n";
495 # end HTML option list
496 $Html .=
"</select>\n";
498 # return constructed HTML to caller
509 $Condition = $IgnoreCase
510 ?
"LOWER(".$this->ItemNameFieldName.
")"
511 .
" = '".addslashes(strtolower($Name)).
"'"
512 : $this->ItemNameFieldName.
" = '".addslashes($Name).
"'";
513 $NameCount = $this->DB->Query(
"SELECT COUNT(*) AS RecordCount FROM "
514 .$this->ItemTableName.
" WHERE ".$Condition,
"RecordCount");
515 return ($NameCount > 0) ? TRUE : FALSE;
518 # retrieve names of items matching search string (array index is IDs)
519 # (NOTE: IncludeVariants parameter is NOT YET SUPPORTED!)
521 $IncludeVariants = FALSE, $UseBooleanMode = TRUE, $Offset=0)
523 # error out if this is an illegal operation for this item type
524 if ($this->ItemNameFieldName == NULL)
526 exit(
"<br>ERROR: attempt to search for item names on item type"
527 .
"(".$this->ItemClassName.
") that has no name field specified<br>\n");
530 # return no results if empty search string passed in
531 if (!strlen(trim($SearchString))) {
return array(); }
533 # construct SQL query
535 $QueryString =
"SELECT ".$this->ItemIdFieldName.
",".$this->ItemNameFieldName
536 .
" FROM ".$this->ItemTableName.
" WHERE";
539 $QueryString .=
" FieldId = ".$this->FieldId.
" AND";
542 # If the search string is valid but shorter than the minimum word length
543 # indexed by the FTS, just do a normal equality test instead of using
544 # the index. Otherwise, FTS away.
545 $MinWordLen =
$DB->Query(
546 "SHOW VARIABLES WHERE variable_name='ft_min_word_len'",
"Value");
547 if (strlen($SearchString) < $MinWordLen)
549 $QueryString .=
" ".$this->ItemNameFieldName.
"='".addslashes($SearchString).
"'";
551 else if ($UseBooleanMode)
553 $SearchString = preg_replace(
"/[)\(><]+/",
"", $SearchString);
554 $Words = preg_split(
"/[\s]+/", trim($SearchString));
555 $NewSearchString =
"";
556 $InQuotedString = FALSE;
558 $StopWordList = $SqlVarObj->GetStopWords();
559 $MinWordLen = $SqlVarObj->Get(
"ft_min_word_len");
560 foreach ($Words as $Word)
562 # remove any query-specific terms, punctuation, etc.
563 $JustTheWord = preg_replace(
"/[^a-zA-Z-]/",
"", $Word);
565 # require (boolean AND) certain words
566 if ($InQuotedString == FALSE
567 && !in_array($JustTheWord, $StopWordList)
568 && strlen($JustTheWord) >= $MinWordLen
572 $NewSearchString .=
"+";
575 if (preg_match(
"/^\"/", $Word)) { $InQuotedString = TRUE; }
576 if (preg_match(
"/\"$/", $Word)) { $InQuotedString = FALSE; }
577 $NewSearchString .= $Word.
" ";
580 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
581 .
" AGAINST ('".addslashes(trim($NewSearchString)).
"'"
582 .
" IN BOOLEAN MODE)";
586 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
587 .
" AGAINST ('".addslashes(trim($SearchString)).
"')";
589 $QueryString .=
" LIMIT ".intval($NumberOfResults).
" OFFSET "
592 # perform query and retrieve names and IDs of items found by query
593 $DB->Query($QueryString);
594 $Names =
$DB->FetchColumn($this->ItemNameFieldName, $this->ItemIdFieldName);
596 # if boolean mode matching was performed
597 if (strlen($SearchString) >= $MinWordLen && $UseBooleanMode)
599 foreach ($Words as $Word)
601 $TgtWord = preg_replace(
"/[^a-zA-Z]/",
"", $Word);
602 if ($Word{0} ==
"-" && strlen($TgtWord) < $MinWordLen)
605 foreach ($Names as $Id => $Name)
607 if (! preg_match(
'/\b'.$TgtWord.
'/i', $Name))
609 $NewNames[$Id] = $Name;
617 # return names to caller
621 # retrieve the count of names of items matching search string (array index
622 # is IDs) (NOTE: IncludeVariants parameter is NOT YET SUPPORTED!)
624 $UseBooleanMode = TRUE)
626 # return no results if empty search string passed in
627 if (!strlen(trim($SearchString))) {
return 0; }
629 # construct SQL query
631 $QueryString =
"SELECT COUNT(*) as ItemCount FROM "
632 .$this->ItemTableName.
" WHERE";
635 $QueryString .=
" FieldId = ".$this->FieldId.
" AND";
638 # If the search string is valid but shorter than the minimum word length
639 # indexed by the FTS, just do a normal equality test instead of using
640 # the index. Otherwise, FTS away.
641 $MinWordLen =
$DB->Query(
642 "SHOW VARIABLES WHERE variable_name='ft_min_word_len'",
"Value");
643 if (strlen($SearchString) < $MinWordLen)
645 $QueryString .=
" ".$this->ItemNameFieldName.
"='".addslashes($SearchString).
"'";
647 else if ($UseBooleanMode)
649 $SearchString = preg_replace(
"/[)\(><]+/",
"", $SearchString);
650 $Words = preg_split(
"/[\s]+/", trim($SearchString));
651 $NewSearchString =
"";
652 $InQuotedString = FALSE;
654 $StopWordList = $SqlVarObj->GetStopWords();
655 $MinWordLen = $SqlVarObj->Get(
"ft_min_word_len");
656 foreach ($Words as $Word)
658 # remove any query-specific terms, punctuation, etc.
659 $JustTheWord = preg_replace(
"/[^a-zA-Z-]/",
"", $Word);
661 # require (boolean AND) certain words
662 if ($InQuotedString == FALSE
663 && !in_array($JustTheWord, $StopWordList)
664 && strlen($JustTheWord) >= $MinWordLen
668 $NewSearchString .=
"+";
671 if (preg_match(
"/^\"/", $Word)) { $InQuotedString = TRUE; }
672 if (preg_match(
"/\"$/", $Word)) { $InQuotedString = FALSE; }
673 $NewSearchString .= $Word.
" ";
676 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
677 .
" AGAINST ('".addslashes(trim($NewSearchString)).
"'"
678 .
" IN BOOLEAN MODE)";
682 $QueryString .=
" MATCH (".$this->ItemNameFieldName.
")"
683 .
" AGAINST ('".addslashes(trim($SearchString)).
"')";
686 # perform query and retrieve names and IDs of items found by query
687 $DB->Query($QueryString);
688 return intval(
$DB->FetchField(
"ItemCount"));
701 # for each supplied item name
703 foreach ($ItemNames as $Name)
705 # if item does not exist with this name
710 $NewItem =
new $this->ItemClassName(NULL, $Name, $this->FieldId);
713 # assign qualifier to item if supplied
714 if ($Qualifier !== NULL)
716 $NewItem->Qualifier($Qualifier);
721 # return count of items added to caller
733 function AddItem($ItemName, $AdditionalValues = NULL)
735 # build initial database query for adding item
736 $Query =
"INSERT INTO ".$this->ItemTableName.
" SET `"
737 .$this->ItemNameFieldName.
"` = '".addslashes($ItemName).
"'";
739 # add any additional values to query
740 if ($AdditionalValues)
742 foreach ($AdditionalValues as $FieldName => $Value)
744 $Query .=
", `".$FieldName.
"` = '".addslashes($Value).
"'";
748 # add item to database
749 $this->DB->Query($Query);
751 # retrieve ID of new item
752 $Id = $this->DB->LastInsertId($this->ItemTableName);
754 # return ID to caller
764 # delete item from database
765 $this->DB->Query(
"DELETE FROM ".$this->ItemTableName
766 .
" WHERE ".$this->ItemIdFieldName.
" = '".addslashes($ItemId).
"'");
770 # ---- order operations --------------------------------------------------
772 # set SQL condition (added to WHERE clause) used to select items for ordering ops
773 # (use NULL to clear any previous condition)
776 # condition is non-negative IDs (non-temp items) plus supplied condition
777 $NewCondition = $this->ItemIdFieldName.
" >= 0"
778 .(($Condition) ?
" AND ".$Condition :
"");
779 $this->OrderList->SqlCondition($NewCondition);
782 # insert/move item to before specified item
785 # error out if ordering operations are not allowed for this item type
786 if (!$this->OrderOpsAllowed)
788 exit(
"<br>ERROR: attempt to perform ordering operation"
789 .
" (InsertBefore()) on item type"
790 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
794 $this->OrderList->InsertBefore($SourceItemOrItemId, $TargetItemOrItemId);
797 # insert/move item to after specified item
800 # error out if ordering operations are not allowed for this item type
801 if (!$this->OrderOpsAllowed)
803 exit(
"<br>ERROR: attempt to perform ordering operation"
804 .
" (InsertAfter()) on item type"
805 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
809 $this->OrderList->InsertAfter($SourceItemOrItemId, $TargetItemOrItemId);
812 # add/move item to beginning of list
815 # error out if ordering operations are not allowed for this item type
816 if (!$this->OrderOpsAllowed)
818 exit(
"<br>ERROR: attempt to perform ordering operation"
819 .
" (Prepend()) on item type"
820 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
824 $this->OrderList->Prepend($ItemOrItemId);
827 # add/move item to end of list
830 # error out if ordering operations are not allowed for this item type
831 if (!$this->OrderOpsAllowed)
833 exit(
"<br>ERROR: attempt to perform ordering operation"
834 .
" (Append()) on item type"
835 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
839 $this->OrderList->Append($ItemOrItemId);
842 # retrieve list of item IDs in order
845 # error out if ordering operations are not allowed for this item type
846 if (!$this->OrderOpsAllowed)
848 exit(
"<br>ERROR: attempt to perform ordering operation"
849 .
" (GetItemIdsInOrder()) on item type"
850 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
853 # retrieve list of IDs
854 return $this->OrderList->GetIds($AddStrayItemsToOrder);
857 # remove item from existing order
860 # error out if ordering operations are not allowed for this item type
861 if (!$this->OrderOpsAllowed)
863 exit(
"<br>ERROR: attempt to perform ordering operation"
864 .
" (RemoveItemFromOrder()) on item type"
865 .
"(".$this->ItemClassName.
") that does not support ordering<br>\n");
869 $this->OrderList->Remove($ItemId);
873 # ---- PRIVATE INTERFACE -------------------------------------------------
878 private $ItemClassName;
879 private $ItemTableName;
880 private $ItemIdFieldName;
881 private $ItemNameFieldName;
882 private $ErrorStatus;
883 private $OrderOpsAllowed;