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

Last change on this file since 17915 was 17915, checked in by mistic100, 12 years ago

improve a bit datas methods

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