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 /* pre-set some template variables */
217 $this->tmpl->assign('web_path', $this->cfg->web_path);
219 /* Starting with F-Spot 0.4.2, the rating-feature was available */
220 if($this->dbver > 10) {
221 $this->tmpl->assign('has_rating', true);
222 $this->sort_orders = array_merge($this->sort_orders, array(
223 'rate_asc' => 'Rate ↑',
224 'rate_desc' => 'Rate ↓',
228 /* check if all necessary indices exist */
229 $this->checkDbIndices();
231 /* if session is not yet started, do it now */
232 if(session_id() == "")
235 if(!isset($_SESSION['tag_condition']))
236 $_SESSION['tag_condition'] = 'or';
238 /* if sort-order has not been set yet, get the one specified in the config */
239 if(!isset($_SESSION['sort_order']))
240 $_SESSION['sort_order'] = $this->cfg->sort_order;
242 if(!isset($_SESSION['searchfor_tag']))
243 $_SESSION['searchfor_tag'] = '';
245 // if begin_with is still set but thumbs_per_page is now 0, unset it
246 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
247 unset($_SESSION['begin_with']);
249 // if user-friendly-url's are enabled, set also a flag for the template handler
250 if($this->is_user_friendly_url()) {
251 $this->tmpl->assign('user_friendly_url', 'true');
256 public function __destruct()
262 * show - generate html output
264 * this function can be called after the constructor has
265 * prepared everyhing. it will load the index.tpl smarty
266 * template. if necessary it will registere pre-selects
267 * (photo index, photo, tag search, date search) into
270 public function show()
272 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
273 $this->tmpl->assign('page_title', $this->cfg->page_title);
274 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
275 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
278 if($this->is_user_friendly_url()) {
279 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
282 if(isset($_GET['mode'])) {
284 $_SESSION['start_action'] = $_GET['mode'];
286 switch($_GET['mode']) {
288 if(isset($_GET['tags'])) {
289 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
291 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
292 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
294 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
295 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
299 if(isset($_GET['tags'])) {
300 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
301 $_SESSION['start_action'] = 'showp';
303 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
304 if($_SESSION['current_photo'] != $_GET['id'])
305 unset($_SESSION['current_version']);
306 $_SESSION['current_photo'] = $_GET['id'];
307 $_SESSION['start_action'] = 'showp';
309 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
310 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
312 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
313 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
317 /* fetch export template */
318 print $this->tmpl->fetch("export.tpl");
319 /* no further execution necessary. */
323 /* fetch slideshow template */
324 print $this->tmpl->show("slideshow.tpl");
325 /* no further execution necessary. */
329 if(isset($_GET['tags'])) {
330 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
332 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
333 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
335 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
336 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
344 /* if date-search variables are registered in the session, set the check
345 for "consider date-range" in the html output
347 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
348 $this->tmpl->assign('date_search_enabled', true);
350 /* if rate-search variables are registered in the session, set the check
351 for "consider rate-range" in the html output
353 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
354 $this->tmpl->assign('rate_search_enabled', true);
357 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
358 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
359 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
361 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
362 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
363 $this->tmpl->assign('rate_search', $this->get_rate_search());
365 /* if no site-content has been set yet... */
366 if(!isset($content)) {
367 /* if tags are already selected, we can immediately display photo-index */
368 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
369 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
370 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
371 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
373 /* if a photo is already selected, we can immediately display single-photo */
374 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
375 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
377 /* ok, then let us show the welcome page... */
378 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
383 $this->tmpl->assign('initial_content', $content);
385 $this->tmpl->show("index.tpl");
390 * get_tags - grab all tags of f-spot's database
392 * this function will get all available tags from
393 * the f-spot database and store them within two
394 * arrays within this class for later usage. in
395 * fact, if the user requests (hide_tags) it will
396 * opt-out some of them.
398 * this function is getting called once by show()
400 private function get_tags()
402 $this->avail_tags = Array();
405 /* if show_tags has been set in the configuration (only show photos
406 which are tagged by these tags) they following will take care,
407 that only these other tags are displayed where the photo is also
408 tagged with one of show_tags.
410 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
413 DISTINCT t1.id as id, t1.name as name
416 INNER JOIN photo_tags
417 pt2 ON pt1.photo_id=pt2.photo_id
423 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
425 t1.sort_priority ASC";
427 $result = $this->db->db_query($query_str);
431 $result = $this->db->db_query("
432 SELECT id as id,name as name
434 ORDER BY sort_priority ASC
438 while($row = $this->db->db_fetch_object($result)) {
440 $tag_id = $row['id'];
441 $tag_name = $row['name'];
443 /* if the user has specified to ignore this tag in phpfspot's
444 configuration, ignore it here so it does not get added to
447 if(in_array($row['name'], $this->cfg->hide_tags))
450 /* if you include the following if-clause and the user has specified
451 to only show certain tags which are specified in phpfspot's
452 configuration, ignore all others so they will not be added to the
454 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
455 !in_array($row['name'], $this->cfg->show_tags))
459 $this->tags[$tag_id] = $tag_name;
460 $this->avail_tags[$count] = $tag_id;
468 * get all photo details from F-Spot database
470 * this function queries the F-Spot database for all available
471 * details of the requested photo. It returns them as a object.
473 * Furthermore it takes care of the photo version to be requested.
474 * If photo version is not yet, it queries information for the
477 * @param integer $idx
478 * @return object|null
480 public function get_photo_details($idx, $version_idx = NULL)
482 /* ~ F-Spot version 0.3.x */
483 if($this->dbver < 9) {
489 p.directory_path as directory_path,
490 p.description as description
496 /* till F-Spot version 0.4.1 */
497 if($this->dbver < 11) {
503 p.description as description
509 /* rating value got introduced */
515 p.description as description,
523 /* if show_tags is set, only return details of photos which are
524 tagged with a tag that has been specified to be shown.
526 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
528 INNER JOIN photo_tags pt
532 WHERE p.id='". $idx ."'
533 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
537 WHERE p.id='". $idx ."'
541 if($row = $this->db->db_fetchSingleRow($query_str)) {
543 /* before F-Spot db version 9 there was no uri column but
544 seperated fields for directory_path and name (= filename).
546 if($this->dbver < 9) {
547 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
550 /* if version-idx has not yet been set, get the latest photo version */
551 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
552 $version_idx = $this->get_latest_version($idx);
554 /* if an alternative version has been requested. But we
555 support this only for F-Spot database versions from
558 if($version_idx > 0 && $this->dbver >= 9) {
559 /* check for alternative versions */
560 if($version = $this->db->db_fetchSingleRow("
562 version_id, name, uri
566 photo_id LIKE '". $idx ."'
568 version_id LIKE '". $version_idx ."'")) {
570 $row['name'] = $version['name'];
571 $row['uri'] = $version['uri'];
581 } // get_photo_details()
584 * returns aligned photo names
586 * this function returns aligned (length) names for a specific photo.
587 * If the length of the name exceeds $limit the name will bei
590 * @param integer $idx
591 * @param integer $limit
592 * @return string|null
594 public function getPhotoName($idx, $limit = 0)
596 if($details = $this->get_photo_details($idx)) {
597 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
598 $name = $this->shrink_text($long_name, $limit);
608 * get photo rating level
610 * this function will return the integer-based rating level of a
611 * photo. This can only be done, if the F-Spot database is at a
612 * specific version. If rating value can not be found, zero will
613 * be returned indicating no rating value is available.
618 public function get_photo_rating($idx)
620 if($detail = $this->get_photo_details($idx)) {
621 if(isset($detail['rating']))
622 return $detail['rating'];
627 } // get_photo_rating()
630 * get rate-search bars
632 * this function will return the rating-bars for the search field.
636 public function get_rate_search()
640 for($i = 1; $i <= 5; $i++) {
642 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
644 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
645 $bar.= $this->cfg->web_path ."/resources/star.png";
647 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
650 onmouseover=\"show_rate('from', ". $i .");\"
651 onmouseout=\"reset_rate('from');\"
652 onclick=\"set_rate('from', ". $i .")\" />";
657 for($i = 1; $i <= 5; $i++) {
659 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
661 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
662 $bar.= $this->cfg->web_path ."/resources/star.png";
664 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
667 onmouseover=\"show_rate('to', ". $i .");\"
668 onmouseout=\"reset_rate('to');\"
669 onclick=\"set_rate('to', ". $i .");\" />";
674 } // get_rate_search()
677 * shrink text according provided limit
679 * If the length of the name exceeds $limit, text will be shortend
680 * and inner content will be replaced with "...".
683 * @param integer $limit
686 private function shrink_text($text, $limit)
688 if($limit != 0 && strlen($text) > $limit) {
689 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
697 * translate f-spoth photo path
699 * as the full-qualified path recorded in the f-spot database
700 * is usally not the same as on the webserver, this function
701 * will replace the path with that one specified in the cfg
702 * @param string $path
705 public function translate_path($path)
707 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
712 * control HTML ouput for a single photo
714 * this function provides all the necessary information
715 * for the single photo template.
716 * @param integer photo
718 public function showPhoto($photo)
720 /* get all photos from the current photo selection */
721 $all_photos = $this->getPhotoSelection();
722 $count = count($all_photos);
724 for($i = 0; $i < $count; $i++) {
726 // $get_next will be set, when the photo which has to
727 // be displayed has been found - this means that the
728 // next available is in fact the NEXT image (for the
730 if(isset($get_next)) {
731 $next_img = $all_photos[$i];
735 /* the next photo is our NEXT photo */
736 if($all_photos[$i] == $photo) {
740 $previous_img = $all_photos[$i];
743 if($photo == $all_photos[$i]) {
748 $details = $this->get_photo_details($photo);
755 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
757 /* if current version is already set, use it */
758 if($this->get_current_version() !== false)
759 $version = $this->get_current_version();
761 /* if version not set yet, we assume to display the latest version */
762 if(!isset($version) || !$this->is_valid_version($photo, $version))
763 $version = $this->get_latest_version($photo);
765 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
767 if(!file_exists($orig_path)) {
768 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
772 if(!is_readable($orig_path)) {
773 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
777 /* If the thumbnail doesn't exist yet, try to create it */
778 if(!file_exists($thumb_path)) {
779 $this->gen_thumb($photo, true);
780 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
783 /* get mime-type, height and width from the original photo */
784 $info = getimagesize($orig_path);
786 /* get EXIF information if JPEG */
787 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
788 $meta = $this->get_meta_informations($orig_path);
791 /* If EXIF data are available, use them */
792 if(isset($meta['ExifImageWidth'])) {
793 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
795 $meta_res = $info[0] ."x". $info[1];
798 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
799 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
801 $extern_link = "index.php?mode=showp&id=". $photo;
802 $current_tags = $this->getCurrentTags();
803 if($current_tags != "") {
804 $extern_link.= "&tags=". $current_tags;
806 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
807 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
810 $this->tmpl->assign('extern_link', $extern_link);
812 if(!file_exists($thumb_path)) {
813 $this->_error("Can't open file ". $thumb_path ."\n");
817 $info_thumb = getimagesize($thumb_path);
819 $this->tmpl->assign('description', $details['description']);
820 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
821 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
823 $this->tmpl->assign('width', $info_thumb[0]);
824 $this->tmpl->assign('height', $info_thumb[1]);
825 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
826 $this->tmpl->assign('ExifMadeWith', $meta_make);
827 $this->tmpl->assign('ExifOrigResolution', $meta_res);
828 $this->tmpl->assign('ExifFileSize', $meta_size);
830 if($this->is_user_friendly_url()) {
831 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
832 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
835 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
836 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
839 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
841 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
842 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
843 $this->tmpl->assign('current_img', $photo);
845 if(isset($previous_img)) {
846 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
847 $this->tmpl->assign('prev_img', $previous_img);
850 if(isset($next_img)) {
851 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
852 $this->tmpl->assign('next_img', $next_img);
855 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
856 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
857 $this->tmpl->assign('photo_number', $i);
858 $this->tmpl->assign('photo_count', count($all_photos));
859 $this->tmpl->assign('photo', $photo);
860 $this->tmpl->assign('version', $version);
862 /* if the photo as alternative versions, set a flag for the template */
863 if($this->get_photo_versions($photo))
864 $this->tmpl->assign('has_versions', true);
866 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
868 return $this->tmpl->fetch("single_photo.tpl");
873 * all available tags and tag cloud
875 * this function outputs all available tags (time ordered)
876 * and in addition output them as tag cloud (tags which have
877 * many photos will appears more then others)
879 public function getAvailableTags()
881 /* retrive tags from database */
886 $result = $this->db->db_query("
887 SELECT tag_id as id, count(tag_id) as quantity
897 while($row = $this->db->db_fetch_object($result)) {
898 $tags[$row['id']] = $row['quantity'];
901 // change these font sizes if you will
902 $max_size = 125; // max font size in %
903 $min_size = 75; // min font size in %
906 $max_sat = hexdec('cc');
907 $min_sat = hexdec('44');
909 // get the largest and smallest array values
910 $max_qty = max(array_values($tags));
911 $min_qty = min(array_values($tags));
913 // find the range of values
914 $spread = $max_qty - $min_qty;
915 if (0 == $spread) { // we don't want to divide by zero
919 // determine the font-size increment
920 // this is the increase per tag quantity (times used)
921 $step = ($max_size - $min_size)/($spread);
922 $step_sat = ($max_sat - $min_sat)/($spread);
924 // loop through our tag array
925 foreach ($tags as $key => $value) {
927 /* has the currently processed tag already been added to
928 the selected tag list? if so, ignore it here...
930 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
933 // calculate CSS font-size
934 // find the $value in excess of $min_qty
935 // multiply by the font-size increment ($size)
936 // and add the $min_size set above
937 $size = $min_size + (($value - $min_qty) * $step);
938 // uncomment if you want sizes in whole %:
941 $color = $min_sat + ($value - $min_qty) * $step_sat;
947 if(isset($this->tags[$key])) {
948 if($this->is_user_friendly_url()) {
949 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
950 onclick=\"Tags('add', ". $key ."); return false;\"
952 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
953 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
956 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
957 onclick=\"Tags('add', ". $key ."); return false;\"
959 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
960 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
965 $output = substr($output, 0, strlen($output)-2);
968 } // getAvailableTags()
971 * output all selected tags
973 * this function output all tags which have been selected
974 * by the user. the selected tags are stored in the
975 * session-variable $_SESSION['selected_tags']
978 public function getSelectedTags($type = 'link')
980 /* retrive tags from database */
985 foreach($this->avail_tags as $tag)
987 // return all selected tags
988 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
993 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
997 <div class=\"tagresulttag\">
998 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
999 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1009 $output = substr($output, 0, strlen($output)-2);
1013 return "no tags selected";
1016 } // getSelectedTags()
1019 * add tag to users session variable
1021 * this function will add the specified to users current
1022 * tag selection. if a date search has been made before
1023 * it will be now cleared
1026 public function addTag($tag)
1028 if(!isset($_SESSION['selected_tags']))
1029 $_SESSION['selected_tags'] = Array();
1031 if(isset($_SESSION['searchfor_tag']))
1032 unset($_SESSION['searchfor_tag']);
1034 // has the user requested to hide this tag, and still someone,
1035 // somehow tries to add it, don't allow this.
1036 if(!isset($this->cfg->hide_tags) &&
1037 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1040 if(!in_array($tag, $_SESSION['selected_tags']))
1041 array_push($_SESSION['selected_tags'], $tag);
1048 * remove tag to users session variable
1050 * this function removes the specified tag from
1051 * users current tag selection
1052 * @param string $tag
1055 public function delTag($tag)
1057 if(isset($_SESSION['searchfor_tag']))
1058 unset($_SESSION['searchfor_tag']);
1060 if(isset($_SESSION['selected_tags'])) {
1061 $key = array_search($tag, $_SESSION['selected_tags']);
1062 unset($_SESSION['selected_tags'][$key]);
1063 sort($_SESSION['selected_tags']);
1071 * reset tag selection
1073 * if there is any tag selection, it will be
1076 public function resetTags()
1078 if(isset($_SESSION['selected_tags']))
1079 unset($_SESSION['selected_tags']);
1084 * returns the value for the autocomplete tag-search
1087 public function get_xml_tag_list()
1089 if(!isset($_GET['search']) || !is_string($_GET['search']))
1090 $_GET['search'] = '';
1095 /* retrive tags from database */
1098 $matched_tags = Array();
1100 header("Content-Type: text/xml");
1102 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1103 $string.= "<results>\n";
1105 foreach($this->avail_tags as $tag)
1107 if(!empty($_GET['search']) &&
1108 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1109 count($matched_tags) < $length) {
1111 $count = $this->get_num_photos($tag);
1114 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1117 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1123 /* if we have collected enough items, break out */
1124 if(count($matched_tags) >= $length)
1128 $string.= "</results>\n";
1132 } // get_xml_tag_list()
1136 * reset single photo
1138 * if a specific photo was requested (external link)
1139 * unset the session variable now
1141 public function resetPhotoView()
1143 if(isset($_SESSION['current_photo']))
1144 unset($_SESSION['current_photo']);
1146 if(isset($_SESSION['current_version']))
1147 unset($_SESSION['current_version']);
1149 } // resetPhotoView();
1154 * if any tag search has taken place, reset it now
1156 public function resetTagSearch()
1158 if(isset($_SESSION['searchfor_tag']))
1159 unset($_SESSION['searchfor_tag']);
1161 } // resetTagSearch()
1166 * if any name search has taken place, reset it now
1168 public function resetNameSearch()
1170 if(isset($_SESSION['searchfor_name']))
1171 unset($_SESSION['searchfor_name']);
1173 } // resetNameSearch()
1178 * if any date search has taken place, reset it now.
1180 public function resetDateSearch()
1182 if(isset($_SESSION['from_date']))
1183 unset($_SESSION['from_date']);
1184 if(isset($_SESSION['to_date']))
1185 unset($_SESSION['to_date']);
1187 } // resetDateSearch();
1192 * if any rate search has taken place, reset it now.
1194 public function resetRateSearch()
1196 if(isset($_SESSION['rate_from']))
1197 unset($_SESSION['rate_from']);
1198 if(isset($_SESSION['rate_to']))
1199 unset($_SESSION['rate_to']);
1201 } // resetRateSearch();
1204 * return all photo according selection
1206 * this function returns all photos based on
1207 * the tag-selection, tag- or date-search.
1208 * the tag-search also has to take care of AND
1209 * and OR conjunctions
1212 public function getPhotoSelection()
1214 $matched_photos = Array();
1215 $additional_where_cond = "";
1217 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1218 $from_date = $_SESSION['from_date'];
1219 $to_date = $_SESSION['to_date'];
1220 $additional_where_cond.= "
1221 p.time>='". $from_date ."'
1223 p.time<='". $to_date ."'
1227 if(isset($_SESSION['searchfor_name'])) {
1229 /* check for previous conditions. if so add 'AND' */
1230 if(!empty($additional_where_cond)) {
1231 $additional_where_cond.= " AND ";
1234 if($this->dbver < 9) {
1235 $additional_where_cond.= "
1237 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1239 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1244 $additional_where_cond.= "
1246 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1248 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1254 /* limit result based on rate-search */
1255 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1257 if($this->dbver > 10) {
1259 /* check for previous conditions. if so add 'AND' */
1260 if(!empty($additional_where_cond)) {
1261 $additional_where_cond.= " AND ";
1264 $additional_where_cond.= "
1265 p.rating >= ". $_SESSION['rate_from'] ."
1267 p.rating <= ". $_SESSION['rate_to'] ."
1272 if(isset($_SESSION['sort_order'])) {
1273 $order_str = $this->get_sort_order();
1276 /* return a search result */
1277 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1280 pt1.photo_id as photo_id
1283 INNER JOIN photo_tags pt2
1284 ON pt1.photo_id=pt2.photo_id
1288 ON pt1.photo_id=p.id
1291 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1293 if(!empty($additional_where_cond))
1294 $query_str.= "AND ". $additional_where_cond ." ";
1296 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1297 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1300 if(isset($order_str))
1301 $query_str.= $order_str;
1303 $result = $this->db->db_query($query_str);
1304 while($row = $this->db->db_fetch_object($result)) {
1305 array_push($matched_photos, $row['photo_id']);
1307 return $matched_photos;
1310 /* return according the selected tags */
1311 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1313 foreach($_SESSION['selected_tags'] as $tag)
1314 $selected.= $tag .",";
1315 $selected = substr($selected, 0, strlen($selected)-1);
1317 /* photo has to match at least on of the selected tags */
1318 if($_SESSION['tag_condition'] == 'or') {
1321 pt1.photo_id as photo_id
1324 INNER JOIN photo_tags pt2
1325 ON pt1.photo_id=pt2.photo_id
1329 ON pt1.photo_id=p.id
1330 WHERE pt1.tag_id IN (". $selected .")
1332 if(!empty($additional_where_cond))
1333 $query_str.= "AND ". $additional_where_cond ." ";
1335 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1336 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1339 if(isset($order_str))
1340 $query_str.= $order_str;
1342 /* photo has to match all selected tags */
1343 elseif($_SESSION['tag_condition'] == 'and') {
1345 if(count($_SESSION['selected_tags']) >= 32) {
1346 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1347 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1351 /* Join together a table looking like
1353 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1355 so the query can quickly return all images matching the
1356 selected tags in an AND condition
1362 pt1.photo_id as photo_id
1367 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1374 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1376 INNER JOIN photo_tags pt". ($i+2) ."
1377 ON pt1.photo_id=pt". ($i+2) .".photo_id
1382 ON pt1.photo_id=p.id
1384 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1385 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1387 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1390 if(!empty($additional_where_cond))
1391 $query_str.= "AND ". $additional_where_cond;
1393 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1394 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1397 if(isset($order_str))
1398 $query_str.= $order_str;
1402 $result = $this->db->db_query($query_str);
1403 while($row = $this->db->db_fetch_object($result)) {
1404 array_push($matched_photos, $row['photo_id']);
1406 return $matched_photos;
1409 /* return all available photos */
1415 LEFT JOIN photo_tags pt
1421 if(!empty($additional_where_cond))
1422 $query_str.= "WHERE ". $additional_where_cond ." ";
1424 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1425 if(!empty($additional_where_cond))
1426 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1428 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1431 if(isset($order_str))
1432 $query_str.= $order_str;
1434 $result = $this->db->db_query($query_str);
1435 while($row = $this->db->db_fetch_object($result)) {
1436 array_push($matched_photos, $row['id']);
1438 return $matched_photos;
1440 } // getPhotoSelection()
1443 * control HTML ouput for photo index
1445 * this function provides all the necessary information
1446 * for the photo index template.
1449 public function showPhotoIndex()
1451 $photos = $this->getPhotoSelection();
1452 $current_tags = $this->getCurrentTags();
1454 $count = count($photos);
1456 /* if all thumbnails should be shown on one page */
1457 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1461 /* thumbnails should be splitted up in several pages */
1462 elseif($this->cfg->thumbs_per_page > 0) {
1464 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1468 $begin_with = $_SESSION['begin_with'];
1471 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1476 for($i = $begin_with; $i < $end_with; $i++) {
1478 if(!isset($photos[$i]))
1481 /* on first run, initalize all used variables */
1484 $images[$thumbs] = Array();
1485 $img_height[$thumbs] = Array();
1486 $img_width[$thumbs] = Array();
1487 $img_id[$thumbs] = Array();
1488 $img_name[$thumbs] = Array();
1489 $img_fullname[$thumbs] = Array();
1490 $img_title = Array();
1491 $img_rating = Array();
1494 $images[$thumbs] = $photos[$i];
1495 $img_id[$thumbs] = $i;
1496 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1497 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1498 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1499 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1501 /* get local path of the thumbnail image to be displayed */
1502 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1504 /* if the image exist and is readable, extract some details */
1505 if(file_exists($thumb_path) && is_readable($thumb_path)) {
1506 if($info = getimagesize($thumb_path) !== false) {
1507 $img_width[$thumbs] = $info[0];
1508 $img_height[$thumbs] = $info[1];
1514 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1515 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1517 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1518 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1519 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1522 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1523 $this->tmpl->assign('tag_result', 1);
1526 /* do we have to display the page selector ? */
1527 if($this->cfg->thumbs_per_page != 0) {
1531 /* calculate the page switchers */
1532 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1533 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1535 if($begin_with != 0)
1536 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1537 if($end_with < $count)
1538 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1540 $photo_per_page = $this->cfg->thumbs_per_page;
1541 $last_page = ceil($count / $photo_per_page);
1543 /* get the current selected page */
1544 if($begin_with == 0) {
1548 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1555 for($i = 1; $i <= $last_page; $i++) {
1557 if($current_page == $i)
1558 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1559 elseif($current_page-1 == $i || $current_page+1 == $i)
1560 $style = "style=\"font-size: 105%;\"";
1561 elseif(($current_page-5 >= $i) && ($i != 1) ||
1562 ($current_page+5 <= $i) && ($i != $last_page))
1563 $style = "style=\"font-size: 75%;\"";
1567 $start_with = ($i*$photo_per_page)-$photo_per_page;
1569 if($this->is_user_friendly_url()) {
1570 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1573 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1575 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1579 $select.= ">". $i ."</a> ";
1581 // until 9 pages we show the selector from 1-9
1582 if($last_page <= 9) {
1583 $page_select.= $select;
1586 if($i == 1 /* first page */ ||
1587 $i == $last_page /* last page */ ||
1588 $i == $current_page /* current page */ ||
1589 $i == ceil($last_page * 0.25) /* first quater */ ||
1590 $i == ceil($last_page * 0.5) /* half */ ||
1591 $i == ceil($last_page * 0.75) /* third quater */ ||
1592 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1593 (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 */ ||
1594 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1595 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1597 $page_select.= $select;
1605 $page_select.= "......... ";
1610 /* only show the page selector if we have more then one page */
1612 $this->tmpl->assign('page_selector', $page_select);
1615 $extern_link = "index.php?mode=showpi";
1616 $rss_link = "index.php?mode=rss";
1617 if($current_tags != "") {
1618 $extern_link.= "&tags=". $current_tags;
1619 $rss_link.= "&tags=". $current_tags;
1621 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1622 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1623 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1626 $export_link = "index.php?mode=export";
1627 $slideshow_link = "index.php?mode=slideshow";
1629 $this->tmpl->assign('extern_link', $extern_link);
1630 $this->tmpl->assign('slideshow_link', $slideshow_link);
1631 $this->tmpl->assign('export_link', $export_link);
1632 $this->tmpl->assign('rss_link', $rss_link);
1633 $this->tmpl->assign('count', $count);
1634 $this->tmpl->assign('width', $this->cfg->thumb_width);
1635 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1636 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1637 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1638 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1639 // +1 for for smarty's selection iteration
1640 $this->tmpl->assign('thumbs', $thumbs+1);
1643 $this->tmpl->assign('images', $images);
1644 $this->tmpl->assign('img_width', $img_width);
1645 $this->tmpl->assign('img_height', $img_height);
1646 $this->tmpl->assign('img_id', $img_id);
1647 $this->tmpl->assign('img_name', $img_name);
1648 $this->tmpl->assign('img_fullname', $img_fullname);
1649 $this->tmpl->assign('img_title', $img_title);
1650 $this->tmpl->assign('img_rating', $img_rating);
1653 $result = $this->tmpl->fetch("photo_index.tpl");
1655 /* if we are returning to photo index from an photo-view,
1656 scroll the window to the last shown photo-thumbnail.
1657 after this, unset the last_photo session variable.
1659 if(isset($_SESSION['last_photo'])) {
1660 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1661 unset($_SESSION['last_photo']);
1666 } // showPhotoIndex()
1669 * show credit template
1671 public function showCredits()
1673 $this->tmpl->assign('version', $this->cfg->version);
1674 $this->tmpl->assign('product', $this->cfg->product);
1675 $this->tmpl->assign('db_version', $this->dbver);
1676 $this->tmpl->show("credits.tpl");
1681 * create thumbnails for the requested width
1683 * this function creates image thumbnails of $orig_image
1684 * stored as $thumb_image. It will check if the image is
1685 * in a supported format, if necessary rotate the image
1686 * (based on EXIF orientation meta headers) and re-sizing.
1687 * @param string $orig_image
1688 * @param string $thumb_image
1689 * @param integer $width
1692 public function create_thumbnail($orig_image, $thumb_image, $width)
1694 if(!file_exists($orig_image)) {
1698 $mime = $this->get_mime_info($orig_image);
1700 /* check if original photo is a support image type */
1701 if(!$this->checkifImageSupported($mime))
1708 $meta = $this->get_meta_informations($orig_image);
1714 if(isset($meta['Orientation'])) {
1715 switch($meta['Orientation']) {
1716 case 1: /* top, left */
1717 /* nothing to do */ break;
1718 case 2: /* top, right */
1719 $rotate = 0; $flip_hori = true; break;
1720 case 3: /* bottom, left */
1721 $rotate = 180; break;
1722 case 4: /* bottom, right */
1723 $flip_vert = true; break;
1724 case 5: /* left side, top */
1725 $rotate = 90; $flip_vert = true; break;
1726 case 6: /* right side, top */
1727 $rotate = 90; break;
1728 case 7: /* left side, bottom */
1729 $rotate = 270; $flip_vert = true; break;
1730 case 8: /* right side, bottom */
1731 $rotate = 270; break;
1735 $src_img = @imagecreatefromjpeg($orig_image);
1741 $src_img = @imagecreatefrompng($orig_image);
1745 case 'image/x-portable-pixmap':
1747 $src_img = new Imagick($orig_image);
1748 $handler = "imagick";
1753 if(!isset($src_img) || empty($src_img)) {
1754 print "Can't load image from ". $orig_image ."\n";
1762 /* grabs the height and width */
1763 $cur_width = imagesx($src_img);
1764 $cur_height = imagesy($src_img);
1766 // If requested width is more then the actual image width,
1767 // do not generate a thumbnail, instead safe the original
1768 // as thumbnail but with lower quality. But if the image
1769 // is to heigh too, then we still have to resize it.
1770 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1771 $result = imagejpeg($src_img, $thumb_image, 75);
1772 imagedestroy($src_img);
1779 $cur_width = $src_img->getImageWidth();
1780 $cur_height = $src_img->getImageHeight();
1782 // If requested width is more then the actual image width,
1783 // do not generate a thumbnail, instead safe the original
1784 // as thumbnail but with lower quality. But if the image
1785 // is to heigh too, then we still have to resize it.
1786 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1787 $src_img->setCompressionQuality(75);
1788 $src_img->setImageFormat('jpeg');
1789 $src_img->writeImage($thumb_image);
1791 $src_img->destroy();
1798 // If the image will be rotate because EXIF orientation said so
1799 // 'virtually rotate' the image for further calculations
1800 if($rotate == 90 || $rotate == 270) {
1802 $cur_width = $cur_height;
1806 /* calculates aspect ratio */
1807 $aspect_ratio = $cur_height / $cur_width;
1810 if($aspect_ratio < 1) {
1812 $new_h = abs($new_w * $aspect_ratio);
1814 /* 'virtually' rotate the image and calculate it's ratio */
1815 $tmp_w = $cur_height;
1816 $tmp_h = $cur_width;
1817 /* now get the ratio from the 'rotated' image */
1818 $tmp_ratio = $tmp_h/$tmp_w;
1819 /* now calculate the new dimensions */
1821 $tmp_h = abs($tmp_w * $tmp_ratio);
1823 // now that we know, how high they photo should be, if it
1824 // gets rotated, use this high to scale the image
1826 $new_w = abs($new_h / $aspect_ratio);
1828 // If the image will be rotate because EXIF orientation said so
1829 // now 'virtually rotate' back the image for the image manipulation
1830 if($rotate == 90 || $rotate == 270) {
1841 /* creates new image of that size */
1842 $dst_img = imagecreatetruecolor($new_w, $new_h);
1844 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1846 /* copies resized portion of original image into new image */
1847 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1849 /* needs the image to be flipped horizontal? */
1851 $this->_debug("(FLIP)");
1852 $dst_img = $this->flipImage($dst_img, 'hori');
1854 /* needs the image to be flipped vertical? */
1856 $this->_debug("(FLIP)");
1857 $dst_img = $this->flipImage($dst_img, 'vert');
1861 $this->_debug("(ROTATE)");
1862 $dst_img = $this->rotateImage($dst_img, $rotate);
1865 /* write down new generated file */
1866 $result = imagejpeg($dst_img, $thumb_image, 75);
1868 /* free your mind */
1869 imagedestroy($dst_img);
1870 imagedestroy($src_img);
1872 if($result === false) {
1873 print "Can't write thumbnail ". $thumb_image ."\n";
1883 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1885 /* needs the image to be flipped horizontal? */
1887 $this->_debug("(FLIP)");
1888 $src_img->rotateImage(new ImagickPixel(), 90);
1889 $src_img->flipImage();
1890 $src_img->rotateImage(new ImagickPixel(), -90);
1892 /* needs the image to be flipped vertical? */
1894 $this->_debug("(FLIP)");
1895 $src_img->flipImage();
1899 $this->_debug("(ROTATE)");
1900 $src_img->rotateImage(new ImagickPixel(), $rotate);
1903 $src_img->setCompressionQuality(75);
1904 $src_img->setImageFormat('jpeg');
1906 if(!$src_img->writeImage($thumb_image)) {
1907 print "Can't write thumbnail ". $thumb_image ."\n";
1912 $src_img->destroy();
1919 } // create_thumbnail()
1922 * return all exif meta data from the file
1923 * @param string $file
1926 public function get_meta_informations($file)
1928 return exif_read_data($file);
1930 } // get_meta_informations()
1933 * create phpfspot own sqlite database
1935 * this function creates phpfspots own sqlite database
1936 * if it does not exist yet. this own is used to store
1937 * some necessary informations (md5 sum's, ...).
1939 public function check_phpfspot_db()
1941 // if the config table doesn't exist yet, create it
1942 if(!$this->cfg_db->db_check_table_exists("images")) {
1943 $this->cfg_db->db_exec("
1944 CREATE TABLE images (
1946 img_version_idx int,
1947 img_md5 varchar(32),
1948 UNIQUE(img_idx, img_version_idx)
1953 if(!$this->cfg_db->db_check_table_exists("meta")) {
1954 $this->cfg_db->db_exec("
1956 meta_key varchar(255),
1957 meta_value varchar(255)
1961 /* db_version was added with phpfspot 1.7, before changes
1962 on the phpfspot database where not necessary.
1965 $this->cfg_db->db_exec("
1967 meta_key, meta_value
1969 'phpfspot Database Version',
1970 '". $this->cfg->db_version ."'
1975 /* if version <= 2 and column img_version_idx does not exist yet */
1976 if($this->get_db_version() <= 2 &&
1977 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
1979 if(!$this->cfg_db->db_start_transaction())
1980 die("Can not start database transaction");
1982 $result = $this->cfg_db->db_exec("
1983 CREATE TEMPORARY TABLE images_temp (
1985 img_version_idx int,
1986 img_md5 varchar(32),
1987 UNIQUE(img_idx, img_version_idx)
1992 $this->cfg_db->db_rollback_transaction();
1993 die("Upgrade failed - transaction rollback");
1996 $result = $this->cfg_db->db_exec("
1997 INSERT INTO images_temp
2006 $this->cfg_db->db_rollback_transaction();
2007 die("Upgrade failed - transaction rollback");
2010 $result = $this->cfg_db->db_exec("
2015 $this->cfg_db->db_rollback_transaction();
2016 die("Upgrade failed - transaction rollback");
2019 $result = $this->cfg_db->db_exec("
2020 CREATE TABLE images (
2022 img_version_idx int,
2023 img_md5 varchar(32),
2024 UNIQUE(img_idx, img_version_idx)
2029 $this->cfg_db->db_rollback_transaction();
2030 die("Upgrade failed - transaction rollback");
2033 $result = $this->cfg_db->db_exec("
2040 $this->cfg_db->db_rollback_transaction();
2041 die("Upgrade failed - transaction rollback");
2044 $result = $this->cfg_db->db_exec("
2045 DROP TABLE images_temp
2049 $this->cfg_db->db_rollback_transaction();
2050 die("Upgrade failed - transaction rollback");
2053 if(!$this->cfg_db->db_commit_transaction())
2054 die("Can not commit database transaction");
2058 } // check_phpfspot_db
2061 * generates thumbnails
2063 * This function generates JPEG thumbnails from
2064 * provided F-Spot photo indize and its alternative
2067 * 1. Check if all thumbnail generations (width) are already in place and
2069 * 2. Check if the md5sum of the original file has changed
2070 * 3. Generate the thumbnails if needed
2071 * @param integer $idx
2072 * @param integer $force
2073 * @param boolean $overwrite
2075 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2078 $versions = Array(0);
2080 $resolutions = Array(
2081 $this->cfg->thumb_width,
2082 $this->cfg->photo_width,
2083 $this->cfg->mini_width,
2087 if($alt_versions = $this->get_photo_versions($idx))
2088 $versions = array_merge($versions, $alt_versions);
2090 foreach($versions as $version) {
2092 /* get details from F-Spot's database */
2093 $details = $this->get_photo_details($idx, $version);
2095 /* calculate file MD5 sum */
2096 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2098 if(!file_exists($full_path)) {
2099 $this->_error("File ". $full_path ." does not exist");
2103 if(!is_readable($full_path)) {
2104 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2108 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2110 /* If Nikon NEF format, we need to treat it another way */
2111 if(isset($this->cfg->dcraw_bin) &&
2112 file_exists($this->cfg->dcraw_bin) &&
2113 is_executable($this->cfg->dcraw_bin) &&
2114 preg_match('/\.nef$/i', $details['uri'])) {
2116 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2118 /* if PPM file does not exist, let dcraw convert it from NEF */
2119 if(!file_exists($ppm_path)) {
2120 system($this->cfg->dcraw_bin ." -a ". $full_path);
2123 /* for now we handle the PPM instead of the NEF */
2124 $full_path = $ppm_path;
2128 $file_md5 = md5_file($full_path);
2131 foreach($resolutions as $resolution) {
2133 $generate_it = false;
2135 $thumb_sub_path = substr($file_md5, 0, 2);
2136 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2138 /* if thumbnail-subdirectory does not exist yet, create it */
2139 if(!file_exists(dirname($thumb_path))) {
2140 mkdir(dirname($thumb_path), 0755);
2143 /* if the thumbnail file doesn't exist, create it */
2144 if(!file_exists($thumb_path) || $force) {
2145 $generate_it = true;
2147 elseif($file_md5 != $this->getMD5($idx, $version)) {
2148 $generate_it = true;
2151 if($generate_it || $overwrite) {
2153 $this->_debug(" ". $resolution ."px");
2154 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2162 $this->_debug(" already exist");
2165 /* set the new/changed MD5 sum for the current photo */
2167 $this->setMD5($idx, $file_md5, $version);
2170 $this->_debug("\n");
2177 * returns stored md5 sum for a specific photo
2179 * this function queries the phpfspot database for a stored MD5
2180 * checksum of the specified photo. It also takes care of the
2181 * requested photo version - original or alternative photo.
2183 * @param integer $idx
2184 * @return string|null
2186 public function getMD5($idx, $version_idx = 0)
2188 $result = $this->cfg_db->db_query("
2194 img_idx='". $idx ."'
2196 img_version_idx='". $version_idx ."'
2202 if($img = $this->cfg_db->db_fetch_object($result))
2203 return $img['img_md5'];
2210 * set MD5 sum for the specific photo
2211 * @param integer $idx
2212 * @param string $md5
2214 private function setMD5($idx, $md5, $version_idx = 0)
2216 $result = $this->cfg_db->db_exec("
2217 INSERT OR REPLACE INTO images (
2218 img_idx, img_version_idx, img_md5
2221 '". $version_idx ."',
2229 * store current tag condition
2231 * this function stores the current tag condition
2232 * (AND or OR) in the users session variables
2233 * @param string $mode
2236 public function setTagCondition($mode)
2238 $_SESSION['tag_condition'] = $mode;
2242 } // setTagCondition()
2245 * invoke tag & date search
2247 * this function will return all matching tags and store
2248 * them in the session variable selected_tags. furthermore
2249 * it also handles the date search.
2250 * getPhotoSelection() will then only return the matching
2254 public function startSearch()
2257 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2258 $date_from = $_POST['date_from'];
2260 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2261 $date_to = $_POST['date_to'];
2264 /* tag-name search */
2265 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2266 $searchfor_tag = $_POST['for_tag'];
2267 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2270 unset($_SESSION['searchfor_tag']);
2273 /* file-name search */
2274 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2275 $_SESSION['searchfor_name'] = $_POST['for_name'];
2278 unset($_SESSION['searchfor_name']);
2282 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2284 $_SESSION['rate_from'] = $_POST['rate_from'];
2286 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2287 $_SESSION['rate_to'] = $_POST['rate_to'];
2291 /* delete any previously set value */
2292 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2297 if(isset($date_from) && !empty($date_from))
2298 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2300 unset($_SESSION['from_date']);
2302 if(isset($date_to) && !empty($date_to))
2303 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2305 unset($_SESSION['to_date']);
2307 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2308 /* new search, reset the current selected tags */
2309 $_SESSION['selected_tags'] = Array();
2310 foreach($this->avail_tags as $tag) {
2311 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2312 array_push($_SESSION['selected_tags'], $tag);
2321 * updates sort order in session variable
2323 * this function is invoked by RPC and will sort the requested
2324 * sort order in the session variable.
2325 * @param string $sort_order
2328 public function updateSortOrder($order)
2330 if(isset($this->sort_orders[$order])) {
2331 $_SESSION['sort_order'] = $order;
2335 return "unkown error";
2337 } // updateSortOrder()
2340 * update photo version in session variable
2342 * this function is invoked by RPC and will set the requested
2343 * photo version in the session variable.
2344 * @param string $photo_version
2347 public function update_photo_version($photo_idx, $photo_version)
2349 if($this->is_valid_version($photo_idx, $photo_version)) {
2350 $_SESSION['current_version'] = $photo_version;
2354 return "incorrect photo version provided";
2356 } // update_photo_version()
2361 * this function rotates the image according the
2363 * @param string $img
2364 * @param integer $degress
2367 private function rotateImage($img, $degrees)
2369 if(function_exists("imagerotate")) {
2370 $img = imagerotate($img, $degrees, 0);
2372 function imagerotate($src_img, $angle)
2374 $src_x = imagesx($src_img);
2375 $src_y = imagesy($src_img);
2376 if ($angle == 180) {
2380 elseif ($src_x <= $src_y) {
2384 elseif ($src_x >= $src_y) {
2389 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2390 imagealphablending($rotate, false);
2395 for ($y = 0; $y < ($src_y); $y++) {
2396 for ($x = 0; $x < ($src_x); $x++) {
2397 $color = imagecolorat($src_img, $x, $y);
2398 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2404 for ($y = 0; $y < ($src_y); $y++) {
2405 for ($x = 0; $x < ($src_x); $x++) {
2406 $color = imagecolorat($src_img, $x, $y);
2407 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2413 for ($y = 0; $y < ($src_y); $y++) {
2414 for ($x = 0; $x < ($src_x); $x++) {
2415 $color = imagecolorat($src_img, $x, $y);
2416 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2430 $img = imagerotate($img, $degrees);
2439 * returns flipped image
2441 * this function will return an either horizontal or
2442 * vertical flipped truecolor image.
2443 * @param string $image
2444 * @param string $mode
2447 private function flipImage($image, $mode)
2449 $w = imagesx($image);
2450 $h = imagesy($image);
2451 $flipped = imagecreatetruecolor($w, $h);
2455 for ($y = 0; $y < $h; $y++) {
2456 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2460 for ($x = 0; $x < $w; $x++) {
2461 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2471 * return all assigned tags for the specified photo
2472 * @param integer $idx
2475 private function get_photo_tags($idx)
2477 $result = $this->db->db_query("
2478 SELECT t.id as id, t.name as name
2480 INNER JOIN photo_tags pt
2482 WHERE pt.photo_id='". $idx ."'
2487 while($row = $this->db->db_fetch_object($result)) {
2488 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2490 $tags[$row['id']] = $row['name'];
2495 } // get_photo_tags()
2498 * create on-the-fly images with text within
2499 * @param string $txt
2500 * @param string $color
2501 * @param integer $space
2502 * @param integer $font
2505 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2507 if (strlen($color) != 6)
2510 $int = hexdec($color);
2511 $h = imagefontheight($font);
2512 $fw = imagefontwidth($font);
2513 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2514 $lines = count($txt);
2515 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2516 $bg = imagecolorallocate($im, 255, 255, 255);
2517 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2520 foreach ($txt as $text) {
2521 $x = (($w - ($fw * strlen($text))) / 2);
2522 imagestring($im, $font, $x, $y, $text, $color);
2523 $y += ($h + $space);
2526 Header("Content-type: image/png");
2529 } // showTextImage()
2532 * check if all requirements are met
2535 private function check_requirements()
2537 if(!function_exists("imagecreatefromjpeg")) {
2538 print "PHP GD library extension is missing<br />\n";
2542 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2543 print "PHP SQLite3 library extension is missing<br />\n";
2547 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2548 ini_set('track_errors', 1);
2549 @include_once 'HTML/AJAX/Server.php';
2550 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2551 print "PEAR HTML_AJAX package is missing<br />\n";
2554 @include_once 'Calendar/Calendar.php';
2555 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2556 print "PEAR Calendar package is missing<br />\n";
2559 @include_once 'Console/Getopt.php';
2560 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2561 print "PEAR Console_Getopt package is missing<br />\n";
2564 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2565 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2566 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2569 ini_restore('track_errors');
2576 } // check_requirements()
2578 private function _debug($text)
2580 if(isset($this->fromcmd)) {
2587 * check if specified MIME type is supported
2588 * @param string $mime
2591 public function checkifImageSupported($mime)
2593 $supported_types = Array(
2596 "image/x-portable-pixmap",
2600 if(in_array($mime, $supported_types))
2605 } // checkifImageSupported()
2609 * @param string $text
2611 public function _error($text)
2613 switch($this->cfg->logging) {
2616 if(isset($this->fromcmd))
2619 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2620 print $text ."<br />\n";
2627 error_log($text, 3, $this->cfg->log_file);
2631 $this->runtime_error = true;
2636 * get calendar input-text fields
2638 * this function returns a text-field used for the data selection.
2639 * Either it will be filled with the current date or, if available,
2640 * filled with the date user entered previously.
2642 * @param string $mode
2645 private function get_date_text_field($mode)
2647 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2649 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2651 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2653 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2654 if(!isset($_SESSION[$mode .'_date']))
2655 $output.= " disabled=\"disabled\"";
2660 } // get_date_text_field()
2663 * output calendar matrix
2664 * @param integer $year
2665 * @param integer $month
2666 * @param integer $day
2668 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2670 if (!isset($year)) $year = date('Y');
2671 if (!isset($month)) $month = date('m');
2672 if (!isset($day)) $day = date('d');
2677 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2678 require_once CALENDAR_ROOT.'Day.php';
2681 $month = new Calendar_Month_Weekdays($year,$month);
2684 $prevStamp = $month->prevMonth(true);
2685 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2686 $nextStamp = $month->nextMonth(true);
2687 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2689 $selectedDays = array (
2690 new Calendar_Day($year,$month,$day),
2691 new Calendar_Day($year,12,25),
2694 // Build the days in the month
2695 $month->build($selectedDays);
2697 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2698 $this->tmpl->assign('prev_month', $prev);
2699 $this->tmpl->assign('next_month', $next);
2701 while ( $day = $month->fetch() ) {
2703 if(!isset($matrix[$rows]))
2704 $matrix[$rows] = Array();
2708 $dayStamp = $day->thisDay(true);
2709 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2711 // isFirst() to find start of week
2712 if ( $day->isFirst() )
2715 if ( $day->isSelected() ) {
2716 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2717 } else if ( $day->isEmpty() ) {
2718 $string.= "<td> </td>\n";
2720 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2723 // isLast() to find end of week
2724 if ( $day->isLast() )
2725 $string.= "</tr>\n";
2727 $matrix[$rows][$cols] = $string;
2737 $this->tmpl->assign('matrix', $matrix);
2738 $this->tmpl->assign('rows', $rows);
2739 $this->tmpl->show("calendar.tpl");
2741 } // get_calendar_matrix()
2744 * output export page
2745 * @param string $mode
2747 public function getExport($mode)
2749 $pictures = $this->getPhotoSelection();
2750 $current_tags = $this->getCurrentTags();
2752 foreach($pictures as $picture) {
2754 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2755 if($current_tags != "") {
2756 $orig_url.= "&tags=". $current_tags;
2758 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2759 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2762 if($this->is_user_friendly_url()) {
2763 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2766 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2772 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2773 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2777 // "[%pictureurl% %thumbnailurl%]"
2778 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2781 case 'MoinMoinList':
2782 // " * [%pictureurl% %thumbnailurl%]"
2783 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2794 public function getRSSFeed()
2796 Header("Content-type: text/xml; charset=utf-8");
2797 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2800 xmlns:media="http://search.yahoo.com/mrss/"
2801 xmlns:dc="http://purl.org/dc/elements/1.1/"
2804 <title>phpfspot</title>
2805 <description>phpfspot RSS feed</description>
2806 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2807 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2808 <generator>phpfspot</generator>
2811 $pictures = $this->getPhotoSelection();
2812 $current_tags = $this->getCurrentTags();
2814 foreach($pictures as $picture) {
2816 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2817 if($current_tags != "") {
2818 $orig_url.= "&tags=". $current_tags;
2820 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2821 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2824 $details = $this->get_photo_details($picture);
2826 if($this->is_user_friendly_url()) {
2827 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2830 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2833 $thumb_html = htmlspecialchars("
2834 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2836 ". $details['description']);
2838 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2840 /* get EXIF information if JPEG */
2841 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2842 $meta = $this->get_meta_informations($orig_path);
2847 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2848 <link><?php print htmlspecialchars($orig_url); ?></link>
2849 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2850 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2852 <?php print $thumb_html; ?>
2854 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2869 * get all selected tags
2871 * This function will return all selected tags as one string, seperated
2875 private function getCurrentTags()
2878 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2879 foreach($_SESSION['selected_tags'] as $tag)
2880 $current_tags.= $tag .",";
2881 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2883 return $current_tags;
2885 } // getCurrentTags()
2888 * return the current photo
2890 public function get_current_photo()
2892 if(isset($_SESSION['current_photo'])) {
2893 return $_SESSION['current_photo'];
2898 } // get_current_photo()
2901 * current selected photo version
2903 * this function returns the current selected photo version
2904 * from the session variables.
2908 public function get_current_version()
2910 /* if current version is set, return it, if the photo really has that version */
2911 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2912 return $_SESSION['current_version'];
2916 } // get_current_version()
2919 * returns latest available photo version
2921 * this function returns the latested available version
2922 * for the requested photo.
2926 public function get_latest_version($photo_idx)
2928 /* try to get the lasted version for the current photo */
2929 if($versions = $this->get_photo_versions($photo_idx))
2930 return $versions[count($versions)-1];
2932 /* if no alternative version were found, return original version */
2935 } // get_current_version()
2938 * tells the client browser what to do
2940 * this function is getting called via AJAX by the
2941 * client browsers. it will tell them what they have
2942 * to do next. This is necessary for directly jumping
2943 * into photo index or single photo view when the are
2944 * requested with specific URLs
2947 public function whatToDo()
2949 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2951 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2952 return "showpi_tags";
2954 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2961 * return the current process-user
2964 private function getuid()
2966 if($uid = posix_getuid()) {
2967 if($user = posix_getpwuid($uid)) {
2968 return $user['name'];
2977 * photo version select list
2979 * this function returns a HTML select list (drop down)
2980 * to select a alternative photo version of the original photo.
2982 * @param array $params
2983 * @param smarty $smarty
2986 public function smarty_photo_version_select_list($params, &$smarty)
2988 if(!isset($params['photo']) || !isset($params['current']))
2991 $output = "<option value=\"0\">Original</option>";
2992 $versions = $this->get_photo_versions($params['photo']);
2994 foreach($versions as $version) {
2996 $output.= "<option value=\"". $version ."\"";
2997 if($version == $params['current']) {
2998 $output.= " selected=\"selected\"";
3000 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3005 } // smarty_photo_version_select_list()
3008 * returns a select-dropdown box to select photo index sort parameters
3009 * @param array $params
3010 * @param smarty $smarty
3013 public function smarty_sort_select_list($params, &$smarty)
3017 foreach($this->sort_orders as $key => $value) {
3018 $output.= "<option value=\"". $key ."\"";
3019 if($key == $_SESSION['sort_order']) {
3020 $output.= " selected=\"selected\"";
3022 $output.= ">". $value ."</option>";
3027 } // smarty_sort_select_list()
3030 * returns the currently selected sort order
3033 private function get_sort_order()
3035 switch($_SESSION['sort_order']) {
3037 return " ORDER BY p.time ASC";
3040 return " ORDER BY p.time DESC";
3043 if($this->dbver < 9) {
3044 return " ORDER BY p.name ASC";
3047 return " ORDER BY basename(p.uri) ASC";
3051 if($this->dbver < 9) {
3052 return " ORDER BY p.name DESC";
3055 return " ORDER BY basename(p.uri) DESC";
3059 return " ORDER BY t.name ASC ,p.time ASC";
3062 return " ORDER BY t.name DESC ,p.time ASC";
3065 return " ORDER BY p.rating ASC, t.name ASC";
3068 return " ORDER BY p.rating DESC, t.name ASC";
3072 } // get_sort_order()
3075 * return the next to be shown slide show image
3077 * this function returns the URL of the next image
3078 * in the slideshow sequence.
3081 public function getNextSlideShowImage()
3083 $all_photos = $this->getPhotoSelection();
3085 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3086 $_SESSION['slideshow_img'] = 0;
3088 $_SESSION['slideshow_img']++;
3090 if($this->is_user_friendly_url()) {
3091 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3094 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3096 } // getNextSlideShowImage()
3099 * return the previous to be shown slide show image
3101 * this function returns the URL of the previous image
3102 * in the slideshow sequence.
3105 public function getPrevSlideShowImage()
3107 $all_photos = $this->getPhotoSelection();
3109 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3110 $_SESSION['slideshow_img'] = 0;
3112 $_SESSION['slideshow_img']--;
3114 if($this->is_user_friendly_url()) {
3115 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3118 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3120 } // getPrevSlideShowImage()
3122 public function resetSlideShow()
3124 if(isset($_SESSION['slideshow_img']))
3125 unset($_SESSION['slideshow_img']);
3127 } // resetSlideShow()
3132 * this function will get all photos from the fspot
3133 * database and randomly return ONE entry
3135 * saddly there is yet no sqlite3 function which returns
3136 * the bulk result in array, so we have to fill up our
3140 public function get_random_photo()
3149 /* if show_tags is set, only return details for photos which
3150 are specified to be shown
3152 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3154 INNER JOIN photo_tags pt
3159 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3162 $result = $this->db->db_query($query_str);
3164 while($row = $this->db->db_fetch_object($result)) {
3165 array_push($all, $row['id']);
3168 return $all[array_rand($all)];
3170 } // get_random_photo()
3173 * get random photo tag photo
3175 * this function will get all photos tagged with the requested
3176 * tag from the fspot database and randomly return ONE entry
3178 * saddly there is yet no sqlite3 function which returns
3179 * the bulk result in array, so we have to fill up our
3183 public function get_random_tag_photo($tagidx)
3187 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3190 DISTINCT pt1.photo_id as id
3193 INNER JOIN photo_tags
3194 pt2 ON pt1.photo_id=pt2.photo_id
3200 pt1.tag_id LIKE '". $tagidx ."'
3202 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3204 t1.sort_priority ASC";
3212 INNER JOIN photo_tags pt
3215 pt.tag_id LIKE '". $tagidx ."'";
3218 $result = $this->db->db_query($query_str);
3220 while($row = $this->db->db_fetch_object($result)) {
3221 array_push($all, $row['id']);
3224 return $all[array_rand($all)];
3226 } // get_random_tag_photo()
3229 * validates provided date
3231 * this function validates if the provided date
3232 * contains a valid date and will return true
3234 * @param string $date_str
3237 public function isValidDate($date_str)
3239 $timestamp = strtotime($date_str);
3241 if(is_numeric($timestamp))
3249 * timestamp to string conversion
3250 * @param integer $timestamp
3253 private function ts2str($timestamp)
3255 if(!empty($timestamp) && is_numeric($timestamp))
3256 return strftime("%Y-%m-%d", $timestamp);
3261 * extract tag-names from $_GET['tags']
3262 * @param string $tags_str
3265 private function extractTags($tags_str)
3267 $not_validated = split(',', $tags_str);
3268 $validated = array();
3270 foreach($not_validated as $tag) {
3271 if(is_numeric($tag))
3272 array_push($validated, $tag);
3280 * returns the full path to a thumbnail
3281 * @param integer $width
3282 * @param integer $photo
3285 public function get_thumb_path($width, $photo_idx, $version_idx)
3287 $md5 = $this->getMD5($photo_idx, $version_idx);
3288 $sub_path = substr($md5, 0, 2);
3289 return $this->cfg->thumb_path
3297 } // get_thumb_path()
3300 * returns server's virtual host name
3303 private function get_server_name()
3305 return $_SERVER['SERVER_NAME'];
3306 } // get_server_name()
3309 * returns type of webprotocol which is currently used
3312 private function get_web_protocol()
3314 if(!isset($_SERVER['HTTPS']))
3318 } // get_web_protocol()
3321 * return url to this phpfspot installation
3324 private function get_phpfspot_url()
3326 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3328 } // get_phpfspot_url()
3331 * returns the number of photos which are tagged with $tag_id
3332 * @param integer $tag_id
3335 public function get_num_photos($tag_id)
3337 if($result = $this->db->db_fetchSingleRow("
3338 SELECT count(*) as number
3341 tag_id LIKE '". $tag_id ."'")) {
3343 return $result['number'];
3349 } // get_num_photos()
3352 * check file exists and is readable
3354 * returns true, if everything is ok, otherwise false
3355 * if $silent is not set, this function will output and
3357 * @param string $file
3358 * @param boolean $silent
3361 private function check_readable($file, $silent = null)
3363 if(!file_exists($file)) {
3365 print "File \"". $file ."\" does not exist.\n";
3369 if(!is_readable($file)) {
3371 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3377 } // check_readable()
3380 * check if all needed indices are present
3382 * this function checks, if some needed indices are already
3383 * present, or if not, create them on the fly. they are
3384 * necessary to speed up some queries like that one look for
3385 * all tags, when show_tags is specified in the configuration.
3387 private function checkDbIndices()
3389 $result = $this->db->db_exec("
3390 CREATE INDEX IF NOT EXISTS
3397 } // checkDbIndices()
3400 * retrive F-Spot database version
3402 * this function will return the F-Spot database version number
3403 * It is stored within the sqlite3 database in the table meta
3404 * @return string|null
3406 public function getFspotDBVersion()
3408 if($result = $this->db->db_fetchSingleRow("
3409 SELECT data as version
3412 name LIKE 'F-Spot Database Version'
3414 return $result['version'];
3418 } // getFspotDBVersion()
3421 * parse the provided URI and will returned the requested chunk
3422 * @param string $uri
3423 * @param string $mode
3426 public function parse_uri($uri, $mode)
3428 if(($components = parse_url($uri)) !== false) {
3432 return basename($components['path']);
3435 return dirname($components['path']);
3438 return $components['path'];
3448 * validate config options
3450 * this function checks if all necessary configuration options are
3451 * specified and set.