source: extensions/AMetaData/amd_root.class.inc.php @ 6891

Last change on this file since 6891 was 6891, checked in by grum, 14 years ago

Implement metadata search, release 0.5.1
bug:1846, bug:1691

  • Property svn:executable set to *
File size: 22.3 KB
Line 
1<?php
2/*
3 * -----------------------------------------------------------------------------
4 * Plugin Name: Advanced MetaData
5 * -----------------------------------------------------------------------------
6 * Author     : Grum
7 *   email    : grum@piwigo.org
8 *   website  : http://photos.grum.fr
9 *   PWG user : http://forum.piwigo.org/profile.php?id=3706
10 *
11 *   << May the Little SpaceFrog be with you ! >>
12 *
13 * -----------------------------------------------------------------------------
14 *
15 * See main.inc.php for release information
16 *
17 * AMD_install : classe to manage plugin install
18 * ---------------------------------------------------------------------------
19 */
20
21if (!defined('PHPWG_ROOT_PATH')) { die('Hacking attempt!'); }
22
23include_once(PHPWG_PLUGINS_PATH.'GrumPluginClasses/classes/CommonPlugin.class.inc.php');
24include_once(PHPWG_PLUGINS_PATH.'GrumPluginClasses/classes/GPCCss.class.inc.php');
25include_once(PHPWG_PLUGINS_PATH.'GrumPluginClasses/classes/GPCRequestBuilder.class.inc.php');
26
27include_once('amd_jpegmetadata.class.inc.php');
28include_once(JPEG_METADATA_DIR."Common/L10n.class.php");
29include_once(JPEG_METADATA_DIR."TagDefinitions/XmpTags.class.php");
30
31class AMD_root extends CommonPlugin
32{
33  protected $css;   //the css object
34  protected $jpegMD;
35
36  public function __construct($prefixeTable, $filelocation)
37  {
38    global $user;
39    $this->setPluginName("AMetaData");
40    $this->setPluginNameFiles("amd");
41    parent::__construct($prefixeTable, $filelocation);
42
43    $tableList=array(
44      'used_tags',
45      'images_tags',
46      'images',
47      'selected_tags',
48      'groups_names',
49      'groups',
50      'user_tags_label',
51      'user_tags_def',
52      'tags_values');
53    $this->setTablesList($tableList);
54
55    $this->css = new GPCCss(dirname($this->getFileLocation()).'/'.$this->getPluginNameFiles().".css");
56    $this->jpegMD=new AMD_JpegMetaData();
57
58    if(isset($user['language']))
59    {
60      L10n::setLanguage($user['language']);
61    }
62  }
63
64  public function __destruct()
65  {
66    unset($this->jpegMD);
67    unset($this->css);
68    //parent::__destruct();
69  }
70
71  /* ---------------------------------------------------------------------------
72  common AIP & PIP functions
73  --------------------------------------------------------------------------- */
74
75  /* this function initialize var $config with default values */
76  public function initConfig()
77  {
78    $this->config=array(
79      // options set by the plugin interface - don't modify them manually
80      'amd_GetListTags_OrderType' => "tag",
81      'amd_GetListTags_FilterType' => "magic",
82      'amd_GetListTags_ExcludeUnusedTag' => "y",
83      'amd_GetListTags_SelectedTagOnly' => "n",
84      'amd_GetListImages_OrderType' => "value",
85      'amd_AllPicturesAreAnalyzed' => "n",
86      'amd_FillDataBaseContinuously' => "y",
87      'amd_FillDataBaseIgnoreSchemas' => array(),
88      'amd_UseMetaFromHD' => "y",
89
90      // theses options can be set manually
91      'amd_NumberOfItemsPerRequest' => 25,
92      'amd_DisplayWarningsMessageStatus' => "y",
93      'amd_DisplayWarningsMessageUpdate' => "y",
94      'amd_FillDataBaseExcludeTags' => array(),
95      'amd_FillDataBaseExcludeFilters' => array(),
96    );
97    /*
98     * ==> amd_FillDataBaseExcludeTags : array of tagId
99     *     the listed tag are completely excluded by the plugin, as they don't
100     *     exist
101     *     for each tagId you can use generic char as the LIKE sql operator
102     *      array('xmp.%', 'exif.maker.%')
103     *        -> exclude all XMP and EXIF MAKER tags
104     *
105     * ==> amd_FillDataBaseExcludeFilters : array of filterValue
106     *     if you exclude all the xmp tag you probably want to exclude everything
107     *     displaying 'xmp'
108     *     array('exif.maker',
109     *           'exif',
110     *           'iptc',
111     *           'xmp',
112     *           'magic')
113     *
114     * ==> amd_DisplayWarningsMessageStatus : 'y' or 'n'
115     *     amd_DisplayWarningsMessageUpdate
116     *     you can disable warnings messages displayed on the database status&update
117     *     page
118     */
119  }
120
121  public function loadConfig()
122  {
123    parent::loadConfig();
124  }
125
126  public function initEvents()
127  {
128    parent::initEvents();
129  }
130
131  public function getAdminLink($mode='')
132  {
133    if($mode=='ajax')
134    {
135      return('plugins/'.basename(dirname($this->getFileLocation())).'/amd_ajax.php');
136    }
137    else
138    {
139      return(parent::getAdminLink());
140    }
141  }
142
143  /**
144   *
145   */
146  protected function configForTemplate()
147  {
148    global $template;
149
150    $template->assign('amdConfig', $this->config);
151  }
152
153  /**
154   * returns the number of pictures analyzed
155   *
156   * @return Integer
157   */
158  protected function getNumOfPictures()
159  {
160    $numOfPictures=0;
161    $sql="SELECT COUNT(imageId) FROM ".$this->tables['images']."
162            WHERE analyzed='y';";
163    $result=pwg_query($sql);
164    if($result)
165    {
166      while($row=pwg_db_fetch_row($result))
167      {
168        $numOfPictures=$row[0];
169      }
170    }
171    return($numOfPictures);
172  }
173
174
175  /**
176   * this function randomly choose a picture in the list of pictures not
177   * analyzed, and analyze it
178   *
179   */
180  public function doRandomAnalyze()
181  {
182    $sql="SELECT tai.imageId, ti.path, ti.has_high FROM ".$this->tables['images']." tai
183            LEFT JOIN ".IMAGES_TABLE." ti ON tai.imageId = ti.id
184          WHERE tai.analyzed = 'n'
185          ORDER BY RAND() LIMIT 1;";
186    $result=pwg_query($sql);
187    if($result)
188    {
189      // $path = path of piwigo's on the server filesystem
190      $path=dirname(dirname(dirname(__FILE__)));
191
192      while($row=pwg_db_fetch_assoc($result))
193      {
194        if($row['has_high']===true and $this->config['amd_UseMetaFromHD']=='y')
195        {
196          $this->analyzeImageFile($path."/".dirname($row['path'])."/pwg_high/".basename($row['path']), $row['imageId']);
197        }
198        else
199        {
200          $this->analyzeImageFile($path."/".$row['path'], $row['imageId']);
201        }
202      }
203
204      $this->makeStatsConsolidation();
205    }
206  }
207
208
209  /**
210   * this function analyze tags from a picture, and insert the result into the
211   * database
212   *
213   * NOTE : only implemented tags are analyzed and stored
214   *
215   * @param String $fileName : filename of picture to analyze
216   * @param Integer $imageId : id of image in piwigo's database
217   * @param Boolean $loaded  : default = false
218   *                            WARNING
219   *                            if $loaded is set to TRUE, the function assume
220   *                            that the metadata have been alreay loaded
221   *                            do not use the TRUE value if you are not sure
222   *                            of the consequences
223   */
224  protected function analyzeImageFile($fileName, $imageId, $loaded=false)
225  {
226    $schemas=array_flip($this->config['amd_FillDataBaseIgnoreSchemas']);
227    /*
228     * the JpegMetaData object is instancied in the constructor
229     */
230    if(!$loaded)
231    {
232      $this->jpegMD->load(
233        $fileName,
234        Array(
235          'filter' => AMD_JpegMetaData::TAGFILTER_IMPLEMENTED,
236          'optimizeIptcDateTime' => true,
237          'exif' => !isset($schemas['exif']),
238          'iptc' => !isset($schemas['iptc']),
239          'xmp' => !isset($schemas['xmp']),
240          'magic' => !isset($schemas['magic']),
241        )
242      );
243    }
244
245    //$sqlInsert="";
246    $massInsert=array();
247    $nbTags=0;
248    foreach($this->jpegMD->getTags() as $key => $val)
249    {
250      $value=$val->getLabel();
251
252      if($val->isTranslatable())
253        $translatable="y";
254      else
255        $translatable="n";
256
257      if($value instanceof DateTime)
258      {
259        $value=$value->format("Y-m-d H:i:s");
260      }
261      elseif(is_array($value))
262      {
263        /*
264         * array values are stored in a serialized string
265         */
266        $value=serialize($value);
267      }
268
269      $sql="SELECT numId FROM ".$this->tables['used_tags']." WHERE tagId = '$key'";
270
271      $result=pwg_query($sql);
272      if($result)
273      {
274        $numId=-1;
275        while($row=pwg_db_fetch_assoc($result))
276        {
277          $numId=$row['numId'];
278        }
279
280        if($numId>0)
281        {
282          $nbTags++;
283          //if($sqlInsert!="") $sqlInsert.=", ";
284          //$sqlInsert.="($imageId, '$numId', '".addslashes($value)."')";
285          $massInsert[]="('$imageId', '$numId', '".addslashes($value)."') ";
286        }
287      }
288    }
289
290    if(count($massInsert)>0)
291    {
292      $sql="REPLACE INTO ".$this->tables['images_tags']." (imageId, numId, value) VALUES ".implode(", ", $massInsert).";";
293      pwg_query($sql);
294    }
295    //mass_inserts($this->tables['images_tags'], array('imageId', 'numId', 'value'), $massInsert);
296
297    $sql="UPDATE ".$this->tables['images']."
298            SET analyzed = 'y', nbTags=".$nbTags."
299            WHERE imageId=$imageId;";
300    pwg_query($sql);
301
302    unset($massInsert);
303
304    return("$imageId=$nbTags;");
305  }
306
307
308  /**
309   * returns the userDefined tag for one image (without searching in the
310   * database)
311   *
312   * @param Array $numId : array of userDefined numId to get
313   * @param Array $values : array of existing tag for the images
314   * @return Array : associated array of numId=>value
315   */
316  protected function pictureGetUserDefinedTags($listId, $values)
317  {
318    if(count($listId)==0 or count($values)==0) return(array());
319
320    $listIds=implode(',', $listId);
321    $rules=array();
322    $returned=array();
323
324    $sql="SELECT numId, defId, parentId, `order`, `type`, value, conditionType, conditionValue
325          FROM ".$this->tables['user_tags_def']."
326          WHERE numId IN ($listIds)
327          ORDER BY numId, parentId, `order`;";
328    $result=pwg_query($sql);
329    if($result)
330    {
331      while($row=pwg_db_fetch_assoc($result))
332      {
333        $rules[$row['numId']][$row['parentId']][$row['defId']]=$row;
334      }
335    }
336
337    foreach($listId as $numId)
338    {
339      $returned[$numId]=$this->buildUserDefinedTagConditionRule(0, $values, $rules[$numId]);
340    }
341
342    return($returned);
343  }
344
345  /**
346   *
347   * @param String $id : id of the metadata to build
348   */
349  protected function buildUserDefinedTags($id)
350  {
351    $num=0;
352    $sql="SELECT GROUP_CONCAT(DISTINCT value ORDER BY value SEPARATOR ',')
353          FROM ".$this->tables['user_tags_def']."
354          WHERE `type`='C' or `type`='M'
355            AND numId='$id'";
356    $result=pwg_query($sql);
357    if($result)
358    {
359      // get the list of tags used to build the user defined tag
360      $list='';
361      while($row=pwg_db_fetch_row($result))
362      {
363        $list=$row[0];
364      }
365
366      $sql="(SELECT ait.imageId, ait.numId, ait.value
367             FROM ".$this->tables['images_tags']." ait
368             WHERE ait.numId IN ($list)
369            )
370            UNION
371            (SELECT pai.imageId, 0, ''
372            FROM ".$this->tables['images']." pai)
373            ORDER BY imageId, numId";
374      $result=pwg_query($sql);
375      if($result)
376      {
377        //build a list of properties for each image
378        $images=array();
379        while($row=pwg_db_fetch_assoc($result))
380        {
381          if(!array_key_exists($row['imageId'], $images))
382          {
383            $images[$row['imageId']]=array();
384          }
385          $images[$row['imageId']][$row['numId']]=$row['value'];
386        }
387
388        //load the rules
389        $sql="SELECT defId, parentId, `order`, `type`, value, conditionType, conditionValue
390              FROM ".$this->tables['user_tags_def']."
391              WHERE numId='$id'
392              ORDER BY parentId, `order`;";
393        $result=pwg_query($sql);
394        if($result)
395        {
396          $rules=array();
397          while($row=pwg_db_fetch_assoc($result))
398          {
399            $rules[$row['parentId']][$row['defId']]=$row;
400          }
401
402          $inserts=array();
403          // calculate tag values for each image
404          foreach($images as $key=>$val)
405          {
406            $buildValue=$this->buildUserDefinedTag($key, $val, $id, $rules);
407
408            if(!is_null($buildValue['value']))
409            {
410              $buildValue['value']=addslashes($buildValue['value']);
411              $inserts[]=$buildValue;
412              $num++;
413            }
414          }
415
416          mass_inserts($this->tables['images_tags'], array('imageId', 'numId', 'value'), $inserts);
417        }
418      }
419    }
420    return($num);
421  }
422
423
424  /**
425   * build the userDefined tag for an image
426   *
427   * @param String $imageId : id of the image
428   * @param Array $values : array of existing tag for the images
429   * @param String $numId : id of the metadata to build
430   * @param Array $rules  : rules to apply to build the metadata
431   */
432  protected function buildUserDefinedTag($imageId, $values, $numId, $rules)
433  {
434    $returned=array(
435      'imageId' => $imageId,
436      'numId' => $numId,
437      'value' => $this->buildUserDefinedTagConditionRule(0, $values, $rules)
438    );
439
440    return($returned);
441  }
442
443
444  /**
445   * build the userDefined tag for an image
446   *
447   * @param String $imageId : id of the image
448   * @param Array $values : array of existing tag for the images
449   * @param String $numId : id of the metadata to build
450   * @param Array $rules  : rules to apply to build the metadata
451   */
452  protected function buildUserDefinedTagConditionRule($parentId, $values, $rules)
453  {
454    $null=true;
455    $returned='';
456    foreach($rules[$parentId] as $rule)
457    {
458      switch($rule['type'])
459      {
460        case 'T':
461          $returned.=$rule['value'];
462          $null=false;
463          break;
464        case 'M':
465          if(isset($values[$rule['value']]))
466          {
467            $returned.=$values[$rule['value']];
468            $null=false;
469          }
470          break;
471        case 'C':
472          $ok=false;
473          switch($rule['conditionType'])
474          {
475            case 'E':
476              if(isset($values[$rule['value']])) $ok=true;
477              break;
478            case '!E':
479              if(!isset($values[$rule['value']])) $ok=true;
480              break;
481            case '=':
482              if(isset($values[$rule['value']]) and
483                 $values[$rule['value']]==$rule['conditionValue']) $ok=true;
484              break;
485            case '!=':
486              if(isset($values[$rule['value']]) and
487                 $values[$rule['value']]!=$rule['conditionValue']) $ok=true;
488              break;
489            case '%':
490              if(isset($values[$rule['value']]) and
491                 preg_match('/'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
492              break;
493            case '!%':
494              if(isset($values[$rule['value']]) and
495                 !preg_match('/'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
496              break;
497            case '^%':
498              if(isset($values[$rule['value']]) and
499                 preg_match('/^'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
500              break;
501            case '!^%':
502              if(isset($values[$rule['value']]) and
503                 !preg_match('/^'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
504              break;
505            case '$%':
506              if(isset($values[$rule['value']]) and
507                 preg_match('/'.$rule['conditionValue'].'$/i', $values[$rule['value']])) $ok=true;
508              break;
509            case '!$%':
510              if(isset($values[$rule['value']]) and
511                 !preg_match('/'.$rule['conditionValue'].'$/i', $values[$rule['value']])) $ok=true;
512              break;
513          }
514          if($ok)
515          {
516            $subRule=$this->buildUserDefinedTagConditionRule($rule['defId'], $values, $rules);
517            if(!is_null($subRule))
518            {
519              $null=false;
520              $returned.=$subRule;
521            }
522          }
523          break;
524      }
525    }
526    if($null)
527    {
528      return(null);
529    }
530    return($returned);
531  }
532
533
534
535
536  /**
537   * do some consolidations on database to optimize other requests
538   *
539   */
540  protected function makeStatsConsolidation()
541  {
542    // reset numbers
543    $sql="UPDATE ".$this->tables['used_tags']." ut
544          SET ut.numOfImg = 0;";
545    pwg_query($sql);
546
547    $sql="UPDATE ".$this->tables['images']." pai
548          SET pai.nbTags = 0;";
549    pwg_query($sql);
550
551
552    // count number of images per tag
553    $sql="UPDATE ".$this->tables['used_tags']." ut,
554            (SELECT COUNT(DISTINCT imageId) AS nb, numId
555              FROM ".$this->tables['images_tags']."
556              GROUP BY numId) nb
557          SET ut.numOfImg = nb.nb
558          WHERE ut.numId = nb.numId;";
559    pwg_query($sql);
560
561    //count number of tags per images
562    $sql="UPDATE ".$this->tables['images']." pai,
563            (SELECT COUNT(DISTINCT numId) AS nb, imageId
564              FROM ".$this->tables['images_tags']."
565              GROUP BY imageId) nb
566          SET pai.nbTags = nb.nb
567          WHERE pai.imageId = nb.imageId;";
568    pwg_query($sql);
569
570
571    $sql="SELECT COUNT(imageId) AS nb
572          FROM ".$this->tables['images']."
573          WHERE analyzed = 'n';";
574    $result=pwg_query($sql);
575    if($result)
576    {
577      while($row=pwg_db_fetch_assoc($result))
578      {
579        $this->config['amd_AllPicturesAreAnalyzed']=($row['nb']==0)?'y':'n';
580      }
581
582    }
583    $this->saveConfig();
584  }
585
586  /**
587   * This function :
588   *  - convert arrays (stored as a serialized string) into human readable string
589   *  - translate value in user language (if value is translatable)
590   *
591   * @param String $value         : value to prepare
592   * @param Boolean $translatable : set to tru if the value can be translated in
593   *                                the user language
594   * @param String $separator     : separator for arrays items
595   * @return String               : the value prepared
596   */
597  static public function prepareValueForDisplay($value, $translatable=true, $separator=", ")
598  {
599    global $user;
600
601    if(preg_match('/^a:\d+:\{.*\}$/is', $value))
602    {
603      // $value is a serialized array
604      $tmp=unserialize($value);
605
606      if(count($tmp)==0)
607      {
608        return(L10n::get("Unknown"));
609      }
610
611      if(array_key_exists("computed", $tmp) and array_key_exists("detail", $tmp))
612      {
613        /* keys 'computed' and 'detail' are present
614         *
615         * assume this is the 'exif.exif.Flash' metadata and return the computed
616         * value only
617         */
618        return(L10n::get($tmp['computed']));
619      }
620      elseif(array_key_exists("type", $tmp) and array_key_exists("values", $tmp))
621      {
622        /* keys 'computed' and 'detail' are present
623         *
624         * assume this is an Xmp 'ALT', 'BAG' or 'SEQ' metadata and return the
625         * values only
626         */
627        if($tmp['type']=='alt')
628        {
629          /* 'ALT' structure
630           *
631           * ==> assuming the structure is used only for multi language values
632           *
633           * Array(
634           *    'type'   => 'ALT'
635           *    'values' =>
636           *        Array(
637           *            Array(
638           *                'type'  => Array(
639           *                            'name'  =>'xml:lang',
640           *                            'value' => ''           // language code
641           *                           )
642           *               'value' => ''         //value in the defined language
643           *            ),
644           *
645           *            Array(
646           *                // data2
647           *            ),
648           *
649           *        )
650           * )
651           */
652          $tmp=XmpTags::getAltValue($tmp, $user['language']);
653          if(trim($tmp)=="") $tmp="(".L10n::get("not defined").")";
654
655          return($tmp);
656        }
657        else
658        {
659          /* 'SEQ' or 'BAG' structure
660           *
661           *  Array(
662           *    'type'   => 'XXX',
663           *    'values' => Array(val1, val2, .., valN)
664           *  )
665           */
666          $tmp=$tmp['values'];
667
668          if(trim(implode("", $tmp))=="")
669          {
670            return("(".L10n::get("not defined").")");
671          }
672        }
673      }
674
675
676      foreach($tmp as $key=>$val)
677      {
678        if(is_array($val))
679        {
680          if($translatable)
681          {
682            foreach($val as $key2=>$val2)
683            {
684              $tmp[$key][$key2]=L10n::get($val2);
685            }
686            if(count($val)>0)
687            {
688              $tmp[$key]="[".implode($separator, $val)."]";
689            }
690            else
691            {
692              unset($tmp[$key]);
693            }
694          }
695        }
696        else
697        {
698          if($translatable)
699          {
700            $tmp[$key]=L10n::get($val);
701          }
702        }
703      }
704      return(implode($separator, $tmp));
705    }
706    elseif(preg_match('/\d{1,3}°\s\d{1,2}\'\s(\d{1,2}\.{0,1}\d{0,2}){0,1}.,\s(north|south|east|west)$/i', $value))
707    {
708      /* \d{1,3}°\s\d{1,2}\'\s(\d{1,2}\.{0,1}\d{0,2}){0,1}.
709       *
710       * keys 'coord' and 'card' are present
711       *
712       * assume this is a GPS coordinate
713       */
714        return(preg_replace(
715          Array('/, north$/i', '/, south$/i', '/, east$/i', '/, west$/i'),
716          Array(" ".L10n::get("North"), " ".L10n::get("South"), " ".L10n::get("East"), " ".L10n::get("West")),
717          $value)
718        );
719    }
720    else
721    {
722      if(trim($value)=="")
723      {
724        return("(".L10n::get("not defined").")");
725      }
726
727      if(strpos($value, "|")>0)
728      {
729        $value=explode("|", $value);
730        if($translatable)
731        {
732          foreach($value as $key=>$val)
733          {
734            $value[$key]=L10n::get($val);
735          }
736        }
737        return(implode("", $value));
738      }
739
740      if($translatable)
741      {
742        return(L10n::get($value));
743      }
744      return($value);
745    }
746  }
747
748
749} // amd_root  class
750
751
752
753Class AMD_functions {
754  /**
755   *  return all HTML&JS code necessary to display a dialogbox to choose
756   *  a metadata
757   */
758  static public function dialogBoxMetadata()
759  {
760    global $template, $prefixeTable;
761
762    $tables=array(
763        'used_tags' => $prefixeTable.'amd_used_tags',
764        'selected_tags' => $prefixeTable.'amd_selected_tags',
765    );
766
767    $template->set_filename('metadata_choose',
768                  dirname(__FILE__).'/templates/amd_dialog_metadata_choose.tpl');
769
770    $datas=array(
771      'urlRequest' => 'plugins/'.basename(dirname(__FILE__)).'/amd_ajax.php',
772      'tagList' => array(),
773    );
774
775    /*
776     * build tagList
777     */
778    $sql="SELECT ut.name, ut.numId, ut.tagId
779          FROM ".$tables['used_tags']." ut
780            JOIN ".$tables['selected_tags']." st ON st.tagId = ut.tagId
781          ORDER BY tagId";
782    $result=pwg_query($sql);
783    if($result)
784    {
785      while($row=pwg_db_fetch_assoc($result))
786      {
787        $datas['tagList'][]=Array(
788          'tagId' => $row['tagId'],
789          'name'  => L10n::get($row['name']),
790          'numId' => $row['numId']
791        );
792      }
793    }
794
795    $template->assign('datas', $datas);
796    unset($data);
797
798    return($template->parse('metadata_choose', true));
799  }
800}
801
802
803
804?>
Note: See TracBrowser for help on using the repository browser.