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

Last change on this file since 21863 was 16007, checked in by grum, 12 years ago

feature:2637- compatibility with Piwigo 2.4

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