3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
31 * this class contains the most functions which will to the major
39 * phpfspot configuration
47 * SQLite database handle to f-spot database
55 * SQLite database handle to phpfspot database
63 * Smarty template engine
64 * @link http://smarty.php.net smarty.php.net
65 * @see PHPFSPOT_TMPL()
79 * list of available, not-selected, tags
86 * true if runtime error occued
90 private $runtime_error = false;
93 * F-Spot database version
100 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
102 * this function will be called on class construct
103 * and will check requirements, loads configuration,
104 * open databases and start the user session
106 public function __construct()
109 * register PHPFSPOT class global
111 * @global PHPFSPOT $GLOBALS['phpfspot']
114 $GLOBALS['phpfspot'] =& $this;
116 $this->cfg = new PHPFSPOT_CFG;
118 /* verify config settings */
119 if($this->check_config_options()) {
123 /* set application name and version information */
124 $this->cfg->product = "phpfspot";
125 $this->cfg->version = "1.7";
126 $this->cfg->db_version = 2;
128 $this->sort_orders= array(
129 'date_asc' => 'Date ↑',
130 'date_desc' => 'Date ↓',
131 'name_asc' => 'Name ↑',
132 'name_desc' => 'Name ↓',
133 'tags_asc' => 'Tags ↑',
134 'tags_desc' => 'Tags ↓',
137 /* Check necessary requirements */
138 if(!$this->check_requirements()) {
142 /******* Opening F-Spot's sqlite database *********/
144 /* Check if database file exists and is readable */
145 if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n";
150 /* Check if database file is writeable */
151 if(!is_writeable($this->cfg->fspot_db)) {
152 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
153 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
154 ." speed up some database operations.\n";
158 /* open the database */
159 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
161 /* change sqlite temp directory, if requested */
162 if(isset($this->cfg->sqlite_temp_dir)) {
165 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
169 /* get F-Spot database version */
170 $this->dbver = $this->getFspotDBVersion();
172 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
173 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
177 if(!is_writeable($this->cfg->thumb_path)) {
178 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
182 /******* Opening phpfspot's sqlite database *********/
184 /* Check if directory where the database file is stored is writeable */
185 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
186 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
187 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
191 /* Check if database file is writeable */
192 if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) {
193 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
194 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
198 /* open the database */
199 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
201 /* change sqlite temp directory, if requested */
202 if(isset($this->cfg->sqlite_temp_dir)) {
203 $this->cfg_db->db_exec("
205 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
209 /* Check if some tables need to be created */
210 $this->check_phpfspot_db();
212 /* overload Smarty class with our own template handler */
213 require_once "phpfspot_tmpl.php";
214 $this->tmpl = new PHPFSPOT_TMPL();
216 $this->tmpl->assign('web_path', $this->cfg->web_path);
218 /* Starting with F-Spot 0.4.2, the rating-feature was available */
219 if($this->dbver > 10) {
220 $this->tmpl->assign('has_rating', true);
221 $this->sort_orders = array_merge($this->sort_orders, array(
222 'rate_asc' => 'Rate ↑',
223 'rate_desc' => 'Rate ↓',
227 /* check if all necessary indices exist */
228 $this->checkDbIndices();
230 /* if session is not yet started, do it now */
231 if(session_id() == "")
234 if(!isset($_SESSION['tag_condition']))
235 $_SESSION['tag_condition'] = 'or';
237 /* if sort-order has not been set yet, get the one specified in the config */
238 if(!isset($_SESSION['sort_order']))
239 $_SESSION['sort_order'] = $this->cfg->sort_order;
241 if(!isset($_SESSION['searchfor_tag']))
242 $_SESSION['searchfor_tag'] = '';
244 // if begin_with is still set but thumbs_per_page is now 0, unset it
245 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
246 unset($_SESSION['begin_with']);
248 // if user-friendly-url's are enabled, set also a flag for the template handler
249 if($this->is_user_friendly_url()) {
250 $this->tmpl->assign('user_friendly_url', 'true');
255 public function __destruct()
261 * show - generate html output
263 * this function can be called after the constructor has
264 * prepared everyhing. it will load the index.tpl smarty
265 * template. if necessary it will registere pre-selects
266 * (photo index, photo, tag search, date search) into
269 public function show()
271 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
272 $this->tmpl->assign('page_title', $this->cfg->page_title);
273 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
274 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
277 if($this->is_user_friendly_url()) {
278 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
281 if(isset($_GET['mode'])) {
283 $_SESSION['start_action'] = $_GET['mode'];
285 switch($_GET['mode']) {
287 if(isset($_GET['tags'])) {
288 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
290 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
291 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
293 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
294 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
298 if(isset($_GET['tags'])) {
299 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
300 $_SESSION['start_action'] = 'showp';
302 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
303 if($_SESSION['current_photo'] != $_GET['id'])
304 unset($_SESSION['current_version']);
305 $_SESSION['current_photo'] = $_GET['id'];
306 $_SESSION['start_action'] = 'showp';
308 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
309 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
311 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
312 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
316 /* fetch export template */
317 print $this->tmpl->fetch("export.tpl");
318 /* no further execution necessary. */
322 /* fetch slideshow template */
323 print $this->tmpl->show("slideshow.tpl");
324 /* no further execution necessary. */
328 if(isset($_GET['tags'])) {
329 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
331 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
332 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
334 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
335 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
343 /* if date-search variables are registered in the session, set the check
344 for "consider date-range" in the html output
346 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
347 $this->tmpl->assign('date_search_enabled', true);
349 /* if rate-search variables are registered in the session, set the check
350 for "consider rate-range" in the html output
352 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
353 $this->tmpl->assign('rate_search_enabled', true);
356 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
357 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
358 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
360 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
361 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
362 $this->tmpl->assign('rate_search', $this->get_rate_search());
364 /* if no site-content has been set yet... */
365 if(!isset($content)) {
366 /* if tags are already selected, we can immediately display photo-index */
367 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
368 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
369 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
370 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
372 /* if a photo is already selected, we can immediately display single-photo */
373 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
374 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
376 /* ok, then let us show the welcome page... */
377 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
382 $this->tmpl->assign('initial_content', $content);
384 $this->tmpl->show("index.tpl");
389 * get_tags - grab all tags of f-spot's database
391 * this function will get all available tags from
392 * the f-spot database and store them within two
393 * arrays within this class for later usage. in
394 * fact, if the user requests (hide_tags) it will
395 * opt-out some of them.
397 * this function is getting called once by show()
399 private function get_tags()
401 $this->avail_tags = Array();
404 /* if show_tags has been set in the configuration (only show photos
405 which are tagged by these tags) they following will take care,
406 that only these other tags are displayed where the photo is also
407 tagged with one of show_tags.
409 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
412 DISTINCT t1.id as id, t1.name as name
415 INNER JOIN photo_tags
416 pt2 ON pt1.photo_id=pt2.photo_id
422 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
424 t1.sort_priority ASC";
426 $result = $this->db->db_query($query_str);
430 $result = $this->db->db_query("
431 SELECT id as id,name as name
433 ORDER BY sort_priority ASC
437 while($row = $this->db->db_fetch_object($result)) {
439 $tag_id = $row['id'];
440 $tag_name = $row['name'];
442 /* if the user has specified to ignore this tag in phpfspot's
443 configuration, ignore it here so it does not get added to
446 if(in_array($row['name'], $this->cfg->hide_tags))
449 /* if you include the following if-clause and the user has specified
450 to only show certain tags which are specified in phpfspot's
451 configuration, ignore all others so they will not be added to the
453 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
454 !in_array($row['name'], $this->cfg->show_tags))
458 $this->tags[$tag_id] = $tag_name;
459 $this->avail_tags[$count] = $tag_id;
467 * get all photo details from F-Spot database
469 * this function queries the F-Spot database for all available
470 * details of the requested photo. It returns them as a object.
472 * Furthermore it takes care of the photo version to be requested.
473 * If photo version is not yet, it queries information for the
476 * @param integer $idx
477 * @return object|null
479 public function get_photo_details($idx, $version_idx = NULL)
481 /* ~ F-Spot version 0.3.x */
482 if($this->dbver < 9) {
488 p.directory_path as directory_path,
489 p.description as description
495 /* till F-Spot version 0.4.1 */
496 if($this->dbver < 11) {
502 p.description as description
508 /* rating value got introduced */
514 p.description as description,
522 /* if show_tags is set, only return details of photos which are
523 tagged with a tag that has been specified to be shown.
525 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
527 INNER JOIN photo_tags pt
531 WHERE p.id='". $idx ."'
532 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
536 WHERE p.id='". $idx ."'
540 if($row = $this->db->db_fetchSingleRow($query_str)) {
542 /* before F-Spot db version 9 there was no uri column but
543 seperated fields for directory_path and name (= filename).
545 if($this->dbver < 9) {
546 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
549 /* if version-idx has not yet been set, get the latest photo version */
550 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
551 $version_idx = $this->get_latest_version($idx);
553 /* if an alternative version has been requested. But we
554 support this only for F-Spot database versions from
557 if($version_idx > 0 && $this->dbver >= 9) {
558 /* check for alternative versions */
559 if($version = $this->db->db_fetchSingleRow("
561 version_id, name, uri
565 photo_id LIKE '". $idx ."'
567 version_id LIKE '". $version_idx ."'")) {
569 $row['name'] = $version['name'];
570 $row['uri'] = $version['uri'];
580 } // get_photo_details()
583 * returns aligned photo names
585 * this function returns aligned (length) names for a specific photo.
586 * If the length of the name exceeds $limit the name will bei
589 * @param integer $idx
590 * @param integer $limit
591 * @return string|null
593 public function getPhotoName($idx, $limit = 0)
595 if($details = $this->get_photo_details($idx)) {
596 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
597 $name = $this->shrink_text($long_name, $limit);
607 * get photo rating level
609 * this function will return the integer-based rating level of a
610 * photo. This can only be done, if the F-Spot database is at a
611 * specific version. If rating value can not be found, zero will
612 * be returned indicating no rating value is available.
617 public function get_photo_rating($idx)
619 if($detail = $this->get_photo_details($idx)) {
620 if(isset($detail['rating']))
621 return $detail['rating'];
626 } // get_photo_rating()
629 * get rate-search bars
631 * this function will return the rating-bars for the search field.
635 public function get_rate_search()
639 for($i = 1; $i <= 5; $i++) {
641 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
643 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
644 $bar.= $this->cfg->web_path ."/resources/star.png";
646 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
649 onmouseover=\"show_rate('from', ". $i .");\"
650 onmouseout=\"reset_rate('from');\"
651 onclick=\"set_rate('from', ". $i .")\" />";
656 for($i = 1; $i <= 5; $i++) {
658 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
660 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
661 $bar.= $this->cfg->web_path ."/resources/star.png";
663 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
666 onmouseover=\"show_rate('to', ". $i .");\"
667 onmouseout=\"reset_rate('to');\"
668 onclick=\"set_rate('to', ". $i .");\" />";
673 } // get_rate_search()
676 * shrink text according provided limit
678 * If the length of the name exceeds $limit, text will be shortend
679 * and inner content will be replaced with "...".
682 * @param integer $limit
685 private function shrink_text($text, $limit)
687 if($limit != 0 && strlen($text) > $limit) {
688 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
696 * translate f-spoth photo path
698 * as the full-qualified path recorded in the f-spot database
699 * is usally not the same as on the webserver, this function
700 * will replace the path with that one specified in the cfg
701 * @param string $path
704 public function translate_path($path)
706 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
711 * control HTML ouput for a single photo
713 * this function provides all the necessary information
714 * for the single photo template.
715 * @param integer photo
717 public function showPhoto($photo)
719 /* get all photos from the current photo selection */
720 $all_photos = $this->getPhotoSelection();
721 $count = count($all_photos);
723 for($i = 0; $i < $count; $i++) {
725 // $get_next will be set, when the photo which has to
726 // be displayed has been found - this means that the
727 // next available is in fact the NEXT image (for the
729 if(isset($get_next)) {
730 $next_img = $all_photos[$i];
734 /* the next photo is our NEXT photo */
735 if($all_photos[$i] == $photo) {
739 $previous_img = $all_photos[$i];
742 if($photo == $all_photos[$i]) {
747 $details = $this->get_photo_details($photo);
754 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
756 /* if current version is already set, use it */
757 if($this->get_current_version() !== false)
758 $version = $this->get_current_version();
760 /* if version not set yet, we assume to display the latest version */
761 if(!isset($version) || !$this->is_valid_version($photo, $version))
762 $version = $this->get_latest_version($photo);
764 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
766 if(!file_exists($orig_path)) {
767 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
771 if(!is_readable($orig_path)) {
772 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
776 /* If the thumbnail doesn't exist yet, try to create it */
777 if(!file_exists($thumb_path)) {
778 $this->gen_thumb($photo, true);
779 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
782 /* get mime-type, height and width from the original photo */
783 $info = getimagesize($orig_path);
785 /* get EXIF information if JPEG */
786 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
787 $meta = $this->get_meta_informations($orig_path);
790 /* If EXIF data are available, use them */
791 if(isset($meta['ExifImageWidth'])) {
792 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
794 $meta_res = $info[0] ."x". $info[1];
797 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
798 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
800 $extern_link = "index.php?mode=showp&id=". $photo;
801 $current_tags = $this->getCurrentTags();
802 if($current_tags != "") {
803 $extern_link.= "&tags=". $current_tags;
805 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
806 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
809 $this->tmpl->assign('extern_link', $extern_link);
811 if(!file_exists($thumb_path)) {
812 $this->_error("Can't open file ". $thumb_path ."\n");
816 $info_thumb = getimagesize($thumb_path);
818 $this->tmpl->assign('description', $details['description']);
819 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
820 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
822 $this->tmpl->assign('width', $info_thumb[0]);
823 $this->tmpl->assign('height', $info_thumb[1]);
824 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
825 $this->tmpl->assign('ExifMadeWith', $meta_make);
826 $this->tmpl->assign('ExifOrigResolution', $meta_res);
827 $this->tmpl->assign('ExifFileSize', $meta_size);
829 if($this->is_user_friendly_url()) {
830 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
831 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
834 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
835 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
838 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
840 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
841 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
842 $this->tmpl->assign('current_img', $photo);
844 if(isset($previous_img)) {
845 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
846 $this->tmpl->assign('prev_img', $previous_img);
849 if(isset($next_img)) {
850 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
851 $this->tmpl->assign('next_img', $next_img);
854 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
855 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
856 $this->tmpl->assign('photo_number', $i);
857 $this->tmpl->assign('photo_count', count($all_photos));
858 $this->tmpl->assign('photo', $photo);
859 $this->tmpl->assign('version', $version);
861 /* if the photo as alternative versions, set a flag for the template */
862 if($this->get_photo_versions($photo))
863 $this->tmpl->assign('has_versions', true);
865 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
867 return $this->tmpl->fetch("single_photo.tpl");
872 * all available tags and tag cloud
874 * this function outputs all available tags (time ordered)
875 * and in addition output them as tag cloud (tags which have
876 * many photos will appears more then others)
878 public function getAvailableTags()
880 /* retrive tags from database */
885 $result = $this->db->db_query("
886 SELECT tag_id as id, count(tag_id) as quantity
896 while($row = $this->db->db_fetch_object($result)) {
897 $tags[$row['id']] = $row['quantity'];
900 // change these font sizes if you will
901 $max_size = 125; // max font size in %
902 $min_size = 75; // min font size in %
905 $max_sat = hexdec('cc');
906 $min_sat = hexdec('44');
908 // get the largest and smallest array values
909 $max_qty = max(array_values($tags));
910 $min_qty = min(array_values($tags));
912 // find the range of values
913 $spread = $max_qty - $min_qty;
914 if (0 == $spread) { // we don't want to divide by zero
918 // determine the font-size increment
919 // this is the increase per tag quantity (times used)
920 $step = ($max_size - $min_size)/($spread);
921 $step_sat = ($max_sat - $min_sat)/($spread);
923 // loop through our tag array
924 foreach ($tags as $key => $value) {
926 /* has the currently processed tag already been added to
927 the selected tag list? if so, ignore it here...
929 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
932 // calculate CSS font-size
933 // find the $value in excess of $min_qty
934 // multiply by the font-size increment ($size)
935 // and add the $min_size set above
936 $size = $min_size + (($value - $min_qty) * $step);
937 // uncomment if you want sizes in whole %:
940 $color = $min_sat + ($value - $min_qty) * $step_sat;
946 if(isset($this->tags[$key])) {
947 if($this->is_user_friendly_url()) {
948 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
949 onclick=\"Tags('add', ". $key ."); return false;\"
951 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
952 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
955 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
956 onclick=\"Tags('add', ". $key ."); return false;\"
958 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
959 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
964 $output = substr($output, 0, strlen($output)-2);
967 } // getAvailableTags()
970 * output all selected tags
972 * this function output all tags which have been selected
973 * by the user. the selected tags are stored in the
974 * session-variable $_SESSION['selected_tags']
977 public function getSelectedTags($type = 'link')
979 /* retrive tags from database */
984 foreach($this->avail_tags as $tag)
986 // return all selected tags
987 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
992 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
996 <div class=\"tagresulttag\">
997 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
998 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1008 $output = substr($output, 0, strlen($output)-2);
1012 return "no tags selected";
1015 } // getSelectedTags()
1018 * add tag to users session variable
1020 * this function will add the specified to users current
1021 * tag selection. if a date search has been made before
1022 * it will be now cleared
1025 public function addTag($tag)
1027 if(!isset($_SESSION['selected_tags']))
1028 $_SESSION['selected_tags'] = Array();
1030 if(isset($_SESSION['searchfor_tag']))
1031 unset($_SESSION['searchfor_tag']);
1033 // has the user requested to hide this tag, and still someone,
1034 // somehow tries to add it, don't allow this.
1035 if(!isset($this->cfg->hide_tags) &&
1036 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1039 if(!in_array($tag, $_SESSION['selected_tags']))
1040 array_push($_SESSION['selected_tags'], $tag);
1047 * remove tag to users session variable
1049 * this function removes the specified tag from
1050 * users current tag selection
1051 * @param string $tag
1054 public function delTag($tag)
1056 if(isset($_SESSION['searchfor_tag']))
1057 unset($_SESSION['searchfor_tag']);
1059 if(isset($_SESSION['selected_tags'])) {
1060 $key = array_search($tag, $_SESSION['selected_tags']);
1061 unset($_SESSION['selected_tags'][$key]);
1062 sort($_SESSION['selected_tags']);
1070 * reset tag selection
1072 * if there is any tag selection, it will be
1075 public function resetTags()
1077 if(isset($_SESSION['selected_tags']))
1078 unset($_SESSION['selected_tags']);
1083 * returns the value for the autocomplete tag-search
1086 public function get_xml_tag_list()
1088 if(!isset($_GET['search']) || !is_string($_GET['search']))
1089 $_GET['search'] = '';
1094 /* retrive tags from database */
1097 $matched_tags = Array();
1099 header("Content-Type: text/xml");
1101 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1102 $string.= "<results>\n";
1104 foreach($this->avail_tags as $tag)
1106 if(!empty($_GET['search']) &&
1107 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1108 count($matched_tags) < $length) {
1110 $count = $this->get_num_photos($tag);
1113 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1116 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1122 /* if we have collected enough items, break out */
1123 if(count($matched_tags) >= $length)
1127 $string.= "</results>\n";
1131 } // get_xml_tag_list()
1135 * reset single photo
1137 * if a specific photo was requested (external link)
1138 * unset the session variable now
1140 public function resetPhotoView()
1142 if(isset($_SESSION['current_photo']))
1143 unset($_SESSION['current_photo']);
1145 if(isset($_SESSION['current_version']))
1146 unset($_SESSION['current_version']);
1148 } // resetPhotoView();
1153 * if any tag search has taken place, reset it now
1155 public function resetTagSearch()
1157 if(isset($_SESSION['searchfor_tag']))
1158 unset($_SESSION['searchfor_tag']);
1160 } // resetTagSearch()
1165 * if any name search has taken place, reset it now
1167 public function resetNameSearch()
1169 if(isset($_SESSION['searchfor_name']))
1170 unset($_SESSION['searchfor_name']);
1172 } // resetNameSearch()
1177 * if any date search has taken place, reset it now.
1179 public function resetDateSearch()
1181 if(isset($_SESSION['from_date']))
1182 unset($_SESSION['from_date']);
1183 if(isset($_SESSION['to_date']))
1184 unset($_SESSION['to_date']);
1186 } // resetDateSearch();
1191 * if any rate search has taken place, reset it now.
1193 public function resetRateSearch()
1195 if(isset($_SESSION['rate_from']))
1196 unset($_SESSION['rate_from']);
1197 if(isset($_SESSION['rate_to']))
1198 unset($_SESSION['rate_to']);
1200 } // resetRateSearch();
1203 * return all photo according selection
1205 * this function returns all photos based on
1206 * the tag-selection, tag- or date-search.
1207 * the tag-search also has to take care of AND
1208 * and OR conjunctions
1211 public function getPhotoSelection()
1213 $matched_photos = Array();
1214 $additional_where_cond = "";
1216 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1217 $from_date = $_SESSION['from_date'];
1218 $to_date = $_SESSION['to_date'];
1219 $additional_where_cond.= "
1220 p.time>='". $from_date ."'
1222 p.time<='". $to_date ."'
1226 if(isset($_SESSION['searchfor_name'])) {
1228 /* check for previous conditions. if so add 'AND' */
1229 if(!empty($additional_where_cond)) {
1230 $additional_where_cond.= " AND ";
1233 if($this->dbver < 9) {
1234 $additional_where_cond.= "
1236 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1238 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1243 $additional_where_cond.= "
1245 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1247 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1253 /* limit result based on rate-search */
1254 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1256 if($this->dbver > 10) {
1258 /* check for previous conditions. if so add 'AND' */
1259 if(!empty($additional_where_cond)) {
1260 $additional_where_cond.= " AND ";
1263 $additional_where_cond.= "
1264 p.rating >= ". $_SESSION['rate_from'] ."
1266 p.rating <= ". $_SESSION['rate_to'] ."
1271 if(isset($_SESSION['sort_order'])) {
1272 $order_str = $this->get_sort_order();
1275 /* return a search result */
1276 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1279 pt1.photo_id as photo_id
1282 INNER JOIN photo_tags pt2
1283 ON pt1.photo_id=pt2.photo_id
1287 ON pt1.photo_id=p.id
1290 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1292 if(!empty($additional_where_cond))
1293 $query_str.= "AND ". $additional_where_cond ." ";
1295 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1296 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1299 if(isset($order_str))
1300 $query_str.= $order_str;
1302 $result = $this->db->db_query($query_str);
1303 while($row = $this->db->db_fetch_object($result)) {
1304 array_push($matched_photos, $row['photo_id']);
1306 return $matched_photos;
1309 /* return according the selected tags */
1310 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1312 foreach($_SESSION['selected_tags'] as $tag)
1313 $selected.= $tag .",";
1314 $selected = substr($selected, 0, strlen($selected)-1);
1316 /* photo has to match at least on of the selected tags */
1317 if($_SESSION['tag_condition'] == 'or') {
1320 pt1.photo_id as photo_id
1323 INNER JOIN photo_tags pt2
1324 ON pt1.photo_id=pt2.photo_id
1328 ON pt1.photo_id=p.id
1329 WHERE pt1.tag_id IN (". $selected .")
1331 if(!empty($additional_where_cond))
1332 $query_str.= "AND ". $additional_where_cond ." ";
1334 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1335 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1338 if(isset($order_str))
1339 $query_str.= $order_str;
1341 /* photo has to match all selected tags */
1342 elseif($_SESSION['tag_condition'] == 'and') {
1344 if(count($_SESSION['selected_tags']) >= 32) {
1345 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1346 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1350 /* Join together a table looking like
1352 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1354 so the query can quickly return all images matching the
1355 selected tags in an AND condition
1361 pt1.photo_id as photo_id
1366 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1373 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1375 INNER JOIN photo_tags pt". ($i+2) ."
1376 ON pt1.photo_id=pt". ($i+2) .".photo_id
1381 ON pt1.photo_id=p.id
1383 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1384 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1386 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1389 if(!empty($additional_where_cond))
1390 $query_str.= "AND ". $additional_where_cond;
1392 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1393 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1396 if(isset($order_str))
1397 $query_str.= $order_str;
1401 $result = $this->db->db_query($query_str);
1402 while($row = $this->db->db_fetch_object($result)) {
1403 array_push($matched_photos, $row['photo_id']);
1405 return $matched_photos;
1408 /* return all available photos */
1414 LEFT JOIN photo_tags pt
1420 if(!empty($additional_where_cond))
1421 $query_str.= "WHERE ". $additional_where_cond ." ";
1423 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1424 if(!empty($additional_where_cond))
1425 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1427 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1430 if(isset($order_str))
1431 $query_str.= $order_str;
1433 $result = $this->db->db_query($query_str);
1434 while($row = $this->db->db_fetch_object($result)) {
1435 array_push($matched_photos, $row['id']);
1437 return $matched_photos;
1439 } // getPhotoSelection()
1442 * control HTML ouput for photo index
1444 * this function provides all the necessary information
1445 * for the photo index template.
1448 public function showPhotoIndex()
1450 $photos = $this->getPhotoSelection();
1451 $current_tags = $this->getCurrentTags();
1453 $count = count($photos);
1455 /* if all thumbnails should be shown on one page */
1456 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1460 /* thumbnails should be splitted up in several pages */
1461 elseif($this->cfg->thumbs_per_page > 0) {
1463 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1467 $begin_with = $_SESSION['begin_with'];
1470 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1474 $images[$thumbs] = Array();
1475 $img_height[$thumbs] = Array();
1476 $img_width[$thumbs] = Array();
1477 $img_id[$thumbs] = Array();
1478 $img_name[$thumbs] = Array();
1479 $img_fullname[$thumbs] = Array();
1480 $img_title = Array();
1481 $img_rating = Array();
1483 for($i = $begin_with; $i < $end_with; $i++) {
1485 if(isset($photos[$i])) {
1487 $images[$thumbs] = $photos[$i];
1488 $img_id[$thumbs] = $i;
1489 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1490 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1491 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1492 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1494 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1496 if(file_exists($thumb_path)) {
1497 $info = getimagesize($thumb_path);
1498 $img_width[$thumbs] = $info[0];
1499 $img_height[$thumbs] = $info[1];
1505 // +1 for for smarty's selection iteration
1508 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1509 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1511 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1512 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1513 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1516 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1517 $this->tmpl->assign('tag_result', 1);
1520 /* do we have to display the page selector ? */
1521 if($this->cfg->thumbs_per_page != 0) {
1525 /* calculate the page switchers */
1526 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1527 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1529 if($begin_with != 0)
1530 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1531 if($end_with < $count)
1532 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1534 $photo_per_page = $this->cfg->thumbs_per_page;
1535 $last_page = ceil($count / $photo_per_page);
1537 /* get the current selected page */
1538 if($begin_with == 0) {
1542 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1549 for($i = 1; $i <= $last_page; $i++) {
1551 if($current_page == $i)
1552 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1553 elseif($current_page-1 == $i || $current_page+1 == $i)
1554 $style = "style=\"font-size: 105%;\"";
1555 elseif(($current_page-5 >= $i) && ($i != 1) ||
1556 ($current_page+5 <= $i) && ($i != $last_page))
1557 $style = "style=\"font-size: 75%;\"";
1561 $start_with = ($i*$photo_per_page)-$photo_per_page;
1563 if($this->is_user_friendly_url()) {
1564 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1567 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1569 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1573 $select.= ">". $i ."</a> ";
1575 // until 9 pages we show the selector from 1-9
1576 if($last_page <= 9) {
1577 $page_select.= $select;
1580 if($i == 1 /* first page */ ||
1581 $i == $last_page /* last page */ ||
1582 $i == $current_page /* current page */ ||
1583 $i == ceil($last_page * 0.25) /* first quater */ ||
1584 $i == ceil($last_page * 0.5) /* half */ ||
1585 $i == ceil($last_page * 0.75) /* third quater */ ||
1586 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1587 (in_array($i, array($last_page, $last_page-1, $last_page-2, $last_page-3, $last_page-4, $last_page-5)) && $current_page >= $last_page-4) /* the last 6 */ ||
1588 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1589 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1591 $page_select.= $select;
1599 $page_select.= "......... ";
1604 /* only show the page selector if we have more then one page */
1606 $this->tmpl->assign('page_selector', $page_select);
1609 $extern_link = "index.php?mode=showpi";
1610 $rss_link = "index.php?mode=rss";
1611 if($current_tags != "") {
1612 $extern_link.= "&tags=". $current_tags;
1613 $rss_link.= "&tags=". $current_tags;
1615 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1616 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1617 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1620 $export_link = "index.php?mode=export";
1621 $slideshow_link = "index.php?mode=slideshow";
1623 $this->tmpl->assign('extern_link', $extern_link);
1624 $this->tmpl->assign('slideshow_link', $slideshow_link);
1625 $this->tmpl->assign('export_link', $export_link);
1626 $this->tmpl->assign('rss_link', $rss_link);
1627 $this->tmpl->assign('count', $count);
1628 $this->tmpl->assign('width', $this->cfg->thumb_width);
1629 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1630 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1631 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1632 $this->tmpl->assign('images', $images);
1633 $this->tmpl->assign('img_width', $img_width);
1634 $this->tmpl->assign('img_height', $img_height);
1635 $this->tmpl->assign('img_id', $img_id);
1636 $this->tmpl->assign('img_name', $img_name);
1637 $this->tmpl->assign('img_fullname', $img_fullname);
1638 $this->tmpl->assign('img_title', $img_title);
1639 $this->tmpl->assign('img_rating', $img_rating);
1640 $this->tmpl->assign('thumbs', $thumbs);
1641 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1643 $result = $this->tmpl->fetch("photo_index.tpl");
1645 /* if we are returning to photo index from an photo-view,
1646 scroll the window to the last shown photo-thumbnail.
1647 after this, unset the last_photo session variable.
1649 if(isset($_SESSION['last_photo'])) {
1650 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1651 unset($_SESSION['last_photo']);
1656 } // showPhotoIndex()
1659 * show credit template
1661 public function showCredits()
1663 $this->tmpl->assign('version', $this->cfg->version);
1664 $this->tmpl->assign('product', $this->cfg->product);
1665 $this->tmpl->assign('db_version', $this->dbver);
1666 $this->tmpl->show("credits.tpl");
1671 * create thumbnails for the requested width
1673 * this function creates image thumbnails of $orig_image
1674 * stored as $thumb_image. It will check if the image is
1675 * in a supported format, if necessary rotate the image
1676 * (based on EXIF orientation meta headers) and re-sizing.
1677 * @param string $orig_image
1678 * @param string $thumb_image
1679 * @param integer $width
1682 public function create_thumbnail($orig_image, $thumb_image, $width)
1684 if(!file_exists($orig_image)) {
1688 $mime = $this->get_mime_info($orig_image);
1690 /* check if original photo is a support image type */
1691 if(!$this->checkifImageSupported($mime))
1698 $meta = $this->get_meta_informations($orig_image);
1704 if(isset($meta['Orientation'])) {
1705 switch($meta['Orientation']) {
1706 case 1: /* top, left */
1707 /* nothing to do */ break;
1708 case 2: /* top, right */
1709 $rotate = 0; $flip_hori = true; break;
1710 case 3: /* bottom, left */
1711 $rotate = 180; break;
1712 case 4: /* bottom, right */
1713 $flip_vert = true; break;
1714 case 5: /* left side, top */
1715 $rotate = 90; $flip_vert = true; break;
1716 case 6: /* right side, top */
1717 $rotate = 90; break;
1718 case 7: /* left side, bottom */
1719 $rotate = 270; $flip_vert = true; break;
1720 case 8: /* right side, bottom */
1721 $rotate = 270; break;
1725 $src_img = @imagecreatefromjpeg($orig_image);
1731 $src_img = @imagecreatefrompng($orig_image);
1735 case 'image/x-portable-pixmap':
1737 $src_img = new Imagick($orig_image);
1738 $handler = "imagick";
1743 if(!isset($src_img) || empty($src_img)) {
1744 print "Can't load image from ". $orig_image ."\n";
1752 /* grabs the height and width */
1753 $cur_width = imagesx($src_img);
1754 $cur_height = imagesy($src_img);
1756 // If requested width is more then the actual image width,
1757 // do not generate a thumbnail, instead safe the original
1758 // as thumbnail but with lower quality. But if the image
1759 // is to heigh too, then we still have to resize it.
1760 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1761 $result = imagejpeg($src_img, $thumb_image, 75);
1762 imagedestroy($src_img);
1769 $cur_width = $src_img->getImageWidth();
1770 $cur_height = $src_img->getImageHeight();
1772 // If requested width is more then the actual image width,
1773 // do not generate a thumbnail, instead safe the original
1774 // as thumbnail but with lower quality. But if the image
1775 // is to heigh too, then we still have to resize it.
1776 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1777 $src_img->setCompressionQuality(75);
1778 $src_img->setImageFormat('jpeg');
1779 $src_img->writeImage($thumb_image);
1781 $src_img->destroy();
1788 // If the image will be rotate because EXIF orientation said so
1789 // 'virtually rotate' the image for further calculations
1790 if($rotate == 90 || $rotate == 270) {
1792 $cur_width = $cur_height;
1796 /* calculates aspect ratio */
1797 $aspect_ratio = $cur_height / $cur_width;
1800 if($aspect_ratio < 1) {
1802 $new_h = abs($new_w * $aspect_ratio);
1804 /* 'virtually' rotate the image and calculate it's ratio */
1805 $tmp_w = $cur_height;
1806 $tmp_h = $cur_width;
1807 /* now get the ratio from the 'rotated' image */
1808 $tmp_ratio = $tmp_h/$tmp_w;
1809 /* now calculate the new dimensions */
1811 $tmp_h = abs($tmp_w * $tmp_ratio);
1813 // now that we know, how high they photo should be, if it
1814 // gets rotated, use this high to scale the image
1816 $new_w = abs($new_h / $aspect_ratio);
1818 // If the image will be rotate because EXIF orientation said so
1819 // now 'virtually rotate' back the image for the image manipulation
1820 if($rotate == 90 || $rotate == 270) {
1831 /* creates new image of that size */
1832 $dst_img = imagecreatetruecolor($new_w, $new_h);
1834 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1836 /* copies resized portion of original image into new image */
1837 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1839 /* needs the image to be flipped horizontal? */
1841 $this->_debug("(FLIP)");
1842 $dst_img = $this->flipImage($dst_img, 'hori');
1844 /* needs the image to be flipped vertical? */
1846 $this->_debug("(FLIP)");
1847 $dst_img = $this->flipImage($dst_img, 'vert');
1851 $this->_debug("(ROTATE)");
1852 $dst_img = $this->rotateImage($dst_img, $rotate);
1855 /* write down new generated file */
1856 $result = imagejpeg($dst_img, $thumb_image, 75);
1858 /* free your mind */
1859 imagedestroy($dst_img);
1860 imagedestroy($src_img);
1862 if($result === false) {
1863 print "Can't write thumbnail ". $thumb_image ."\n";
1873 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1875 /* needs the image to be flipped horizontal? */
1877 $this->_debug("(FLIP)");
1878 $src_img->rotateImage(new ImagickPixel(), 90);
1879 $src_img->flipImage();
1880 $src_img->rotateImage(new ImagickPixel(), -90);
1882 /* needs the image to be flipped vertical? */
1884 $this->_debug("(FLIP)");
1885 $src_img->flipImage();
1889 $this->_debug("(ROTATE)");
1890 $src_img->rotateImage(new ImagickPixel(), $rotate);
1893 $src_img->setCompressionQuality(75);
1894 $src_img->setImageFormat('jpeg');
1896 if(!$src_img->writeImage($thumb_image)) {
1897 print "Can't write thumbnail ". $thumb_image ."\n";
1902 $src_img->destroy();
1909 } // create_thumbnail()
1912 * return all exif meta data from the file
1913 * @param string $file
1916 public function get_meta_informations($file)
1918 return exif_read_data($file);
1920 } // get_meta_informations()
1923 * create phpfspot own sqlite database
1925 * this function creates phpfspots own sqlite database
1926 * if it does not exist yet. this own is used to store
1927 * some necessary informations (md5 sum's, ...).
1929 public function check_phpfspot_db()
1931 // if the config table doesn't exist yet, create it
1932 if(!$this->cfg_db->db_check_table_exists("images")) {
1933 $this->cfg_db->db_exec("
1934 CREATE TABLE images (
1936 img_version_idx int,
1937 img_md5 varchar(32),
1938 UNIQUE(img_idx, img_version_idx)
1943 if(!$this->cfg_db->db_check_table_exists("meta")) {
1944 $this->cfg_db->db_exec("
1946 meta_key varchar(255),
1947 meta_value varchar(255)
1951 /* db_version was added with phpfspot 1.7, before changes
1952 on the phpfspot database where not necessary.
1955 $this->cfg_db->db_exec("
1957 meta_key, meta_value
1959 'phpfspot Database Version',
1960 '". $this->cfg->db_version ."'
1965 /* if version <= 2 and column img_version_idx does not exist yet */
1966 if($this->get_db_version() <= 2 &&
1967 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
1969 if(!$this->cfg_db->db_start_transaction())
1970 die("Can not start database transaction");
1972 $result = $this->cfg_db->db_exec("
1973 CREATE TEMPORARY TABLE images_temp (
1975 img_version_idx int,
1976 img_md5 varchar(32),
1977 UNIQUE(img_idx, img_version_idx)
1982 $this->cfg_db->db_rollback_transaction();
1983 die("Upgrade failed - transaction rollback");
1986 $result = $this->cfg_db->db_exec("
1987 INSERT INTO images_temp
1996 $this->cfg_db->db_rollback_transaction();
1997 die("Upgrade failed - transaction rollback");
2000 $result = $this->cfg_db->db_exec("
2005 $this->cfg_db->db_rollback_transaction();
2006 die("Upgrade failed - transaction rollback");
2009 $result = $this->cfg_db->db_exec("
2010 CREATE TABLE images (
2012 img_version_idx int,
2013 img_md5 varchar(32),
2014 UNIQUE(img_idx, img_version_idx)
2019 $this->cfg_db->db_rollback_transaction();
2020 die("Upgrade failed - transaction rollback");
2023 $result = $this->cfg_db->db_exec("
2030 $this->cfg_db->db_rollback_transaction();
2031 die("Upgrade failed - transaction rollback");
2034 $result = $this->cfg_db->db_exec("
2035 DROP TABLE images_temp
2039 $this->cfg_db->db_rollback_transaction();
2040 die("Upgrade failed - transaction rollback");
2043 if(!$this->cfg_db->db_commit_transaction())
2044 die("Can not commit database transaction");
2048 } // check_phpfspot_db
2051 * generates thumbnails
2053 * This function generates JPEG thumbnails from
2054 * provided F-Spot photo indize and its alternative
2057 * 1. Check if all thumbnail generations (width) are already in place and
2059 * 2. Check if the md5sum of the original file has changed
2060 * 3. Generate the thumbnails if needed
2061 * @param integer $idx
2062 * @param integer $force
2063 * @param boolean $overwrite
2065 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2068 $versions = Array(0);
2070 $resolutions = Array(
2071 $this->cfg->thumb_width,
2072 $this->cfg->photo_width,
2073 $this->cfg->mini_width,
2077 if($alt_versions = $this->get_photo_versions($idx))
2078 $versions = array_merge($versions, $alt_versions);
2080 foreach($versions as $version) {
2082 /* get details from F-Spot's database */
2083 $details = $this->get_photo_details($idx, $version);
2085 /* calculate file MD5 sum */
2086 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2088 if(!file_exists($full_path)) {
2089 $this->_error("File ". $full_path ." does not exist");
2093 if(!is_readable($full_path)) {
2094 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2098 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2100 /* If Nikon NEF format, we need to treat it another way */
2101 if(isset($this->cfg->dcraw_bin) &&
2102 file_exists($this->cfg->dcraw_bin) &&
2103 is_executable($this->cfg->dcraw_bin) &&
2104 preg_match('/\.nef$/i', $details['uri'])) {
2106 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2108 /* if PPM file does not exist, let dcraw convert it from NEF */
2109 if(!file_exists($ppm_path)) {
2110 system($this->cfg->dcraw_bin ." -a ". $full_path);
2113 /* for now we handle the PPM instead of the NEF */
2114 $full_path = $ppm_path;
2118 $file_md5 = md5_file($full_path);
2121 foreach($resolutions as $resolution) {
2123 $generate_it = false;
2125 $thumb_sub_path = substr($file_md5, 0, 2);
2126 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2128 /* if thumbnail-subdirectory does not exist yet, create it */
2129 if(!file_exists(dirname($thumb_path))) {
2130 mkdir(dirname($thumb_path), 0755);
2133 /* if the thumbnail file doesn't exist, create it */
2134 if(!file_exists($thumb_path) || $force) {
2135 $generate_it = true;
2137 elseif($file_md5 != $this->getMD5($idx, $version)) {
2138 $generate_it = true;
2141 if($generate_it || $overwrite) {
2143 $this->_debug(" ". $resolution ."px");
2144 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2152 $this->_debug(" already exist");
2155 /* set the new/changed MD5 sum for the current photo */
2157 $this->setMD5($idx, $file_md5, $version);
2160 $this->_debug("\n");
2167 * returns stored md5 sum for a specific photo
2169 * this function queries the phpfspot database for a stored MD5
2170 * checksum of the specified photo. It also takes care of the
2171 * requested photo version - original or alternative photo.
2173 * @param integer $idx
2174 * @return string|null
2176 public function getMD5($idx, $version_idx = 0)
2178 $result = $this->cfg_db->db_query("
2184 img_idx='". $idx ."'
2186 img_version_idx='". $version_idx ."'
2192 if($img = $this->cfg_db->db_fetch_object($result))
2193 return $img['img_md5'];
2200 * set MD5 sum for the specific photo
2201 * @param integer $idx
2202 * @param string $md5
2204 private function setMD5($idx, $md5, $version_idx = 0)
2206 $result = $this->cfg_db->db_exec("
2207 INSERT OR REPLACE INTO images (
2208 img_idx, img_version_idx, img_md5
2211 '". $version_idx ."',
2219 * store current tag condition
2221 * this function stores the current tag condition
2222 * (AND or OR) in the users session variables
2223 * @param string $mode
2226 public function setTagCondition($mode)
2228 $_SESSION['tag_condition'] = $mode;
2232 } // setTagCondition()
2235 * invoke tag & date search
2237 * this function will return all matching tags and store
2238 * them in the session variable selected_tags. furthermore
2239 * it also handles the date search.
2240 * getPhotoSelection() will then only return the matching
2244 public function startSearch()
2247 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2248 $date_from = $_POST['date_from'];
2250 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2251 $date_to = $_POST['date_to'];
2254 /* tag-name search */
2255 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2256 $searchfor_tag = $_POST['for_tag'];
2257 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2260 unset($_SESSION['searchfor_tag']);
2263 /* file-name search */
2264 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2265 $_SESSION['searchfor_name'] = $_POST['for_name'];
2268 unset($_SESSION['searchfor_name']);
2272 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2274 $_SESSION['rate_from'] = $_POST['rate_from'];
2276 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2277 $_SESSION['rate_to'] = $_POST['rate_to'];
2281 /* delete any previously set value */
2282 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2287 if(isset($date_from) && !empty($date_from))
2288 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2290 unset($_SESSION['from_date']);
2292 if(isset($date_to) && !empty($date_to))
2293 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2295 unset($_SESSION['to_date']);
2297 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2298 /* new search, reset the current selected tags */
2299 $_SESSION['selected_tags'] = Array();
2300 foreach($this->avail_tags as $tag) {
2301 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2302 array_push($_SESSION['selected_tags'], $tag);
2311 * updates sort order in session variable
2313 * this function is invoked by RPC and will sort the requested
2314 * sort order in the session variable.
2315 * @param string $sort_order
2318 public function updateSortOrder($order)
2320 if(isset($this->sort_orders[$order])) {
2321 $_SESSION['sort_order'] = $order;
2325 return "unkown error";
2327 } // updateSortOrder()
2330 * update photo version in session variable
2332 * this function is invoked by RPC and will set the requested
2333 * photo version in the session variable.
2334 * @param string $photo_version
2337 public function update_photo_version($photo_idx, $photo_version)
2339 if($this->is_valid_version($photo_idx, $photo_version)) {
2340 $_SESSION['current_version'] = $photo_version;
2344 return "incorrect photo version provided";
2346 } // update_photo_version()
2351 * this function rotates the image according the
2353 * @param string $img
2354 * @param integer $degress
2357 private function rotateImage($img, $degrees)
2359 if(function_exists("imagerotate")) {
2360 $img = imagerotate($img, $degrees, 0);
2362 function imagerotate($src_img, $angle)
2364 $src_x = imagesx($src_img);
2365 $src_y = imagesy($src_img);
2366 if ($angle == 180) {
2370 elseif ($src_x <= $src_y) {
2374 elseif ($src_x >= $src_y) {
2379 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2380 imagealphablending($rotate, false);
2385 for ($y = 0; $y < ($src_y); $y++) {
2386 for ($x = 0; $x < ($src_x); $x++) {
2387 $color = imagecolorat($src_img, $x, $y);
2388 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2394 for ($y = 0; $y < ($src_y); $y++) {
2395 for ($x = 0; $x < ($src_x); $x++) {
2396 $color = imagecolorat($src_img, $x, $y);
2397 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2403 for ($y = 0; $y < ($src_y); $y++) {
2404 for ($x = 0; $x < ($src_x); $x++) {
2405 $color = imagecolorat($src_img, $x, $y);
2406 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2420 $img = imagerotate($img, $degrees);
2429 * returns flipped image
2431 * this function will return an either horizontal or
2432 * vertical flipped truecolor image.
2433 * @param string $image
2434 * @param string $mode
2437 private function flipImage($image, $mode)
2439 $w = imagesx($image);
2440 $h = imagesy($image);
2441 $flipped = imagecreatetruecolor($w, $h);
2445 for ($y = 0; $y < $h; $y++) {
2446 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2450 for ($x = 0; $x < $w; $x++) {
2451 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2461 * return all assigned tags for the specified photo
2462 * @param integer $idx
2465 private function get_photo_tags($idx)
2467 $result = $this->db->db_query("
2468 SELECT t.id as id, t.name as name
2470 INNER JOIN photo_tags pt
2472 WHERE pt.photo_id='". $idx ."'
2477 while($row = $this->db->db_fetch_object($result)) {
2478 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2480 $tags[$row['id']] = $row['name'];
2485 } // get_photo_tags()
2488 * create on-the-fly images with text within
2489 * @param string $txt
2490 * @param string $color
2491 * @param integer $space
2492 * @param integer $font
2495 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2497 if (strlen($color) != 6)
2500 $int = hexdec($color);
2501 $h = imagefontheight($font);
2502 $fw = imagefontwidth($font);
2503 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2504 $lines = count($txt);
2505 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2506 $bg = imagecolorallocate($im, 255, 255, 255);
2507 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2510 foreach ($txt as $text) {
2511 $x = (($w - ($fw * strlen($text))) / 2);
2512 imagestring($im, $font, $x, $y, $text, $color);
2513 $y += ($h + $space);
2516 Header("Content-type: image/png");
2519 } // showTextImage()
2522 * check if all requirements are met
2525 private function check_requirements()
2527 if(!function_exists("imagecreatefromjpeg")) {
2528 print "PHP GD library extension is missing<br />\n";
2532 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2533 print "PHP SQLite3 library extension is missing<br />\n";
2537 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2538 ini_set('track_errors', 1);
2539 @include_once 'HTML/AJAX/Server.php';
2540 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2541 print "PEAR HTML_AJAX package is missing<br />\n";
2544 @include_once 'Calendar/Calendar.php';
2545 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2546 print "PEAR Calendar package is missing<br />\n";
2549 @include_once 'Console/Getopt.php';
2550 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2551 print "PEAR Console_Getopt package is missing<br />\n";
2554 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2555 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2556 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2559 ini_restore('track_errors');
2566 } // check_requirements()
2568 private function _debug($text)
2570 if(isset($this->fromcmd)) {
2577 * check if specified MIME type is supported
2578 * @param string $mime
2581 public function checkifImageSupported($mime)
2583 $supported_types = Array(
2586 "image/x-portable-pixmap",
2590 if(in_array($mime, $supported_types))
2595 } // checkifImageSupported()
2599 * @param string $text
2601 public function _error($text)
2603 switch($this->cfg->logging) {
2606 if(isset($this->fromcmd))
2609 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2610 print $text ."<br />\n";
2617 error_log($text, 3, $this->cfg->log_file);
2621 $this->runtime_error = true;
2626 * get calendar input-text fields
2628 * this function returns a text-field used for the data selection.
2629 * Either it will be filled with the current date or, if available,
2630 * filled with the date user entered previously.
2632 * @param string $mode
2635 private function get_date_text_field($mode)
2637 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2639 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2641 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2643 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2644 if(!isset($_SESSION[$mode .'_date']))
2645 $output.= " disabled=\"disabled\"";
2650 } // get_date_text_field()
2653 * output calendar matrix
2654 * @param integer $year
2655 * @param integer $month
2656 * @param integer $day
2658 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2660 if (!isset($year)) $year = date('Y');
2661 if (!isset($month)) $month = date('m');
2662 if (!isset($day)) $day = date('d');
2667 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2668 require_once CALENDAR_ROOT.'Day.php';
2671 $month = new Calendar_Month_Weekdays($year,$month);
2674 $prevStamp = $month->prevMonth(true);
2675 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2676 $nextStamp = $month->nextMonth(true);
2677 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2679 $selectedDays = array (
2680 new Calendar_Day($year,$month,$day),
2681 new Calendar_Day($year,12,25),
2684 // Build the days in the month
2685 $month->build($selectedDays);
2687 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2688 $this->tmpl->assign('prev_month', $prev);
2689 $this->tmpl->assign('next_month', $next);
2691 while ( $day = $month->fetch() ) {
2693 if(!isset($matrix[$rows]))
2694 $matrix[$rows] = Array();
2698 $dayStamp = $day->thisDay(true);
2699 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2701 // isFirst() to find start of week
2702 if ( $day->isFirst() )
2705 if ( $day->isSelected() ) {
2706 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2707 } else if ( $day->isEmpty() ) {
2708 $string.= "<td> </td>\n";
2710 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2713 // isLast() to find end of week
2714 if ( $day->isLast() )
2715 $string.= "</tr>\n";
2717 $matrix[$rows][$cols] = $string;
2727 $this->tmpl->assign('matrix', $matrix);
2728 $this->tmpl->assign('rows', $rows);
2729 $this->tmpl->show("calendar.tpl");
2731 } // get_calendar_matrix()
2734 * output export page
2735 * @param string $mode
2737 public function getExport($mode)
2739 $pictures = $this->getPhotoSelection();
2740 $current_tags = $this->getCurrentTags();
2742 foreach($pictures as $picture) {
2744 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2745 if($current_tags != "") {
2746 $orig_url.= "&tags=". $current_tags;
2748 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2749 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2752 if($this->is_user_friendly_url()) {
2753 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2756 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2762 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2763 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2767 // "[%pictureurl% %thumbnailurl%]"
2768 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2771 case 'MoinMoinList':
2772 // " * [%pictureurl% %thumbnailurl%]"
2773 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2784 public function getRSSFeed()
2786 Header("Content-type: text/xml; charset=utf-8");
2787 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2790 xmlns:media="http://search.yahoo.com/mrss/"
2791 xmlns:dc="http://purl.org/dc/elements/1.1/"
2794 <title>phpfspot</title>
2795 <description>phpfspot RSS feed</description>
2796 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2797 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2798 <generator>phpfspot</generator>
2801 $pictures = $this->getPhotoSelection();
2802 $current_tags = $this->getCurrentTags();
2804 foreach($pictures as $picture) {
2806 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2807 if($current_tags != "") {
2808 $orig_url.= "&tags=". $current_tags;
2810 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2811 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2814 $details = $this->get_photo_details($picture);
2816 if($this->is_user_friendly_url()) {
2817 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2820 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2823 $thumb_html = htmlspecialchars("
2824 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2826 ". $details['description']);
2828 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2830 /* get EXIF information if JPEG */
2831 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2832 $meta = $this->get_meta_informations($orig_path);
2837 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2838 <link><?php print htmlspecialchars($orig_url); ?></link>
2839 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2840 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2842 <?php print $thumb_html; ?>
2844 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2859 * get all selected tags
2861 * This function will return all selected tags as one string, seperated
2865 private function getCurrentTags()
2868 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2869 foreach($_SESSION['selected_tags'] as $tag)
2870 $current_tags.= $tag .",";
2871 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2873 return $current_tags;
2875 } // getCurrentTags()
2878 * return the current photo
2880 public function get_current_photo()
2882 if(isset($_SESSION['current_photo'])) {
2883 return $_SESSION['current_photo'];
2888 } // get_current_photo()
2891 * current selected photo version
2893 * this function returns the current selected photo version
2894 * from the session variables.
2898 public function get_current_version()
2900 /* if current version is set, return it, if the photo really has that version */
2901 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2902 return $_SESSION['current_version'];
2906 } // get_current_version()
2909 * returns latest available photo version
2911 * this function returns the latested available version
2912 * for the requested photo.
2916 public function get_latest_version($photo_idx)
2918 /* try to get the lasted version for the current photo */
2919 if($versions = $this->get_photo_versions($photo_idx))
2920 return $versions[count($versions)-1];
2922 /* if no alternative version were found, return original version */
2925 } // get_current_version()
2928 * tells the client browser what to do
2930 * this function is getting called via AJAX by the
2931 * client browsers. it will tell them what they have
2932 * to do next. This is necessary for directly jumping
2933 * into photo index or single photo view when the are
2934 * requested with specific URLs
2937 public function whatToDo()
2939 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2941 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2942 return "showpi_tags";
2944 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2951 * return the current process-user
2954 private function getuid()
2956 if($uid = posix_getuid()) {
2957 if($user = posix_getpwuid($uid)) {
2958 return $user['name'];
2967 * photo version select list
2969 * this function returns a HTML select list (drop down)
2970 * to select a alternative photo version of the original photo.
2972 * @param array $params
2973 * @param smarty $smarty
2976 public function smarty_photo_version_select_list($params, &$smarty)
2978 if(!isset($params['photo']) || !isset($params['current']))
2981 $output = "<option value=\"0\">Original</option>";
2982 $versions = $this->get_photo_versions($params['photo']);
2984 foreach($versions as $version) {
2986 $output.= "<option value=\"". $version ."\"";
2987 if($version == $params['current']) {
2988 $output.= " selected=\"selected\"";
2990 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
2995 } // smarty_photo_version_select_list()
2998 * returns a select-dropdown box to select photo index sort parameters
2999 * @param array $params
3000 * @param smarty $smarty
3003 public function smarty_sort_select_list($params, &$smarty)
3007 foreach($this->sort_orders as $key => $value) {
3008 $output.= "<option value=\"". $key ."\"";
3009 if($key == $_SESSION['sort_order']) {
3010 $output.= " selected=\"selected\"";
3012 $output.= ">". $value ."</option>";
3017 } // smarty_sort_select_list()
3020 * returns the currently selected sort order
3023 private function get_sort_order()
3025 switch($_SESSION['sort_order']) {
3027 return " ORDER BY p.time ASC";
3030 return " ORDER BY p.time DESC";
3033 if($this->dbver < 9) {
3034 return " ORDER BY p.name ASC";
3037 return " ORDER BY basename(p.uri) ASC";
3041 if($this->dbver < 9) {
3042 return " ORDER BY p.name DESC";
3045 return " ORDER BY basename(p.uri) DESC";
3049 return " ORDER BY t.name ASC ,p.time ASC";
3052 return " ORDER BY t.name DESC ,p.time ASC";
3055 return " ORDER BY p.rating ASC, t.name ASC";
3058 return " ORDER BY p.rating DESC, t.name ASC";
3062 } // get_sort_order()
3065 * return the next to be shown slide show image
3067 * this function returns the URL of the next image
3068 * in the slideshow sequence.
3071 public function getNextSlideShowImage()
3073 $all_photos = $this->getPhotoSelection();
3075 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3076 $_SESSION['slideshow_img'] = 0;
3078 $_SESSION['slideshow_img']++;
3080 if($this->is_user_friendly_url()) {
3081 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3084 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3086 } // getNextSlideShowImage()
3089 * return the previous to be shown slide show image
3091 * this function returns the URL of the previous image
3092 * in the slideshow sequence.
3095 public function getPrevSlideShowImage()
3097 $all_photos = $this->getPhotoSelection();
3099 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3100 $_SESSION['slideshow_img'] = 0;
3102 $_SESSION['slideshow_img']--;
3104 if($this->is_user_friendly_url()) {
3105 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3108 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3110 } // getPrevSlideShowImage()
3112 public function resetSlideShow()
3114 if(isset($_SESSION['slideshow_img']))
3115 unset($_SESSION['slideshow_img']);
3117 } // resetSlideShow()
3122 * this function will get all photos from the fspot
3123 * database and randomly return ONE entry
3125 * saddly there is yet no sqlite3 function which returns
3126 * the bulk result in array, so we have to fill up our
3130 public function get_random_photo()
3139 /* if show_tags is set, only return details for photos which
3140 are specified to be shown
3142 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3144 INNER JOIN photo_tags pt
3149 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3152 $result = $this->db->db_query($query_str);
3154 while($row = $this->db->db_fetch_object($result)) {
3155 array_push($all, $row['id']);
3158 return $all[array_rand($all)];
3160 } // get_random_photo()
3163 * get random photo tag photo
3165 * this function will get all photos tagged with the requested
3166 * tag from the fspot database and randomly return ONE entry
3168 * saddly there is yet no sqlite3 function which returns
3169 * the bulk result in array, so we have to fill up our
3173 public function get_random_tag_photo($tagidx)
3177 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3180 DISTINCT pt1.photo_id as id
3183 INNER JOIN photo_tags
3184 pt2 ON pt1.photo_id=pt2.photo_id
3190 pt1.tag_id LIKE '". $tagidx ."'
3192 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3194 t1.sort_priority ASC";
3202 INNER JOIN photo_tags pt
3205 pt.tag_id LIKE '". $tagidx ."'";
3208 $result = $this->db->db_query($query_str);
3210 while($row = $this->db->db_fetch_object($result)) {
3211 array_push($all, $row['id']);
3214 return $all[array_rand($all)];
3216 } // get_random_tag_photo()
3219 * validates provided date
3221 * this function validates if the provided date
3222 * contains a valid date and will return true
3224 * @param string $date_str
3227 public function isValidDate($date_str)
3229 $timestamp = strtotime($date_str);
3231 if(is_numeric($timestamp))
3239 * timestamp to string conversion
3240 * @param integer $timestamp
3243 private function ts2str($timestamp)
3245 if(!empty($timestamp) && is_numeric($timestamp))
3246 return strftime("%Y-%m-%d", $timestamp);
3251 * extract tag-names from $_GET['tags']
3252 * @param string $tags_str
3255 private function extractTags($tags_str)
3257 $not_validated = split(',', $tags_str);
3258 $validated = array();
3260 foreach($not_validated as $tag) {
3261 if(is_numeric($tag))
3262 array_push($validated, $tag);
3270 * returns the full path to a thumbnail
3271 * @param integer $width
3272 * @param integer $photo
3275 public function get_thumb_path($width, $photo_idx, $version_idx)
3277 $md5 = $this->getMD5($photo_idx, $version_idx);
3278 $sub_path = substr($md5, 0, 2);
3279 return $this->cfg->thumb_path
3287 } // get_thumb_path()
3290 * returns server's virtual host name
3293 private function get_server_name()
3295 return $_SERVER['SERVER_NAME'];
3296 } // get_server_name()
3299 * returns type of webprotocol which is currently used
3302 private function get_web_protocol()
3304 if(!isset($_SERVER['HTTPS']))
3308 } // get_web_protocol()
3311 * return url to this phpfspot installation
3314 private function get_phpfspot_url()
3316 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3318 } // get_phpfspot_url()
3321 * returns the number of photos which are tagged with $tag_id
3322 * @param integer $tag_id
3325 public function get_num_photos($tag_id)
3327 if($result = $this->db->db_fetchSingleRow("
3328 SELECT count(*) as number
3331 tag_id LIKE '". $tag_id ."'")) {
3333 return $result['number'];
3339 } // get_num_photos()
3342 * check file exists and is readable
3344 * returns true, if everything is ok, otherwise false
3345 * if $silent is not set, this function will output and
3347 * @param string $file
3348 * @param boolean $silent
3351 private function check_readable($file, $silent = null)
3353 if(!file_exists($file)) {
3355 print "File \"". $file ."\" does not exist.\n";
3359 if(!is_readable($file)) {
3361 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3367 } // check_readable()
3370 * check if all needed indices are present
3372 * this function checks, if some needed indices are already
3373 * present, or if not, create them on the fly. they are
3374 * necessary to speed up some queries like that one look for
3375 * all tags, when show_tags is specified in the configuration.
3377 private function checkDbIndices()
3379 $result = $this->db->db_exec("
3380 CREATE INDEX IF NOT EXISTS
3387 } // checkDbIndices()
3390 * retrive F-Spot database version
3392 * this function will return the F-Spot database version number
3393 * It is stored within the sqlite3 database in the table meta
3394 * @return string|null
3396 public function getFspotDBVersion()
3398 if($result = $this->db->db_fetchSingleRow("
3399 SELECT data as version
3402 name LIKE 'F-Spot Database Version'
3404 return $result['version'];
3408 } // getFspotDBVersion()
3411 * parse the provided URI and will returned the requested chunk
3412 * @param string $uri
3413 * @param string $mode
3416 public function parse_uri($uri, $mode)
3418 if(($components = parse_url($uri)) !== false) {
3422 return basename($components['path']);
3425 return dirname($components['path']);
3428 return $components['path'];
3438 * validate config options
3440 * this function checks if all necessary configuration options are
3441 * specified and set.
3444 private function check_config_options()
3446 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3447 $this->_error("Please set \$page_title in phpfspot_cfg");
3449 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3450 $this->_error("Please set \$base_path in phpfspot_cfg");
3452 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3453 $this->_error("Please set \$web_path in phpfspot_cfg");
3455 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3456 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3458 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3459 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3461 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3462 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3464 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3465 $this->_error("Please set \$db_access in phpfspot_cfg");
3467 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3468 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3470 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3471 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3473 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3474 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3476 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3477 $this->_error("Please set \$photo_width in phpfspot_cfg");
3479 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3480 $this->_error("Please set \$mini_width in phpfspot_cfg");
3482 if(!isset($this->cfg->thumbs_per_page))
3483 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3485 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3486 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3488 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3489 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3491 if(!isset($this->cfg->hide_tags))
3492 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3494 if(!isset($this->cfg->theme_name))
3495 $this->_error("Please set \$theme_name in phpfspot_cfg");
3497 if(!isset($this->cfg->logging))
3498 $this->_error("Please set \$logging in phpfspot_cfg");
3500 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3502 if(!isset($this->cfg->log_file))
3503 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3505 if(!is_writeable($this->cfg->log_file))
3506 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3510 /* remove trailing slash, if set */
3511 if($this->cfg->web_path == "/")
3512 $this->cfg->web_path = "";
3513 elseif(preg_match('/\/$/', $this->cfg->web_path))
3514 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3516 return $this->runtime_error;
3518 } // check_config_options()
3521 * cleanup phpfspot own database
3523 * When photos are getting delete from F-Spot, there will remain
3524 * remain some residues in phpfspot own database. This function
3525 * will try to wipe them out.
3527 public function cleanup_phpfspot_db()
3529 $to_delete = Array();
3531 $result = $this->cfg_db->db_query("
3532 SELECT img_idx as img_idx
3534 ORDER BY img_idx ASC
3537 while($row = $this->cfg_db->db_fetch_object($result)) {
3538 if(!$this->db->db_fetchSingleRow("
3541 WHERE id='". $row['img_idx'] ."'")) {
3543 array_push($to_delete, $row['img_idx'], ',');
3547 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3549 $this->cfg_db->db_exec("
3551 WHERE img_idx IN (". implode($to_delete) .")
3554 } // cleanup_phpfspot_db()
3557 * return first image of the page, the $current photo
3560 * this function is used to find out the first photo of the
3561 * current page, in which the $current photo lies. this is
3562 * used to display the correct photo, when calling showPhotoIndex()
3564 * @param integer $current
3565 * @param integer $max
3568 private function getCurrentPage($current, $max)
3570 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3571 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3572 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3578 } // getCurrentPage()
3583 * this function tries to find out the correct mime-type
3584 * for the provided file.
3585 * @param string $file
3588 public function get_mime_info($file)
3590 $details = getimagesize($file);
3592 /* if getimagesize() returns empty, try at least to find out the
3595 if(empty($details) && function_exists('mime_content_type')) {
3597 // mime_content_type is marked as deprecated in the documentation,
3598 // but is it really necessary to force users to install a PECL
3600 $details['mime'] = mime_content_type($file);
3603 return $details['mime'];
3605 } // get_mime_info()
3608 * return tag-name by tag-idx
3610 * this function returns the tag-name for the requested
3611 * tag specified by tag-idx.
3612 * @param integer $idx
3615 public function get_tag_name($idx)
3617 if($result = $this->db->db_fetchSingleRow("
3621 id LIKE '". $idx ."'")) {
3623 return $result['name'];
3632 * parse user friendly url which got rewritten by the websever
3633 * @param string $request_uri
3636 private function parse_user_friendly_url($request_uri)
3638 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3640 $options = explode('/', $request_uri);
3642 switch($options[1]) {
3644 if(is_numeric($options[2])) {
3645 $this->session_cleanup();
3646 //unset($_SESSION['start_action']);
3647 //unset($_SESSION['selected_tags']);
3648 $_GET['mode'] = 'showp';
3649 return $this->showPhoto($options[2]);
3653 if(is_numeric($options[2])) {
3656 if(isset($options[3]) && is_numeric($options[3]))
3657 $width = $options[3];
3658 if(isset($options[4]) && is_numeric($options[4]))
3659 $version = $options[4];
3660 require_once "phpfspot_img.php";
3661 $img = new PHPFSPOT_IMG;
3662 $img->showImg($options[2], $width, $version);
3667 if(is_numeric($options[2])) {
3668 $this->session_cleanup();
3669 $_GET['tags'] = $options[2];
3670 $_SESSION['selected_tags'] = Array($options[2]);
3671 if(isset($options[3]) && is_numeric($options[3]))
3672 $_SESSION['begin_with'] = $options[3];
3673 return $this->showPhotoIndex();
3679 } // parse_user_friendly_url()
3682 * check if user-friendly-urls are enabled
3684 * this function will return true, if the config option
3685 * $user_friendly_url has been set. Otherwise false.
3688 private function is_user_friendly_url()
3690 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3695 } // is_user_friendly_url()
3700 * this function will cleanup user's session information
3702 private function session_cleanup()
3704 unset($_SESSION['begin_with']);
3705 $this->resetDateSearch();
3706 $this->resetPhotoView();
3707 $this->resetTagSearch();
3708 $this->resetNameSearch();
3709 $this->resetDateSearch();
3712 } // session_cleanup()
3715 * get database version
3717 * this function queries the meta table
3718 * and returns the current database version.
3722 public function get_db_version()
3724 if($row = $this->cfg_db->db_fetchSingleRow("
3725 SELECT meta_value as meta_value
3729 meta_key LIKE 'phpfspot Database Version'
3732 return $row['meta_value'];
3738 } // get_db_version()
3741 * get photo versions
3743 * this function returns an array of all available
3744 * alterntaive versions of the provided photo id.
3745 * has alternative photo versions available
3750 public function get_photo_versions($idx)
3752 $versions = Array();
3754 $result = $this->db->db_query("
3760 photo_id LIKE '". $idx ."'");
3762 while($row = $this->cfg_db->db_fetch_object($result)) {
3763 array_push($versions, $row['version_id']);
3768 } // get_photo_versions()
3771 * check for invalid version of photo
3773 * this function validates the provided photo-id and version-id
3775 * @param int $photo_idx
3776 * @param int $version_idx
3779 public function is_valid_version($photo_idx, $version_idx)
3781 /* the original version is always valid */
3782 if($version_idx == 0)
3785 if($versions = $this->get_photo_versions($photo_idx)) {
3786 if(in_array($version_idx, $versions))
3792 } // is_valid_version()
3795 * get photo version name
3797 * this function returns the name of the version
3798 * identified by the photo-id and version-id.
3800 * @param int $photo_idx
3801 * @param int $version_idx
3804 public function get_photo_version_name($photo_idx, $version_idx)
3806 if($row = $this->db->db_fetchSingleRow("
3812 photo_id LIKE '". $photo_idx ."'
3814 version_id LIKE '". $version_idx ."'")) {
3816 return $row['name'];
3822 } // get_photo_version_name()
3826 public function is_valid_width($image_width)
3828 if(in_array($image_width,
3829 Array($this->cfg->thumb_width,
3830 $this->cfg->photo_width,
3831 $this->cfg->mini_width,
3838 } // is_valid_width()