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

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

Using metadata on HD picture (fix bug and forgotten files), release 0.5.1
bug:1846

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