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

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

fix bug when there is no personnal metadata

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