CMS  Version 3.9
taxonomy_manager.inc
Go to the documentation of this file.
1 <?php
7 /**************************************************************
8 
9  Copyright (c) 2014 Sonjara, Inc
10 
11  Permission is hereby granted, free of charge, to any person
12  obtaining a copy of this software and associated documentation
13  files (the "Software"), to deal in the Software without
14  restriction, including without limitation the rights to use,
15  copy, modify, merge, publish, distribute, sublicense, and/or sell
16  copies of the Software, and to permit persons to whom the
17  Software is furnished to do so, subject to the following
18  conditions:
19 
20  The above copyright notice and this permission notice shall be
21  included in all copies or substantial portions of the Software.
22 
23  Except as contained in this notice, the name(s) of the above
24  copyright holders shall not be used in advertising or otherwise
25  to promote the sale, use or other dealings in this Software
26  without prior written authorization.
27 
28  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
30  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
32  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
33  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
34  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
35  OTHER DEALINGS IN THE SOFTWARE.
36 
37 *****************************************************************/
38 
39 Fakoli::usingFeature("search_form");
40 
42 {
43  function export()
44  {
45  $xml = "\n<TaxonomyMap>";
46  $xml .= SerializationManager::serialize(Taxonomy, "ORDER BY taxonomy_id");
47  $xml .= SerializationManager::serialize(TaxonomyTerm, "ORDER BY term_id");
48  $xml .= "</TaxonomyMap>";
49 
50  return $xml;
51  }
52 
53  function import($doc, $tx)
54  {
57  }
58 }
59 
60 class TaxonomySearchParameterHandler extends SearchParameterHandler
61 {
62  var $cl;
63 
64  function __construct($class)
65  {
66  $this->cl = $class;
67  }
68 
69  function member($name, $set)
70  {
71  $values = $set;
72  if(is_array($set))
73  $values = implode(",", array_keys($set));
74 
75  trace("## Generating taxonomy terms clause for $name using values ($values)", 3);
76 
77  $obj = new $this->cl;
78  $pk = $obj->getPrimaryKey();
79  return "$pk IN (SELECT id FROM taxonomy_term_association WHERE class='{$this->cl}' AND term_id IN ($set))";
80  }
81 }
82 
91 {
92  static $bindableClasses = null;
93 
98  function __construct()
99  {
100 
101  }
102 
103  static function setDefaults()
104  {
105  //TODO: Set default configuration parameters here
106  }
107 
108  static function getTabs($key)
109  {
110  $tabs = array( "Taxonomy Definition" => "/admin/taxonomy_form",
111  "Terms" => "/admin/taxonomy_terms");
112 
113  $qs = ($key) ? "taxonomy_id=$key" : "";
114  return new TabBar("tabs", $tabs, $qs);
115  }
116 
121  static function getRegisteredClasses()
122  {
123  $registeredClasses = array();
124 
125  $registeredClasses = ComponentManager::fireEvent("RegisterTaxonomyClasses", $registeredClasses);
126 
127  return $registeredClasses;
128  }
129 
135  static function getRegisteredClassOptions()
136  {
138 
139  $options = array();
140 
141  foreach($classes as $class)
142  {
143  $proto = new $class;
144  $options[$class] = $proto->prettifyClassName(true);
145  }
146 
147  return $options;
148  }
149 
150  static function getBindableClasses()
151  {
153  {
154  $bindableClasses = array();
155  ComponentManager::fireEvent("RegisterBindableClasses");
156  }
157 
159  }
160 
161  static function registerBindableClass($class, $format, $constraint = "", $fieldRenderer = BindingTargetFieldRenderer, $facetFilter = BindingTargetFacetFilter)
162  {
163  TaxonomyManager::$bindableClasses[$class] = array("format" => $format, "constraint" => $constraint,
164  "field_renderer" => $fieldRenderer, "facet_filter" => $facetFilter);
165  }
166 
167  static function getBindableClassOptions()
168  {
170 
171  $options = array();
172 
173  foreach($classes as $class => $format)
174  {
175  $proto = new $class;
176  $options[$class] = $proto->prettifyClassName();
177  }
178 
179  return $options;
180  }
181 
183  {
185 
186  $class = is_object($bindingTarget) ? get_class($bindingTarget) : $bindingTarget;
187  $bindingDetails = TaxonomyManager::$bindableClasses[$class];
188  if (!$bindingDetails)
189  {
190  trace("## $class is not registered as a binding target", 1);
191  return array();
192  }
193 
194  $targets = Query::create($class, $bindingDetails["constraint"])->execute();
195 
196  return $targets;
197  }
198 
200  {
202  return $classes[$bindingTarget->target_class]["format"];
203  }
204 
206  {
208  return $classes[$bindingTarget->target_class]["field_renderer"];
209  }
210 
212  {
214  return $classes[$bindingTarget->target_class]["facet_filter"];
215  }
216 
217  static function addTaxonomyFieldRenderers($form, $showSelectAll = true, $group = null, $limitTo = null)
218  {
219  $renderers = TaxonomyManager::addBindingFieldRenderers($form, $showSelectAll, $group, $limitTo);
220 
221  $class = get_class($form->data);
222 
223  $taxonomies = Query::create(Taxonomy, "")->execute();
224 
225  foreach($taxonomies as $taxonomy)
226  {
227  $classes = explode(",", $taxonomy->associated_classes);
228  if (array_search($class, $classes) !== FALSE)
229  {
230  if (is_array($limitTo) && !array_search($taxonomy->identifier, $limitTo))
231  {
232  continue;
233  }
234 
235  trace("Adding TaxonomyTermFieldRenderer for {$taxonomy->identifier}", 3);
237  $renderer->setSize(400, 125);
238  $renderer->resizable = true;
239  $renderer->showSelectAll = $showSelectAll;
240 
241  if ($group)
242  {
243  $form->group($group, $taxonomy->identifier);
244  }
245 
246  if ($form instanceof SearchForm)
247  {
248  $form->params->setHandler($taxonomy->identifier, new TaxonomySearchParameterHandler($class));
249  $form->setMatchingMode("member", $taxonomy->identifier);
250  }
251 
252  $renderers[] = $renderer;
253  }
254  }
255 
256  return $renderers;
257  }
258 
259  static function addBindingFieldRenderers($form, $showSelectAll = true, $group = null)
260  {
262 
263  $class = get_class($form->data);
264 
265  $targets = Query::create(BindingTarget, "")->execute();
266 
267  trace("### Found ".count($targets)." binding targets", 3);
268  $renderers = array();
269 
270  foreach($targets as $target)
271  {
272  trace("## Checking {$target->target_class} - {$target->associated_classes}", 3);
273 
274  $classes = explode(",", $target->associated_classes);
275  if (array_search($class, $classes) !== FALSE)
276  {
277  trace("## Found a match", 3);
278 
280 
281  $renderer = new $fieldRenderer($form, $target);
282  $renderer->setSize(400, 125);
283  $renderer->resizable = true;
284  $renderer->showSelectAll = $showSelectAll;
285 
286  if ($group)
287  {
288  $form->group($group, $class);
289  }
290 
291  $renderers[] = $renderer;
292  }
293  }
294 
295  return $renderers;
296  }
297 
298  static function getAssociatedTaxonomies($classes, $facet_filter_only = false)
299  {
300  if (!is_array($classes))
301  {
302  $classes = array($classes);
303  }
304 
305  $matched = array();
306 
307  $taxonomies = Query::create(Taxonomy, "")->execute();
308 
309  foreach($taxonomies as $taxonomy)
310  {
311  if ($facet_filter_only and !$taxonomy->enable_facet_filter) continue;
312 
313  $taxonomy_classes = explode(",", $taxonomy->associated_classes);
314  foreach($classes as $class)
315  {
316  if (array_search($class, $taxonomy_classes) !== FALSE)
317  {
318  $matched[] = $taxonomy;
319  break;
320  }
321  }
322  }
323 
324  return $matched;
325  }
326 
327  static function getBindingTargets($classes, $facet_filter_only = false)
328  {
329  if (!is_array($classes))
330  {
331  $classes = array($classes);
332  }
333 
334  $matched = array();
335 
336  $bindingTargets = Query::create(BindingTarget, "")->execute();
337 
338  foreach($bindingTargets as $bindingTarget)
339  {
340  if ($facet_filter_only and !$bindingTarget->enable_facet_filter) continue;
341 
342  $assoc_classes = explode(",", $bindingTarget->associated_classes);
343  foreach($classes as $class)
344  {
345  if (array_search($class, $assoc_classes) !== FALSE)
346  {
347  $matched[] = $bindingTarget;
348  break;
349  }
350  }
351  }
352 
353  return $matched;
354  }
355 
356  static function importTerms($taxonomy, $file)
357  {
358  $indexedTerms = reindexList($taxonomy->Terms(), "term");
359 
360  $fp = fopen($file, "r");
361 
362  $fields = fgetcsv($fp);
363 
364  $fileIdx = -1;
365 
366  foreach($fields as $field)
367  {
368  if ($field == "term")
369  {
370  $fileIdx = $i;
371  break;
372  }
373 
374  $i++;
375  }
376 
377  if ($fileIdx < 0)
378  {
379  throw new FakoliException("'term' column not present");
380  }
381 
382  while($values = fgetcsv($fp))
383  {
384  $term = $values[$fileIdx];
385 
386  trace("Importing term", 3);
387 
388  $i = 0;
389 
390  if (array_key_exists($term, $indexedTerms))
391  {
392  $import = $indexedTerms[$term];
393  foreach($fields as $field)
394  {
395  $import->set($field, $values[$i]);
396  $i++;
397  }
398 
399  $import->save();
400  }
401  else
402  {
403  $import = new TaxonomyTerm();
404  foreach($fields as $field)
405  {
406  $import->set($field, $values[$i]);
407  $i++;
408  }
409 
410  $import->taxonomy_id = $taxonomy->taxonomy_id;
411  $import->save();
412  }
413  }
414 
415  fclose($fp);
416  }
417 
418  static function addFacets($manager, $classes, $dropdown = true, $max_width = "200px", $width = "200px", $height = "120px")
419  {
420  if (!is_array($classes) && $classes) $classes = array($classes);
421 
422  $bindingTargets = TaxonomyManager::getBindingTargets($classes, true);
423  foreach($bindingTargets as $target)
424  {
426 
427  $facet = $manager->addFacet($target->target_class, new $facetFilter($target));
428  $facet->dropdown = $dropdown;
429  $facet->dropdownMaxWidth = $max_width;
430  $facet->width = $width;
431  $facet->height = $height;
432  }
433 
435  foreach($taxonomies as $taxonomy)
436  {
437  $facet = $manager->addFacet($taxonomy->taxonomy_name, new TaxonomyFacetFilter($taxonomy));
438  $facet->dropdown = $dropdown;
439  $facet->dropdownMaxWidth = $max_width;
440  $facet->width = $width;
441  $facet->height = $height;
442  }
443  }
444 
445  static function getRequestTerms($classes)
446  {
447  $ids = array();
448 
449  if (!is_array($classes) && $classes) $classes = array($classes);
450 
452  foreach($taxonomies as $taxonomy)
453  {
454  if (isset($_REQUEST[$taxonomy->identifier]))
455  {
456  $term_ids = explode(",", $_REQUEST[$taxonomy->identifier]);
457  checkNumeric($term_ids);
458  foreach($term_ids as $term_id)
459  {
460  if ($term_id) $ids[] = $term_id;
461  }
462  }
463  }
464 
465  if (count($ids) == 0) return array();
466  $idString = implode(", ", $ids);
467 
468  return Query::create(TaxonomyTerm, "WHERE term_id IN ($idString)")->execute();
469  }
470 
472  {
474 
475  $obj = new $class;
476  $pk = $obj->getPrimaryKey();
477 
478  $clauses = array();
479 
480  foreach($taxonomies as $taxonomy)
481  {
482  if (isset($_REQUEST[$taxonomy->identifier]))
483  {
484  $term_ids = $_REQUEST[$taxonomy->identifier];
485 
486  if (!$term_ids) continue;
487  if (!preg_match("/^[\\d,]+/", $term_ids))
488  {
489  throw new FakoliException("Invalid facet filter terms");
490  }
491 
492  $clauses[] = "$pk IN (SELECT id FROM taxonomy_term_association WHERE class='$class' AND term_id IN ($term_ids))";
493  }
494  }
495 
496  return implode(" AND ", $clauses);
497  }
498 
499  static function cloneTaxonomy($form)
500  {
501  $tx = new DataTransaction();
502 
503  try
504  {
505  $clone = $form->data;
506 
507  $clone->joinTransaction($tx);
508 
509  $clone->filter = null;
510  $name = $clone->taxonomy_name;
511  $ident = $clone->identifier;
512 
513  $taxonomy_id = checkNumeric($_GET["taxonomy_id"]);
514  $src = new Taxonomy($taxonomy_id);
515  $clone->copy($src);
516  unset($clone->taxonomy_id);
517  $clone->taxonomy_name = $name;
518  $clone->identifier = $ident;
519  $clone->published = false;
520  $clone->save();
521 
522  $terms = $src->Terms();
523 
524  foreach($terms as $term)
525  {
526  $cloneTerm = new TaxonomyTerm();
527  $cloneTerm->copy($term);
528  $cloneTerm->taxonomy_id = $clone->taxonomy_id;
529  unset($cloneTerm->term_id);
530  $cloneTerm->joinTransaction($tx);
531  $cloneTerm->save();
532  }
533 
534  $tx->commit();
535  return true;
536  }
537  catch (Exception $e)
538  {
539  $tx->rollback();
540  $form->msg = $e->getMessage();
541  return false;
542  }
543  }
544 
552  static function getBoundItems($source, $class, $constraint = "")
553  {
554  $constraint = preg_replace("/^\\s*WHERE/i", "AND", $constraint);
555  $obj = new $class;
556  $pk = $obj->getPrimaryKey();
557 
558  $items = Query::create($class, "WHERE $pk IN (SELECT target_id FROM binding WHERE id=:source_id and class=:source_class AND target_class=:target_class) $constraint")
559  ->bind(":source_id", $source->get($source->getPrimaryKey()),
560  ":target_class", $class,
561  ":source_class", get_class($source))
562  ->execute();
563 
564  return $items;
565  }
566 
575  {
576  $constraint = preg_replace("/^\\s*WHERE/i", "AND", $constraint);
577  $obj = new $class;
578  $pk = $obj->getPrimaryKey();
579 
580  $items = Query::create($class, "WHERE $pk IN (SELECT id FROM binding WHERE target_id=:target_id and class=:source_class AND target_class=:target_class) $constraint")
581  ->bind(":target_id", $target->get($target->getPrimaryKey()),
582  ":target_class", get_class($target),
583  ":source_class", $class)
584  ->execute();
585 
586  return $items;
587 
588  }
589 
597  static function countBoundItems($target, $class, $constraint = "")
598  {
599  $constraint = preg_replace("/^\\s*WHERE/i", "AND", $constraint);
600  $obj = new $class;
601  $pk = $obj->getPrimaryKey();
602 
603  $items = Query::create($class, "WHERE $pk IN (SELECT target_id FROM binding WHERE id=:source_id and class=:source_class AND target_class=:target_class) $constraint")
604  ->bind(":source_id", $source->get($source->getPrimaryKey()),
605  ":target_class", $class,
606  ":source_class", get_class($source))
607  ->executeValue("COUNT(1)");
608 
609  return $items;
610  }
611 
620  {
621  $constraint = preg_replace("/^\\s*WHERE/i", "AND", $constraint);
622  $obj = new $class;
623  $pk = $obj->getPrimaryKey();
624 
625  $items = Query::create($class, "WHERE $pk IN (SELECT id FROM binding WHERE target_id=:target_id and class=:source_class AND target_class=:target_class) $constraint")
626  ->bind(":target_id", $target->get($target->getPrimaryKey()),
627  ":target_class", get_class($target),
628  ":source_class", $class)
629  ->executeValue("COUNT(1)");
630 
631  return $items;
632 
633  }
634 
643  static function generateSearchConstraint($text, $class, $placeholder = false)
644  {
645  $obj = new $class;
646  $pk = $obj->getPrimaryKey();
647  $text = $placeholder ? $text : "'$text'";
648 
649  return "$pk in (select id from taxonomy_term_association a, taxonomy_term t where t.term like {$text} and a.term_id=t.term_id and a.class='{$class}')";
650  }
651 
652  static function upgradeComponent($version)
653  {
655  $mgr->upgrade($version);
656  }
657 
659  {
660  SerializationManager::registerHandler("taxonomy", "Taxonomies and Terms", new TaxonomySerializationHandler());
661  SerializationManager::registerHandler("taxonomy_associations", "Taxonomy Term Associations", new SimpleSerializationHandler(TaxonomyTermAssociation));
662  return true;
663  }
664 }
665 ?>
$constraint
$form
$tabs
$src
Definition: page.inc:37
$taxonomies
Definition: taxonomies.inc:39
$file
Definition: delete.inc:47
$name
Definition: upload.inc:54
$bindingTarget
Field renderer for handling taxonomy term relationships.
static fireEvent($event, $parameter=null, $mustBeConsumed=false)
Fire an event to all subscribers as detailed in their manifests.
FakoliException is the base exception class for all Fakoli errors.
Definition: core.inc:53
static usingFeature()
Uses the specified framework feature(s).
Definition: core.inc:388
static serialize($class, $constraint="")
Serializes the specified DataItems to XML.
registerHandler($component, $title, $handler)
Registers a serialization handler for a component.
static unserialize($class, $doc, $tx, $save=true)
Instantiates DataItems from the supplied XML document and stores them in the database.
Provides a simple implementation of a SerializationHandler that can serialize a single DataItem class...
TaxonomyManager provides the internal API for working with taxonomies, facets and bindings.
static getBindableClassOptions()
static registerBindableClass($class, $format, $constraint="", $fieldRenderer=BindingTargetFieldRenderer, $facetFilter=BindingTargetFacetFilter)
__construct()
Create a new TaxonomyManager instance.
static getAssociatedTaxonomies($classes, $facet_filter_only=false)
static generateSearchConstraint($text, $class, $placeholder=false)
Generates a search constraint matching the supplied text against taxonomy terms that might be associa...
static getBindableClasses()
static upgradeComponent($version)
static getRegisteredClasses()
Retrieved the list of DataItem classes that have registered as supporting taxonomy term associations.
static getBoundItemsReverse($target, $class, $constraint="")
Same as TaxonomyManager::getBoundItems, but with the reverse relationship.
static getRequestFilterConstraint($class)
static addBindingFieldRenderers($form, $showSelectAll=true, $group=null)
static getBindingFacetFilterClass($bindingTarget)
static getBoundItems($source, $class, $constraint="")
Retrieve items of the specified class that are bound to the specified source object.
static getBindingOptionFormat($bindingTarget)
static addTaxonomyFieldRenderers($form, $showSelectAll=true, $group=null, $limitTo=null)
static getTabs($key)
static getBindingFieldRendererClass($bindingTarget)
static getRegisteredClassOptions()
Retrieves the list of DataItem classes that have registered as supporting taxonomy term associations ...
static countBoundItems($target, $class, $constraint="")
Returns the number of items of the specified class that are bound to the specified source object.
static registerSerializationHandler()
static getBindingOptions($bindingTarget)
static cloneTaxonomy($form)
static getBindingTargets($classes, $facet_filter_only=false)
static getRequestTerms($classes)
static importTerms($taxonomy, $file)
static addFacets($manager, $classes, $dropdown=true, $max_width="200px", $width="200px", $height="120px")
static countBoundItemsReverse($target, $class, $constraint="")
Same as TaxonomyManager::countBoundItems, but with the reverse relationship.
Field renderer for handling taxonomy term relationships.
$clone
$height
Definition: cover.inc:38
$width
Definition: cover.inc:37
$group
Definition: group_form.inc:43
$term_id
Definition: blog.inc:40
$renderer
$term
Definition: term_dialog.inc:46