source: extensions/BatchDownloader/include/BatchDownloader.class.php @ 17880

Revision 17880, 17.7 KB checked in by mistic100, 7 years ago (diff)
  • now archive comment is well added
  • add PclZip library for servers without ZipArchive
Line 
1<?php
2defined('BATCH_DOWNLOAD_PATH') or die('Hacking attempt!');
3
4class BatchDownloader
5{
6  private $conf;
7  private $data;
8  private $images;
9 
10  /**
11   * __construct
12   * @param: mixed set id (##|'new')
13   * @param: array images
14   * @param: string set type ('album'|'tag'|'selection')
15   * @param: int set type id (for retrieving album infos for instance)
16   */
17  function __construct($set_id, $images=array(), $type=null, $type_id=null)
18  {
19    global $user, $conf;
20   
21    $this->conf = $conf['batch_download'];
22    $this->data = array(
23      'id' => 0,
24      'user_id' => $user['id'],
25      'date_creation' => '0000-00-00 00:00:00',
26      'type' => null,
27      'type_id' => null,
28      'nb_zip' => 0,
29      'last_zip' => 0,
30      'nb_images' => 0,
31      'total_size' => 0,
32      'status' => 'new',
33      );
34    $this->images = array();
35   
36    // load specific set
37    if (preg_match('#^[0-9]+$#', $set_id))
38    {
39      $query = '
40SELECT
41    id,
42    user_id,
43    date_creation,
44    type,
45    type_id,
46    nb_zip,
47    last_zip,
48    nb_images,
49    total_size,
50    status
51  FROM '.BATCH_DOWNLOAD_TSETS.'
52  WHERE
53    id = '.$set_id.'
54    '.(!is_admin() ? 'AND user_id = '.$this->data['user_id'] : null).'
55;';
56      $result = pwg_query($query);
57     
58      if (pwg_db_num_rows($result))
59      {
60        $this->data = array_merge(
61          $this->data,
62          pwg_db_fetch_assoc($result)
63          );
64       
65        // make sur all pictures of the set exist
66        $query = '
67DELETE FROM '.BATCH_DOWNLOAD_TIMAGES.'
68  WHERE image_id NOT IN (
69    SELECT id FROM '.IMAGES_TABLE.'
70    )
71;';
72        pwg_query($query);
73     
74        $query = '
75SELECT
76    image_id,
77    zip
78  FROM '.BATCH_DOWNLOAD_TIMAGES.'
79  WHERE set_id = '.$this->data['id'].'
80;';
81        $this->images = simple_hash_from_query($query, 'image_id', 'zip');
82       
83        if ( $this->data['status'] != 'done' and count($this->images) != $this->data['nb_images'] )
84        {
85          $this->updateParam('nb_images', count($this->images));
86        }
87      }
88      else
89      {
90        throw new Exception(l10n('Invalid dowload set'));
91      }
92    }
93    // create a new set
94    else if ($set_id == 'new')
95    {
96      $this->data['type'] = $type;
97      $this->data['type_id'] = $type_id;
98     
99      $query = '
100INSERT INTO '.BATCH_DOWNLOAD_TSETS.'(
101    user_id,
102    date_creation,
103    type,
104    type_id,
105    nb_zip,
106    last_zip,
107    nb_images,
108    total_size,
109    status
110  )
111  VALUES(
112    '.$this->data['user_id'].',
113    NOW(),
114    "'.$this->data['type'].'",
115    "'.$this->data['type_id'].'",
116    0,
117    0,
118    0,
119    0,
120    "new"
121  )
122;';
123      pwg_query($query);
124      $this->data['id'] = pwg_db_insert_id();
125     
126      $date = pwg_query('SELECT FROM_UNIXTIME(NOW());');
127      list($this->data['date_creation']) = pwg_db_fetch_row($date);
128     
129      if (!empty($images))
130      {
131        $this->addImages($images);
132      }
133    }
134    else
135    {
136      trigger_error('BatchDownloader::__construct, invalid input parameter', E_USER_ERROR);
137    }
138  }
139 
140  /**
141   * updateParam
142   * @param: string param name
143   * @param: mixed param value
144   */
145  function updateParam($name, $value)
146  {
147    $this->data[$name] = $value;
148    pwg_query('UPDATE '.BATCH_DOWNLOAD_TSETS.' SET '.$name.' = "'.$value.'" WHERE id = '.$this->data['id'].';');
149  }
150 
151  /**
152   * getParam
153   * @param: string param name
154   * @return: mixed param value
155   */
156  function getParam($name)
157  {
158    return $this->data[$name];
159  }
160 
161  /**
162   * getImages
163   * @return: array
164   */
165  function getImages()
166  {
167    return $this->images;
168  }
169 
170  /**
171   * isInSet
172   * @param: int image id
173   * @return: bool
174   */
175  function isInSet($image_id)
176  {
177    return array_key_exists($image_id, $this->images);
178  }
179 
180  /**
181   * removeImages
182   * @param: array image ids
183   */
184  function removeImages($image_ids)
185  {
186    if (empty($image_ids) or !is_array($image_ids)) return;
187   
188    foreach ($image_ids as $image_id)
189    {
190      unset($this->images[ $image_id ]);
191    }
192   
193    $query = '
194DELETE FROM '.BATCH_DOWNLOAD_TIMAGES.'
195  WHERE
196    set_id = '.$this->data['id'].'
197    AND image_id IN('.implode(',', $image_ids).')
198;';
199    pwg_query($query);
200   
201    $this->updateParam('nb_images', count($this->images));
202  }
203 
204  /**
205   * addImages
206   * @param: array image ids
207   */
208  function addImages($image_ids)
209  {
210    if (empty($image_ids) or !is_array($image_ids)) return;
211   
212    $image_ids = array_unique($image_ids);
213    $inserts = array();
214   
215    foreach ($image_ids as $image_id)
216    {
217      if ($this->isInSet($image_id)) continue;
218     
219      $this->images[ $image_id ] = 0;
220      array_push($inserts, array('set_id'=>$this->data['id'], 'image_id'=>$image_id, 'zip'=>0));
221    }
222   
223    mass_inserts(
224      BATCH_DOWNLOAD_TIMAGES,
225      array('set_id', 'image_id', 'zip'),
226      $inserts
227      );
228     
229    $this->updateParam('nb_images', count($this->images));
230  }
231 
232  /**
233   * clearImages
234   */
235  function clearImages()
236  {
237    $this->images = array();
238   
239    $query = '
240DELETE FROM '.BATCH_DOWNLOAD_TIMAGES.'
241  WHERE set_id = '.$this->data['id'].'
242;';
243    pwg_query($query);
244  }
245 
246  /**
247   * deleteLastArchive
248   */
249  function deleteLastArchive()
250  {
251    $zip_path = $this->getArchivePath();
252    if (file_exists($zip_path))
253    {
254      unlink($zip_path);
255    }
256  }
257 
258  /**
259   * createNextArchive
260   * @param: bool force all elements in one archive
261   * @return: string zip path or false
262   */
263  function createNextArchive($force_one_archive=false)
264  {
265    // set already downloaded (we should never be there !)
266    if ( $this->data['status'] == 'done' or $this->data['nb_images'] == 0 )
267    {
268      trigger_error('BatchDownloader::createNextArchive, the set is empty', E_USER_ERROR);
269    }
270   
271    global $conf;
272   
273    // first zip
274    if ($this->data['last_zip'] == 0)
275    {
276      $this->updateParam('status', 'download');
277     
278      // limit number of elements
279      if ($this->data['nb_images'] > $this->conf['max_elements'])
280      {
281        $images_ids = array_slice(array_keys($this->images), 0, $this->conf['max_elements']);
282        $this->clearImages();
283        $this->addImages($images_ids);
284      }
285     
286      $this->getEstimatedArchiveNumber();
287     
288      $this->updateParam('date_creation', date('Y-m-d H:i:s'));
289    }
290   
291    // get next images of the set
292    $images_to_add = array();
293    foreach ($this->images as $image_id => $zip_id)
294    {
295      if ($zip_id != 0) continue; // here are already added images
296      array_push($images_to_add, $image_id);
297    }
298   
299    if (count($images_to_add))
300    {
301      $query = '
302SELECT
303    id,
304    name,
305    file,
306    path,
307    filesize
308  FROM '.IMAGES_TABLE.'
309  WHERE id IN ('.implode(',', $images_to_add).')
310;';
311      $images_to_add = hash_from_query($query, 'id');
312     
313      // open zip
314      $this->updateParam('last_zip', $this->data['last_zip']+1);
315      $zip_path = $this->getArchivePath();
316     
317      $zip = new myZip($zip_path, isset($conf['batch_download_force_pclzip']));
318     
319      // add images until size limit is reach, or all images are added
320      $images_added = array();
321      $total_size = 0;
322      foreach ($images_to_add as $row)
323      {       
324        $zip->addFile(PHPWG_ROOT_PATH . $row['path'], $row['id'].'_'.get_filename_wo_extension($row['file']).'.'.get_extension($row['path']));
325       
326        array_push($images_added, $row['id']);
327        $this->images[ $row['id'] ] = $this->data['last_zip'];
328       
329        $total_size+= $row['filesize'];
330        if ($total_size >= $this->conf['max_size']*1024 and !$force_one_archive) break;
331      }
332     
333      $this->updateParam('total_size', $this->data['total_size'] + $total_size);
334     
335      // archive comment
336      $comment = 'Generated on '.date('r').' with PHP '.PHP_VERSION.' by Piwigo Batch Downloader.';
337      $comment.= "\n".$conf['gallery_title'].' - '.get_absolute_root_url();
338      if (!empty($conf['batch_download_comment']))
339      {
340        $comment.= "\n\n".wordwrap(remove_accents($conf['batch_download_comment']), 60);
341      }
342      $zip->setArchiveComment($comment);
343     
344      $zip->close();
345     
346      // update database
347      $query = '
348UPDATE '.BATCH_DOWNLOAD_TIMAGES.'
349  SET zip = '.$this->data['last_zip'].'
350  WHERE
351    set_id = '.$this->data['id'].'
352    AND image_id IN('.implode(',', $images_added).')
353;';
354      pwg_query($query);
355     
356      // all images added ?
357      if (count($images_to_add) == count($images_added))
358      {
359        $this->updateParam('status', 'done');
360      }
361     
362      // over estimed
363      if ( $this->data['status'] == 'done' and $this->data['last_zip'] < $this->data['nb_zip'] )
364      {
365        $this->updateParam('nb_zip', $this->data['last_zip']);
366      }
367      // under estimed
368      else if ( $this->data['last_zip'] == $this->data['nb_zip'] and $this->data['status'] != 'done' )
369      {
370        $this->updateParam('nb_zip', $this->data['last_zip']+1);
371      }
372     
373      return $zip_path;
374    }
375    else
376    {
377      return false;
378    }
379  }
380 
381  /**
382   * getEstimatedTotalSize
383   * @return: int
384   */
385  function getEstimatedTotalSize()
386  {
387    if ($this->data['status'] == 'done') return $this->data['total_size'];
388    if ($this->data['nb_images'] == 0) return 0;
389   
390    $image_ids = array_slice(array_keys($this->images), 0, $this->conf['max_elements']);
391   
392    $query = '
393SELECT SUM(filesize) AS total
394  FROM '.IMAGES_TABLE.'
395  WHERE id IN ('.implode(',', $image_ids).')
396;';
397    list($total) = pwg_db_fetch_row(pwg_query($query));
398    return $total;
399  }
400 
401  /**
402   * getEstimatedArchiveNumber
403   * @return: int
404   */
405  function getEstimatedArchiveNumber()
406  {
407    $nb_zip = ceil( $this->getEstimatedTotalSize() / ($this->conf['max_size']*1024) );
408    $this->updateParam('nb_zip', $nb_zip);
409    return $nb_zip;
410  }
411 
412  /**
413   * getDownloadList
414   * @return: string html
415   */
416  function getDownloadList($url='')
417  {
418    if ($this->data['nb_images'] == 0)
419    {
420      return '<b>'.l10n('No archive').'</b>';
421    }
422   
423    $root_url = get_root_url();
424   
425    $out = '';
426    for ($i=1; $i<=$this->data['nb_zip']; $i++)
427    {
428      $out.= '<li id="zip-'.$i.'">';
429     
430      if ($this->data['status'] == 'done' or $i < $this->data['last_zip']+1)
431      {
432        $out.= '<img src="'.$root_url.BATCH_DOWNLOAD_PATH.'template/drive.png"> Archive #'.$i.' (already downloaded)';
433      }
434      else if ($i == $this->data['last_zip']+1)
435      {
436          $out.= '<a href="'.add_url_params($url, array('set_id'=>$this->data['id'],'zip'=>$i)).'" rel="nofollow" style="font-weight:bold;"' 
437            .($i!=1 ? 'onClick="return confirm(\'Starting download Archive #'.$i.' will destroy Archive #'.($i-1).', be sure you finish the download. Continue ?\');"' : null).
438            '><img src="'.$root_url.BATCH_DOWNLOAD_PATH.'template/drive_go.png"> Archive #'.$i.' (ready)</a>';
439      }
440      else
441      {
442        $out.= '<img src="'.$root_url.BATCH_DOWNLOAD_PATH.'template/drive.png"> Archive #'.$i.' (pending)';
443      }
444     
445      $out.= '</li>';
446    }
447   
448    return $out;
449  }
450 
451  /**
452   * getArchivePath
453   * @param: int archive number
454   * @return: string
455   */
456  function getArchivePath($i=null)
457  {
458    if (!file_exists(BATCH_DOWNLOAD_LOCAL . 'u-' .$this->data['user_id']. '/'))
459    {
460      mkdir(BATCH_DOWNLOAD_LOCAL . 'u-' .$this->data['user_id']. '/', 0755, true);
461    }
462   
463    if ($i === null) $i = $this->data['last_zip'];
464   
465    include_once(PHPWG_ROOT_PATH . 'admin/include/functions.php');
466   
467    return BATCH_DOWNLOAD_LOCAL .'u-'. $this->data['user_id'] .'/'.
468          (!empty($this->conf['archive_prefix']) ? $this->conf['archive_prefix'] .'_' : null).
469          get_username($this->data['user_id']) .'_'. 
470          $this->data['type'] .'-'. $this->data['type_id'] .'_'.
471          $this->data['user_id'] . $this->data['id'] .
472          ($this->data['nb_zip']!=1 ? '_part'. $i : null).
473          '.zip';
474  }
475 
476  /**
477   * getSetInfo
478   * @return: array
479   */
480  function getSetInfo()
481  {
482    $set = array(
483      'NB_IMAGES' => $this->data['nb_images'],
484      'NB_ARCHIVES' => $this->data['nb_zip'],
485      'TOTAL_SIZE' => ceil($this->getEstimatedTotalSize()/1024),
486      'LINKS' => $this->getDownloadList(BATCH_DOWNLOAD_PUBLIC . 'init_zip'),
487      'DATE_CREATION' => format_date($this->data['date_creation'], true),
488      );
489   
490    switch ($this->data['type'])
491    {
492      // calendar
493      case 'calendar':
494      {
495        global $conf, $page;
496        $old_page = $page;
497       
498        $fields = array(
499          'created' => l10n('Creation date'),
500          'posted' => l10n('Post date'),
501          );
502       
503        $chronology = explode('-', $this->data['type_id']);
504        $page['chronology_field'] = $chronology[0];
505        $page['chronology_style'] = $chronology[1];
506        $page['chronology_view'] = $chronology[2];
507        $page['chronology_date'] = array_splice($chronology, 3);
508       
509        if (!class_exists('Calendar'))
510        {
511          include_once(PHPWG_ROOT_PATH.'include/calendar_'. $page['chronology_style'] .'.class.php');
512        }
513        $calendar = new Calendar();
514        $calendar->initialize('');
515        $display_name = strip_tags($calendar->get_display_name());
516       
517        $set['NAME'] = l10n('Calendar').': '.$fields[$page['chronology_field']].$display_name;
518        $set['sNAME'] = l10n('Calendar').': '.ltrim($display_name, $conf['level_separator']);
519       
520        $page = $old_page;
521        break;
522      }
523     
524      // category
525      case 'category':
526      {
527        $category = get_cat_info($this->data['type_id']);
528        if ($category == null)
529        {
530          $set['NAME'] = l10n('Album').': #'.$this->data['type_id'].' (deleted)';
531        }
532        else
533        {
534          $set['NAME'] = l10n('Album').': '.get_cat_display_name($category['upper_names']);
535          $set['sNAME'] = l10n('Album').': '.trigger_event('render_category_name', $category['name']);
536          $set['COMMENT'] = trigger_event('render_category_description', $category['comment']);
537        }
538        break;
539      }
540     
541      // flat
542      case 'flat':
543      {
544        $set['NAME'] = l10n('Whole gallery');
545        break;
546      }
547     
548      // tags
549      case 'tags':
550      {
551        $tags = find_tags(explode(',', $this->data['type_id']));
552        $set['NAME'] = l10n('Tags').': ';
553       
554        $first = true;
555        foreach ($tags as $tag)
556        {
557          if ($first) $first = false;
558          else $set['NAME'].= ', ';
559          $set['NAME'].=
560            '<a href="' . make_index_url(array('tags'=>array($tag))) . '">'
561            .trigger_event('render_tag_name', $tag['name'])
562            .'</a>';
563        }
564        break;
565      }
566     
567      // search
568      case 'search':
569      {
570        $set['NAME'] = '<a href="'.make_index_url(array('section'=>'search', 'search'=>$this->data['type_id'])).'">'.l10n('Search').'</a>';
571        break;
572      }
573     
574      // favorites
575      case 'favorites':
576      {
577        $set['NAME'] = '<a href="'.make_index_url(array('section'=>'favorites')).'">'.l10n('Your favorites').'</a>';
578        break;
579      }
580     
581      // most_visited
582      case 'most_visited':
583      {
584        $set['NAME'] = '<a href="'.make_index_url(array('section'=>'most_visited')).'">'.l10n('Most visited').'</a>';
585        break;
586      }
587     
588      // best_rated
589      case 'best_rated':
590      {
591        $set['NAME'] = '<a href="'.make_index_url(array('section'=>'best_rated')).'">'.l10n('Best rated').'</a>';
592        break;
593      }
594     
595      // list
596      case 'list':
597      {
598        $set['NAME'] = l10n('Random');
599        break;
600      }
601     
602      // recent_pics
603      case 'recent_pics':
604      {
605        $set['NAME'] = '<a href="'.make_index_url(array('section'=>'recent_pics')).'">'.l10n('Recent photos').'</a>';
606        break;
607      }
608     
609      // collection
610      case 'collection':
611      {
612        try
613        {
614          if (!class_exists('UserCollection')) throw new Exception();
615          $UserCollection = new UserCollection($this->data['type_id']);
616          $infos = $UserCollection->getCollectionInfo();
617          $set['NAME'] = l10n('Collection').': <a href="'.$infos['U_PUBLIC'].'">'.$UserCollection->getParam('name').'</a>';
618        }
619        catch (Exception $e)
620        {
621          $set['NAME'] = l10n('Collection').': #'.$this->data['type_id'].' (deleted)';
622        }
623        break;
624      }
625    }
626   
627    if (!isset($set['sNAME']))   $set['sNAME'] = strip_tags($set['NAME']);
628    if (!isset($set['COMMENT'])) $set['COMMENT'] = null;
629   
630    return $set;
631  }
632}
633
634
635/**
636 * small class implementing basic ZIP creation
637 * with ZipArchive or PclZip
638 */
639class myZip
640{
641  private $lib;
642  private $zip;
643 
644  function __construct($zip_path, $pclzip=false)
645  {
646    if ( class_exists('ZipArchive') and !$pclzip )
647    {
648      $this->lib = 'zipa';
649     
650      $this->zip = new ZipArchive;
651      if ($this->zip->open($zip_path, ZipArchive::CREATE) !== true)
652      {
653        trigger_error('BatchDownloader::createNextArchive, unable to open ZIP archive (ZipArchive)', E_USER_ERROR);
654      }
655    }
656    else
657    {
658      $this->lib = 'pcl';
659     
660      require_once(BATCH_DOWNLOAD_PATH.'include/pclzip.lib.php');
661      $this->zip = new PclZip($zip_path);
662     
663      // create a temporary file for archive creation
664      touch(BATCH_DOWNLOAD_LOCAL.'temp.txt');
665     
666      if ($this->zip->create(BATCH_DOWNLOAD_LOCAL.'temp.txt', PCLZIP_OPT_REMOVE_ALL_PATH) == 0)
667      {
668        trigger_error('BatchDownloader::createNextArchive, unable to open ZIP archive (PclZip)', E_USER_ERROR);
669      }
670     
671      unlink(BATCH_DOWNLOAD_LOCAL.'temp.txt');
672      $this->zip->delete(PCLZIP_OPT_BY_NAME, 'temp.txt');
673    }
674  }
675 
676  function addFile($path, $filename)
677  {
678    if ($this->lib == 'zipa')
679    {
680      $this->zip->addFile($path, $filename);
681    }
682    else
683    {
684      $this->zip->add($path, PCLZIP_OPT_REMOVE_ALL_PATH);
685    }
686  }
687 
688  function setArchiveComment($comment)
689  {
690    if ($this->lib == 'zipa')
691    {
692      $this->zip->setArchiveComment($comment);
693    }
694  }
695 
696  function close()
697  {
698    if ($this->lib == 'zipa')
699    {
700      $this->zip->close();
701    }
702  }
703}
704
705?>
Note: See TracBrowser for help on using the repository browser.