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

Revision 6729, 19.7 KB checked in by grum, 10 years ago (diff)

feature:1777

  • Weight of the metadata database can becomes very heavy
  • Property svn:executable set to *
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 userDefeined 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    $listIds=implode(',', $listId);
309    $rules=array();
310    $returned=array();
311
312    $sql="SELECT numId, defId, parentId, `order`, `type`, value, conditionType, conditionValue
313          FROM ".$this->tables['user_tags_def']."
314          WHERE numId IN ($listIds)
315          ORDER BY numId, parentId, `order`;";
316    $result=pwg_query($sql);
317    if($result)
318    {
319      while($row=pwg_db_fetch_assoc($result))
320      {
321        $rules[$row['numId']][$row['parentId']][$row['defId']]=$row;
322      }
323    }
324
325    foreach($listId as $numId)
326    {
327      $returned[$numId]=$this->buildUserDefinedTagConditionRule(0, $values, $rules[$numId]);
328    }
329
330    return($returned);
331  }
332
333  /**
334   *
335   * @param String $id : id of the metadata to build
336   */
337  protected function buildUserDefinedTags($id)
338  {
339    $num=0;
340    $sql="SELECT GROUP_CONCAT(DISTINCT value ORDER BY value SEPARATOR ',')
341          FROM ".$this->tables['user_tags_def']."
342          WHERE `type`='C' or `type`='M'
343            AND numId='$id'";
344    $result=pwg_query($sql);
345    if($result)
346    {
347      // get the list of tags used to build the user defined tag
348      $list='';
349      while($row=pwg_db_fetch_row($result))
350      {
351        $list=$row[0];
352      }
353
354      $sql="(SELECT ait.imageId, ait.numId, ait.value
355             FROM ".$this->tables['images_tags']." ait
356             WHERE ait.numId IN ($list)
357            )
358            UNION
359            (SELECT pai.imageId, 0, ''
360            FROM ".$this->tables['images']." pai)
361            ORDER BY imageId, numId";
362      $result=pwg_query($sql);
363      if($result)
364      {
365        //build a list of properties for each image
366        $images=array();
367        while($row=pwg_db_fetch_assoc($result))
368        {
369          if(!array_key_exists($row['imageId'], $images))
370          {
371            $images[$row['imageId']]=array();
372          }
373          $images[$row['imageId']][$row['numId']]=$row['value'];
374        }
375
376        //load the rules
377        $sql="SELECT defId, parentId, `order`, `type`, value, conditionType, conditionValue
378              FROM ".$this->tables['user_tags_def']."
379              WHERE numId='$id'
380              ORDER BY parentId, `order`;";
381        $result=pwg_query($sql);
382        if($result)
383        {
384          $rules=array();
385          while($row=pwg_db_fetch_assoc($result))
386          {
387            $rules[$row['parentId']][$row['defId']]=$row;
388          }
389
390          $inserts=array();
391          // calculate tag values for each image
392          foreach($images as $key=>$val)
393          {
394            $buildValue=$this->buildUserDefinedTag($key, $val, $id, $rules);
395
396            if(!is_null($buildValue['value']))
397            {
398              $inserts[]=$buildValue;
399              $num++;
400            }
401          }
402
403          mass_inserts($this->tables['images_tags'], array('imageId', 'numId', 'value'), $inserts);
404        }
405      }
406    }
407    return($num);
408  }
409
410
411  /**
412   * build the userDefined tag for an image
413   *
414   * @param String $imageId : id of the image
415   * @param Array $values : array of existing tag for the images
416   * @param String $numId : id of the metadata to build
417   * @param Array $rules  : rules to apply to build the metadata
418   */
419  protected function buildUserDefinedTag($imageId, $values, $numId, $rules)
420  {
421    $returned=array(
422      'imageId' => $imageId,
423      'numId' => $numId,
424      'value' => $this->buildUserDefinedTagConditionRule(0, $values, $rules)
425    );
426
427    return($returned);
428  }
429
430
431  /**
432   * build the userDefined tag for an image
433   *
434   * @param String $imageId : id of the image
435   * @param Array $values : array of existing tag for the images
436   * @param String $numId : id of the metadata to build
437   * @param Array $rules  : rules to apply to build the metadata
438   */
439  protected function buildUserDefinedTagConditionRule($parentId, $values, $rules)
440  {
441    $null=true;
442    $returned='';
443    foreach($rules[$parentId] as $rule)
444    {
445      switch($rule['type'])
446      {
447        case 'T':
448          $returned.=$rule['value'];
449          $null=false;
450          break;
451        case 'M':
452          if(isset($values[$rule['value']]))
453          {
454            $returned.=$values[$rule['value']];
455            $null=false;
456          }
457          break;
458        case 'C':
459          $ok=false;
460          switch($rule['conditionType'])
461          {
462            case 'E':
463              if(isset($values[$rule['value']])) $ok=true;
464              break;
465            case '!E':
466              if(!isset($values[$rule['value']])) $ok=true;
467              break;
468            case '=':
469              if(isset($values[$rule['value']]) and
470                 $values[$rule['value']]==$rule['conditionValue']) $ok=true;
471              break;
472            case '!=':
473              if(isset($values[$rule['value']]) and
474                 $values[$rule['value']]!=$rule['conditionValue']) $ok=true;
475              break;
476            case '%':
477              if(isset($values[$rule['value']]) and
478                 preg_match('/'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
479              break;
480            case '!%':
481              if(isset($values[$rule['value']]) and
482                 !preg_match('/'.$rule['conditionValue'].'/i', $values[$rule['value']])) $ok=true;
483              break;
484          }
485          if($ok)
486          {
487            $subRule=$this->buildUserDefinedTagConditionRule($rule['defId'], $values, $rules);
488            if(!is_null($subRule))
489            {
490              $null=false;
491              $returned.=$subRule;
492            }
493          }
494          break;
495      }
496    }
497    if($null)
498    {
499      return(null);
500    }
501    return($returned);
502  }
503
504
505
506
507  /**
508   * do some consolidations on database to optimize other requests
509   *
510   */
511  protected function makeStatsConsolidation()
512  {
513    // reset numbers
514    $sql="UPDATE ".$this->tables['used_tags']." ut
515          SET ut.numOfImg = 0;";
516    pwg_query($sql);
517
518    $sql="UPDATE ".$this->tables['images']." pai
519          SET pai.nbTags = 0;";
520    pwg_query($sql);
521
522
523    // count number of images per tag
524    $sql="UPDATE ".$this->tables['used_tags']." ut,
525            (SELECT COUNT(DISTINCT imageId) AS nb, numId
526              FROM ".$this->tables['images_tags']."
527              GROUP BY numId) nb
528          SET ut.numOfImg = nb.nb
529          WHERE ut.numId = nb.numId;";
530    pwg_query($sql);
531
532    //count number of tags per images
533    $sql="UPDATE ".$this->tables['images']." pai,
534            (SELECT COUNT(DISTINCT numId) AS nb, imageId
535              FROM ".$this->tables['images_tags']."
536              GROUP BY imageId) nb
537          SET pai.nbTags = nb.nb
538          WHERE pai.imageId = nb.imageId;";
539    pwg_query($sql);
540
541
542    $sql="SELECT COUNT(imageId) AS nb
543          FROM ".$this->tables['images']."
544          WHERE analyzed = 'n';";
545    $result=pwg_query($sql);
546    if($result)
547    {
548      while($row=pwg_db_fetch_assoc($result))
549      {
550        $this->config['amd_AllPicturesAreAnalyzed']=($row['nb']==0)?'y':'n';
551      }
552
553    }
554    $this->saveConfig();
555  }
556
557
558  /**
559   * This function :
560   *  - convert arrays (stored as a serialized string) into human readable string
561   *  - translate value in user language (if value is translatable)
562   *
563   * @param String $value         : value to prepare
564   * @param Boolean $translatable : set to tru if the value can be translated in
565   *                                the user language
566   * @param String $separator     : separator for arrays items
567   * @return String               : the value prepared
568   */
569  protected function prepareValueForDisplay($value, $translatable=true, $separator=", ")
570  {
571    global $user;
572
573    if(preg_match('/^a:\d+:\{.*\}$/is', $value))
574    {
575      // $value is a serialized array
576      $tmp=unserialize($value);
577
578      if(count($tmp)==0)
579      {
580        return(L10n::get("Unknown"));
581      }
582
583      if(array_key_exists("computed", $tmp) and array_key_exists("detail", $tmp))
584      {
585        /* keys 'computed' and 'detail' are present
586         *
587         * assume this is the 'exif.exif.Flash' metadata and return the computed
588         * value only
589         */
590        return(L10n::get($tmp['computed']));
591      }
592      elseif(array_key_exists("type", $tmp) and array_key_exists("values", $tmp))
593      {
594        /* keys 'computed' and 'detail' are present
595         *
596         * assume this is an Xmp 'ALT', 'BAG' or 'SEQ' metadata and return the
597         * values only
598         */
599        if($tmp['type']=='alt')
600        {
601          /* 'ALT' structure
602           *
603           * ==> assuming the structure is used only for multi language values
604           *
605           * Array(
606           *    'type'   => 'ALT'
607           *    'values' =>
608           *        Array(
609           *            Array(
610           *                'type'  => Array(
611           *                            'name'  =>'xml:lang',
612           *                            'value' => ''           // language code
613           *                           )
614           *               'value' => ''         //value in the defined language
615           *            ),
616           *
617           *            Array(
618           *                // data2
619           *            ),
620           *
621           *        )
622           * )
623           */
624          $tmp=XmpTags::getAltValue($tmp, $user['language']);
625          if(trim($tmp)=="") $tmp="(".L10n::get("not defined").")";
626
627          return($tmp);
628        }
629        else
630        {
631          /* 'SEQ' or 'BAG' structure
632           *
633           *  Array(
634           *    'type'   => 'XXX',
635           *    'values' => Array(val1, val2, .., valN)
636           *  )
637           */
638          $tmp=$tmp['values'];
639
640          if(trim(implode("", $tmp))=="")
641          {
642            return("(".L10n::get("not defined").")");
643          }
644        }
645      }
646
647
648      foreach($tmp as $key=>$val)
649      {
650        if(is_array($val))
651        {
652          if($translatable)
653          {
654            foreach($val as $key2=>$val2)
655            {
656              $tmp[$key][$key2]=L10n::get($val2);
657            }
658            if(count($val)>0)
659            {
660              $tmp[$key]="[".implode($separator, $val)."]";
661            }
662            else
663            {
664              unset($tmp[$key]);
665            }
666          }
667        }
668        else
669        {
670          if($translatable)
671          {
672            $tmp[$key]=L10n::get($val);
673          }
674        }
675      }
676      return(implode($separator, $tmp));
677    }
678    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))
679    {
680      /* \d{1,3}°\s\d{1,2}\'\s(\d{1,2}\.{0,1}\d{0,2}){0,1}.
681       *
682       * keys 'coord' and 'card' are present
683       *
684       * assume this is a GPS coordinate
685       */
686        return(preg_replace(
687          Array('/, north$/i', '/, south$/i', '/, east$/i', '/, west$/i'),
688          Array(" ".L10n::get("North"), " ".L10n::get("South"), " ".L10n::get("East"), " ".L10n::get("West")),
689          $value)
690        );
691    }
692    else
693    {
694      if(trim($value)=="")
695      {
696        return("(".L10n::get("not defined").")");
697      }
698
699      if(strpos($value, "|")>0)
700      {
701        $value=explode("|", $value);
702        if($translatable)
703        {
704          foreach($value as $key=>$val)
705          {
706            $value[$key]=L10n::get($val);
707          }
708        }
709        return(implode("", $value));
710      }
711
712      if($translatable)
713      {
714        return(L10n::get($value));
715      }
716      return($value);
717    }
718  }
719
720} // amd_root  class
721
722
723
724?>
Note: See TracBrowser for help on using the repository browser.