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

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

reset new metadata list after updating repository

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