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 'Date.php';
2565 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2566 print "PEAR Date package is missing<br />\n";
2569 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2570 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2571 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2574 ini_restore('track_errors');
2581 } // check_requirements()
2583 private function _debug($text)
2585 if(isset($this->fromcmd)) {
2592 * check if specified MIME type is supported
2593 * @param string $mime
2596 public function checkifImageSupported($mime)
2598 $supported_types = Array(
2601 "image/x-portable-pixmap",
2605 if(in_array($mime, $supported_types))
2610 } // checkifImageSupported()
2614 * @param string $text
2616 public function _error($text)
2618 switch($this->cfg->logging) {
2621 if(isset($this->fromcmd))
2624 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2625 print $text ."<br />\n";
2632 error_log($text, 3, $this->cfg->log_file);
2636 $this->runtime_error = true;
2641 * get calendar input-text fields
2643 * this function returns a text-field used for the data selection.
2644 * Either it will be filled with the current date or, if available,
2645 * filled with the date user entered previously.
2647 * @param string $mode
2650 private function get_date_text_field($mode)
2652 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2654 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2656 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2658 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2659 if(!isset($_SESSION[$mode .'_date']))
2660 $output.= " disabled=\"disabled\"";
2665 } // get_date_text_field()
2668 * output calendar matrix
2669 * @param integer $year
2670 * @param integer $month
2671 * @param integer $day
2673 public function get_calendar_matrix($userdate)
2675 if(($userdate = strtotime($userdate)) === false) {
2682 $date->setDate($userdate);
2684 $year = $date->getYear();
2685 $month = $date->getMonth();
2686 $day = $date->getDay();
2693 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2694 require_once CALENDAR_ROOT.'Day.php';
2697 $month_cal = new Calendar_Month_Weekdays($year,$month);
2700 $prevStamp = $month_cal->prevMonth(true);
2701 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2702 $nextStamp = $month_cal->nextMonth(true);
2703 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2705 $selectedDays = array (
2706 new Calendar_Day($year,$month,$day),
2709 // Build the days in the month
2710 $month_cal->build($selectedDays);
2712 $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp()));
2713 $this->tmpl->assign('prev_month', $prev);
2714 $this->tmpl->assign('next_month', $next);
2716 while ( $day = $month_cal->fetch() ) {
2718 if(!isset($matrix[$rows]))
2719 $matrix[$rows] = Array();
2723 $dayStamp = $day->thisDay(true);
2724 $link = "javascript:setCalendarDate('"
2725 . date('Y',$dayStamp)
2727 . date('m',$dayStamp)
2729 . date('d',$dayStamp)
2732 // isFirst() to find start of week
2733 if ( $day->isFirst() )
2736 if ( $day->isSelected() ) {
2737 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2738 } else if ( $day->isEmpty() ) {
2739 $string.= "<td> </td>\n";
2741 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2744 // isLast() to find end of week
2745 if ( $day->isLast() )
2746 $string.= "</tr>\n";
2748 $matrix[$rows][$cols] = $string;
2758 $this->tmpl->assign('matrix', $matrix);
2759 $this->tmpl->assign('rows', $rows);
2760 $this->tmpl->show("calendar.tpl");
2762 } // get_calendar_matrix()
2765 * output export page
2766 * @param string $mode
2768 public function getExport($mode)
2770 $pictures = $this->getPhotoSelection();
2771 $current_tags = $this->getCurrentTags();
2773 foreach($pictures as $picture) {
2775 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2776 if($current_tags != "") {
2777 $orig_url.= "&tags=". $current_tags;
2779 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2780 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2783 if($this->is_user_friendly_url()) {
2784 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2787 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2793 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2794 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2798 // "[%pictureurl% %thumbnailurl%]"
2799 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2802 case 'MoinMoinList':
2803 // " * [%pictureurl% %thumbnailurl%]"
2804 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2815 public function getRSSFeed()
2817 Header("Content-type: text/xml; charset=utf-8");
2818 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2821 xmlns:media="http://search.yahoo.com/mrss/"
2822 xmlns:dc="http://purl.org/dc/elements/1.1/"
2825 <title>phpfspot</title>
2826 <description>phpfspot RSS feed</description>
2827 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2828 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2829 <generator>phpfspot</generator>
2832 $pictures = $this->getPhotoSelection();
2833 $current_tags = $this->getCurrentTags();
2835 foreach($pictures as $picture) {
2837 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2838 if($current_tags != "") {
2839 $orig_url.= "&tags=". $current_tags;
2841 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2842 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2845 $details = $this->get_photo_details($picture);
2847 if($this->is_user_friendly_url()) {
2848 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2851 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2854 $thumb_html = htmlspecialchars("
2855 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2857 ". $details['description']);
2859 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2861 /* get EXIF information if JPEG */
2862 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2863 $meta = $this->get_meta_informations($orig_path);
2868 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2869 <link><?php print htmlspecialchars($orig_url); ?></link>
2870 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2871 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2873 <?php print $thumb_html; ?>
2875 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2890 * get all selected tags
2892 * This function will return all selected tags as one string, seperated
2896 private function getCurrentTags()
2899 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2900 foreach($_SESSION['selected_tags'] as $tag)
2901 $current_tags.= $tag .",";
2902 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2904 return $current_tags;
2906 } // getCurrentTags()
2909 * return the current photo
2911 public function get_current_photo()
2913 if(isset($_SESSION['current_photo'])) {
2914 return $_SESSION['current_photo'];
2919 } // get_current_photo()
2922 * current selected photo version
2924 * this function returns the current selected photo version
2925 * from the session variables.
2929 public function get_current_version()
2931 /* if current version is set, return it, if the photo really has that version */
2932 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2933 return $_SESSION['current_version'];
2937 } // get_current_version()
2940 * returns latest available photo version
2942 * this function returns the latested available version
2943 * for the requested photo.
2947 public function get_latest_version($photo_idx)
2949 /* try to get the lasted version for the current photo */
2950 if($versions = $this->get_photo_versions($photo_idx))
2951 return $versions[count($versions)-1];
2953 /* if no alternative version were found, return original version */
2956 } // get_current_version()
2959 * tells the client browser what to do
2961 * this function is getting called via AJAX by the
2962 * client browsers. it will tell them what they have
2963 * to do next. This is necessary for directly jumping
2964 * into photo index or single photo view when the are
2965 * requested with specific URLs
2968 public function whatToDo()
2970 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2972 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2973 return "showpi_tags";
2975 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2982 * return the current process-user
2985 private function getuid()
2987 if($uid = posix_getuid()) {
2988 if($user = posix_getpwuid($uid)) {
2989 return $user['name'];
2998 * photo version select list
3000 * this function returns a HTML select list (drop down)
3001 * to select a alternative photo version of the original photo.
3003 * @param array $params
3004 * @param smarty $smarty
3007 public function smarty_photo_version_select_list($params, &$smarty)
3009 if(!isset($params['photo']) || !isset($params['current']))
3012 $output = "<option value=\"0\">Original</option>";
3013 $versions = $this->get_photo_versions($params['photo']);
3015 foreach($versions as $version) {
3017 $output.= "<option value=\"". $version ."\"";
3018 if($version == $params['current']) {
3019 $output.= " selected=\"selected\"";
3021 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3026 } // smarty_photo_version_select_list()
3029 * returns a select-dropdown box to select photo index sort parameters
3030 * @param array $params
3031 * @param smarty $smarty
3034 public function smarty_sort_select_list($params, &$smarty)
3038 foreach($this->sort_orders as $key => $value) {
3039 $output.= "<option value=\"". $key ."\"";
3040 if($key == $_SESSION['sort_order']) {
3041 $output.= " selected=\"selected\"";
3043 $output.= ">". $value ."</option>";
3048 } // smarty_sort_select_list()
3051 * returns the currently selected sort order
3054 private function get_sort_order()
3056 switch($_SESSION['sort_order']) {
3058 return " ORDER BY p.time ASC";
3061 return " ORDER BY p.time DESC";
3064 if($this->dbver < 9) {
3065 return " ORDER BY p.name ASC";
3068 return " ORDER BY basename(p.uri) ASC";
3072 if($this->dbver < 9) {
3073 return " ORDER BY p.name DESC";
3076 return " ORDER BY basename(p.uri) DESC";
3080 return " ORDER BY t.name ASC ,p.time ASC";
3083 return " ORDER BY t.name DESC ,p.time ASC";
3086 return " ORDER BY p.rating ASC, t.name ASC";
3089 return " ORDER BY p.rating DESC, t.name ASC";
3093 } // get_sort_order()
3096 * return the next to be shown slide show image
3098 * this function returns the URL of the next image
3099 * in the slideshow sequence.
3102 public function getNextSlideShowImage()
3104 $all_photos = $this->getPhotoSelection();
3106 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3107 $_SESSION['slideshow_img'] = 0;
3109 $_SESSION['slideshow_img']++;
3111 if($this->is_user_friendly_url()) {
3112 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3115 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3117 } // getNextSlideShowImage()
3120 * return the previous to be shown slide show image
3122 * this function returns the URL of the previous image
3123 * in the slideshow sequence.
3126 public function getPrevSlideShowImage()
3128 $all_photos = $this->getPhotoSelection();
3130 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3131 $_SESSION['slideshow_img'] = 0;
3133 $_SESSION['slideshow_img']--;
3135 if($this->is_user_friendly_url()) {
3136 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3139 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3141 } // getPrevSlideShowImage()
3143 public function resetSlideShow()
3145 if(isset($_SESSION['slideshow_img']))
3146 unset($_SESSION['slideshow_img']);
3148 } // resetSlideShow()
3153 * this function will get all photos from the fspot
3154 * database and randomly return ONE entry
3156 * saddly there is yet no sqlite3 function which returns
3157 * the bulk result in array, so we have to fill up our
3161 public function get_random_photo()
3170 /* if show_tags is set, only return details for photos which
3171 are specified to be shown
3173 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3175 INNER JOIN photo_tags pt
3180 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3183 $result = $this->db->db_query($query_str);
3185 while($row = $this->db->db_fetch_object($result)) {
3186 array_push($all, $row['id']);
3189 return $all[array_rand($all)];
3191 } // get_random_photo()
3194 * get random photo tag photo
3196 * this function will get all photos tagged with the requested
3197 * tag from the fspot database and randomly return ONE entry
3199 * saddly there is yet no sqlite3 function which returns
3200 * the bulk result in array, so we have to fill up our
3204 public function get_random_tag_photo($tagidx)
3208 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3211 DISTINCT pt1.photo_id as id
3214 INNER JOIN photo_tags
3215 pt2 ON pt1.photo_id=pt2.photo_id
3221 pt1.tag_id LIKE '". $tagidx ."'
3223 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3225 t1.sort_priority ASC";
3233 INNER JOIN photo_tags pt
3236 pt.tag_id LIKE '". $tagidx ."'";
3239 $result = $this->db->db_query($query_str);
3241 while($row = $this->db->db_fetch_object($result)) {
3242 array_push($all, $row['id']);
3245 return $all[array_rand($all)];
3247 } // get_random_tag_photo()
3250 * validates provided date
3252 * this function validates if the provided date
3253 * contains a valid date and will return true
3255 * @param string $date_str
3258 public function isValidDate($date_str)
3260 $timestamp = strtotime($date_str);
3262 if(is_numeric($timestamp))
3270 * timestamp to string conversion
3271 * @param integer $timestamp
3274 private function ts2str($timestamp)
3276 if(!empty($timestamp) && is_numeric($timestamp))
3277 return strftime("%Y-%m-%d", $timestamp);
3282 * extract tag-names from $_GET['tags']
3283 * @param string $tags_str
3286 private function extractTags($tags_str)
3288 $not_validated = split(',', $tags_str);
3289 $validated = array();
3291 foreach($not_validated as $tag) {
3292 if(is_numeric($tag))
3293 array_push($validated, $tag);
3301 * returns the full path to a thumbnail
3302 * @param integer $width
3303 * @param integer $photo
3306 public function get_thumb_path($width, $photo_idx, $version_idx)
3308 $md5 = $this->getMD5($photo_idx, $version_idx);
3309 $sub_path = substr($md5, 0, 2);
3310 return $this->cfg->thumb_path
3318 } // get_thumb_path()
3321 * returns server's virtual host name
3324 private function get_server_name()
3326 return $_SERVER['SERVER_NAME'];
3327 } // get_server_name()
3330 * returns type of webprotocol which is currently used
3333 private function get_web_protocol()
3335 if(!isset($_SERVER['HTTPS']))
3339 } // get_web_protocol()
3342 * return url to this phpfspot installation
3345 private function get_phpfspot_url()
3347 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3349 } // get_phpfspot_url()
3352 * returns the number of photos which are tagged with $tag_id
3353 * @param integer $tag_id
3356 public function get_num_photos($tag_id)
3358 if($result = $this->db->db_fetchSingleRow("
3359 SELECT count(*) as number
3362 tag_id LIKE '". $tag_id ."'")) {
3364 return $result['number'];
3370 } // get_num_photos()
3373 * check file exists and is readable
3375 * returns true, if everything is ok, otherwise false
3376 * if $silent is not set, this function will output and
3378 * @param string $file
3379 * @param boolean $silent
3382 private function check_readable($file, $silent = null)
3384 if(!file_exists($file)) {
3386 print "File \"". $file ."\" does not exist.\n";
3390 if(!is_readable($file)) {
3392 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3398 } // check_readable()
3401 * check if all needed indices are present
3403 * this function checks, if some needed indices are already
3404 * present, or if not, create them on the fly. they are
3405 * necessary to speed up some queries like that one look for
3406 * all tags, when show_tags is specified in the configuration.
3408 private function checkDbIndices()
3410 $result = $this->db->db_exec("
3411 CREATE INDEX IF NOT EXISTS
3418 } // checkDbIndices()
3421 * retrive F-Spot database version
3423 * this function will return the F-Spot database version number
3424 * It is stored within the sqlite3 database in the table meta
3425 * @return string|null
3427 public function getFspotDBVersion()
3429 if($result = $this->db->db_fetchSingleRow("
3430 SELECT data as version
3433 name LIKE 'F-Spot Database Version'
3435 return $result['version'];
3439 } // getFspotDBVersion()
3442 * parse the provided URI and will returned the requested chunk
3443 * @param string $uri
3444 * @param string $mode
3447 public function parse_uri($uri, $mode)
3449 if(($components = parse_url($uri)) !== false) {
3453 return basename($components['path']);
3456 return dirname($components['path']);
3459 return $components['path'];
3469 * validate config options
3471 * this function checks if all necessary configuration options are
3472 * specified and set.
3475 private function check_config_options()
3477 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3478 $this->_error("Please set \$page_title in phpfspot_cfg");
3480 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3481 $this->_error("Please set \$base_path in phpfspot_cfg");
3483 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3484 $this->_error("Please set \$web_path in phpfspot_cfg");
3486 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3487 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3489 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3490 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3492 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3493 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3495 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3496 $this->_error("Please set \$db_access in phpfspot_cfg");
3498 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3499 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3501 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3502 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3504 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3505 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3507 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3508 $this->_error("Please set \$photo_width in phpfspot_cfg");
3510 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3511 $this->_error("Please set \$mini_width in phpfspot_cfg");
3513 if(!isset($this->cfg->thumbs_per_page))
3514 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3516 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3517 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3519 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3520 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3522 if(!isset($this->cfg->hide_tags))
3523 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3525 if(!isset($this->cfg->theme_name))
3526 $this->_error("Please set \$theme_name in phpfspot_cfg");
3528 if(!isset($this->cfg->logging))
3529 $this->_error("Please set \$logging in phpfspot_cfg");
3531 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3533 if(!isset($this->cfg->log_file))
3534 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3536 if(!is_writeable($this->cfg->log_file))
3537 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3541 /* remove trailing slash, if set */
3542 if($this->cfg->web_path == "/")
3543 $this->cfg->web_path = "";
3544 elseif(preg_match('/\/$/', $this->cfg->web_path))
3545 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3547 return $this->runtime_error;
3549 } // check_config_options()
3552 * cleanup phpfspot own database
3554 * When photos are getting delete from F-Spot, there will remain
3555 * remain some residues in phpfspot own database. This function
3556 * will try to wipe them out.
3558 public function cleanup_phpfspot_db()
3560 $to_delete = Array();
3562 $result = $this->cfg_db->db_query("
3563 SELECT img_idx as img_idx
3565 ORDER BY img_idx ASC
3568 while($row = $this->cfg_db->db_fetch_object($result)) {
3569 if(!$this->db->db_fetchSingleRow("
3572 WHERE id='". $row['img_idx'] ."'")) {
3574 array_push($to_delete, $row['img_idx'], ',');
3578 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3580 $this->cfg_db->db_exec("
3582 WHERE img_idx IN (". implode($to_delete) .")
3585 } // cleanup_phpfspot_db()
3588 * return first image of the page, the $current photo
3591 * this function is used to find out the first photo of the
3592 * current page, in which the $current photo lies. this is
3593 * used to display the correct photo, when calling showPhotoIndex()
3595 * @param integer $current
3596 * @param integer $max
3599 private function getCurrentPage($current, $max)
3601 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3602 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3603 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3609 } // getCurrentPage()
3614 * this function tries to find out the correct mime-type
3615 * for the provided file.
3616 * @param string $file
3619 public function get_mime_info($file)
3621 $details = getimagesize($file);
3623 /* if getimagesize() returns empty, try at least to find out the
3626 if(empty($details) && function_exists('mime_content_type')) {
3628 // mime_content_type is marked as deprecated in the documentation,
3629 // but is it really necessary to force users to install a PECL
3631 $details['mime'] = mime_content_type($file);
3634 return $details['mime'];
3636 } // get_mime_info()
3639 * return tag-name by tag-idx
3641 * this function returns the tag-name for the requested
3642 * tag specified by tag-idx.
3643 * @param integer $idx
3646 public function get_tag_name($idx)
3648 if($result = $this->db->db_fetchSingleRow("
3652 id LIKE '". $idx ."'")) {
3654 return $result['name'];
3663 * parse user friendly url which got rewritten by the websever
3664 * @param string $request_uri
3667 private function parse_user_friendly_url($request_uri)
3669 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3671 $options = explode('/', $request_uri);
3673 switch($options[1]) {
3675 if(is_numeric($options[2])) {
3676 $this->session_cleanup();
3677 //unset($_SESSION['start_action']);
3678 //unset($_SESSION['selected_tags']);
3679 $_GET['mode'] = 'showp';
3680 return $this->showPhoto($options[2]);
3684 if(is_numeric($options[2])) {
3687 if(isset($options[3]) && is_numeric($options[3]))
3688 $width = $options[3];
3689 if(isset($options[4]) && is_numeric($options[4]))
3690 $version = $options[4];
3691 require_once "phpfspot_img.php";
3692 $img = new PHPFSPOT_IMG;
3693 $img->showImg($options[2], $width, $version);
3698 if(is_numeric($options[2])) {
3699 $this->session_cleanup();
3700 $_GET['tags'] = $options[2];
3701 $_SESSION['selected_tags'] = Array($options[2]);
3702 if(isset($options[3]) && is_numeric($options[3]))
3703 $_SESSION['begin_with'] = $options[3];
3704 return $this->showPhotoIndex();
3710 } // parse_user_friendly_url()
3713 * check if user-friendly-urls are enabled
3715 * this function will return true, if the config option
3716 * $user_friendly_url has been set. Otherwise false.
3719 private function is_user_friendly_url()
3721 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3726 } // is_user_friendly_url()
3731 * this function will cleanup user's session information
3733 private function session_cleanup()
3735 unset($_SESSION['begin_with']);
3736 $this->resetDateSearch();
3737 $this->resetPhotoView();
3738 $this->resetTagSearch();
3739 $this->resetNameSearch();
3740 $this->resetDateSearch();
3743 } // session_cleanup()
3746 * get database version
3748 * this function queries the meta table
3749 * and returns the current database version.
3753 public function get_db_version()
3755 if($row = $this->cfg_db->db_fetchSingleRow("
3756 SELECT meta_value as meta_value
3760 meta_key LIKE 'phpfspot Database Version'
3763 return $row['meta_value'];
3769 } // get_db_version()
3772 * get photo versions
3774 * this function returns an array of all available
3775 * alterntaive versions of the provided photo id.
3776 * has alternative photo versions available
3781 public function get_photo_versions($idx)
3783 $versions = Array();
3785 $result = $this->db->db_query("
3791 photo_id LIKE '". $idx ."'");
3793 while($row = $this->cfg_db->db_fetch_object($result)) {
3794 array_push($versions, $row['version_id']);
3799 } // get_photo_versions()
3802 * check for invalid version of photo
3804 * this function validates the provided photo-id and version-id
3806 * @param int $photo_idx
3807 * @param int $version_idx
3810 public function is_valid_version($photo_idx, $version_idx)
3812 /* the original version is always valid */
3813 if($version_idx == 0)
3816 if($versions = $this->get_photo_versions($photo_idx)) {
3817 if(in_array($version_idx, $versions))
3823 } // is_valid_version()
3826 * get photo version name
3828 * this function returns the name of the version
3829 * identified by the photo-id and version-id.
3831 * @param int $photo_idx
3832 * @param int $version_idx
3835 public function get_photo_version_name($photo_idx, $version_idx)
3837 if($row = $this->db->db_fetchSingleRow("
3843 photo_id LIKE '". $photo_idx ."'
3845 version_id LIKE '". $version_idx ."'")) {
3847 return $row['name'];
3853 } // get_photo_version_name()
3857 public function is_valid_width($image_width)
3859 if(in_array($image_width,
3860 Array($this->cfg->thumb_width,
3861 $this->cfg->photo_width,
3862 $this->cfg->mini_width,
3869 } // is_valid_width()