3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
31 * this class contains the most functions which will to the major
39 * phpfspot configuration
47 * SQLite database handle to f-spot database
55 * SQLite database handle to phpfspot database
63 * Smarty template engine
64 * @link http://smarty.php.net smarty.php.net
65 * @see PHPFSPOT_TMPL()
79 * list of available, not-selected, tags
86 * true if runtime error occued
90 private $runtime_error = false;
93 * F-Spot database version
100 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
102 * this function will be called on class construct
103 * and will check requirements, loads configuration,
104 * open databases and start the user session
106 public function __construct()
109 * register PHPFSPOT class global
111 * @global PHPFSPOT $GLOBALS['phpfspot']
114 $GLOBALS['phpfspot'] =& $this;
116 $this->cfg = new PHPFSPOT_CFG;
118 /* verify config settings */
119 if($this->check_config_options()) {
123 /* set application name and version information */
124 $this->cfg->product = "phpfspot";
125 $this->cfg->version = "1.7";
126 $this->cfg->db_version = 2;
128 $this->sort_orders= array(
129 'date_asc' => 'Date ↑',
130 'date_desc' => 'Date ↓',
131 'name_asc' => 'Name ↑',
132 'name_desc' => 'Name ↓',
133 'tags_asc' => 'Tags ↑',
134 'tags_desc' => 'Tags ↓',
137 /* Check necessary requirements */
138 if(!$this->check_requirements()) {
142 /******* Opening F-Spot's sqlite database *********/
144 /* Check if database file exists and is readable */
145 if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n";
150 /* Check if database file is writeable */
151 if(!is_writeable($this->cfg->fspot_db)) {
152 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
153 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
154 ." speed up some database operations.\n";
158 /* open the database */
159 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
161 /* change sqlite temp directory, if requested */
162 if(isset($this->cfg->sqlite_temp_dir)) {
165 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
169 /* get F-Spot database version */
170 $this->dbver = $this->getFspotDBVersion();
172 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
173 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
177 if(!is_writeable($this->cfg->thumb_path)) {
178 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
182 /******* Opening phpfspot's sqlite database *********/
184 /* Check if directory where the database file is stored is writeable */
185 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
186 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
187 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
191 /* Check if database file is writeable */
192 if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) {
193 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
194 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
198 /* open the database */
199 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
201 /* change sqlite temp directory, if requested */
202 if(isset($this->cfg->sqlite_temp_dir)) {
203 $this->cfg_db->db_exec("
205 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
209 /* Check if some tables need to be created */
210 $this->check_phpfspot_db();
212 /* overload Smarty class with our own template handler */
213 require_once "phpfspot_tmpl.php";
214 $this->tmpl = new PHPFSPOT_TMPL();
216 $this->tmpl->assign('web_path', $this->cfg->web_path);
218 /* Starting with F-Spot 0.4.2, the rating-feature was available */
219 if($this->dbver > 10) {
220 $this->tmpl->assign('has_rating', true);
221 $this->sort_orders = array_merge($this->sort_orders, array(
222 'rate_asc' => 'Rate ↑',
223 'rate_desc' => 'Rate ↓',
227 /* check if all necessary indices exist */
228 $this->checkDbIndices();
230 /* if session is not yet started, do it now */
231 if(session_id() == "")
234 if(!isset($_SESSION['tag_condition']))
235 $_SESSION['tag_condition'] = 'or';
237 /* if sort-order has not been set yet, get the one specified in the config */
238 if(!isset($_SESSION['sort_order']))
239 $_SESSION['sort_order'] = $this->cfg->sort_order;
241 if(!isset($_SESSION['searchfor_tag']))
242 $_SESSION['searchfor_tag'] = '';
244 // if begin_with is still set but thumbs_per_page is now 0, unset it
245 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
246 unset($_SESSION['begin_with']);
248 // if user-friendly-url's are enabled, set also a flag for the template handler
249 if($this->is_user_friendly_url()) {
250 $this->tmpl->assign('user_friendly_url', 'true');
255 public function __destruct()
261 * show - generate html output
263 * this function can be called after the constructor has
264 * prepared everyhing. it will load the index.tpl smarty
265 * template. if necessary it will registere pre-selects
266 * (photo index, photo, tag search, date search) into
269 public function show()
271 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
272 $this->tmpl->assign('page_title', $this->cfg->page_title);
273 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
274 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
277 if($this->is_user_friendly_url()) {
278 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
281 if(isset($_GET['mode'])) {
283 $_SESSION['start_action'] = $_GET['mode'];
285 switch($_GET['mode']) {
287 if(isset($_GET['tags'])) {
288 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
290 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
291 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
293 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
294 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
298 if(isset($_GET['tags'])) {
299 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
300 $_SESSION['start_action'] = 'showp';
302 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
303 if($_SESSION['current_photo'] != $_GET['id'])
304 unset($_SESSION['current_version']);
305 $_SESSION['current_photo'] = $_GET['id'];
306 $_SESSION['start_action'] = 'showp';
308 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
309 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
311 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
312 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
316 /* fetch export template */
317 print $this->tmpl->fetch("export.tpl");
318 /* no further execution necessary. */
322 /* fetch slideshow template */
323 print $this->tmpl->show("slideshow.tpl");
324 /* no further execution necessary. */
328 if(isset($_GET['tags'])) {
329 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
331 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
332 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
334 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
335 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
343 /* if date-search variables are registered in the session, set the check
344 for "consider date-range" in the html output
346 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
347 $this->tmpl->assign('date_search_enabled', true);
349 /* if rate-search variables are registered in the session, set the check
350 for "consider rate-range" in the html output
352 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
353 $this->tmpl->assign('rate_search_enabled', true);
356 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
357 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
358 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
360 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
361 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
362 $this->tmpl->assign('rate_search', $this->get_rate_search());
364 /* if no site-content has been set yet... */
365 if(!isset($content)) {
366 /* if tags are already selected, we can immediately display photo-index */
367 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
368 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
369 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
370 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
372 /* if a photo is already selected, we can immediately display single-photo */
373 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
374 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
376 /* ok, then let us show the welcome page... */
377 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
382 $this->tmpl->assign('initial_content', $content);
384 $this->tmpl->show("index.tpl");
389 * get_tags - grab all tags of f-spot's database
391 * this function will get all available tags from
392 * the f-spot database and store them within two
393 * arrays within this class for later usage. in
394 * fact, if the user requests (hide_tags) it will
395 * opt-out some of them.
397 * this function is getting called once by show()
399 private function get_tags()
401 $this->avail_tags = Array();
404 /* if show_tags has been set in the configuration (only show photos
405 which are tagged by these tags) they following will take care,
406 that only these other tags are displayed where the photo is also
407 tagged with one of show_tags.
409 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
412 DISTINCT t1.id as id, t1.name as name
415 INNER JOIN photo_tags
416 pt2 ON pt1.photo_id=pt2.photo_id
422 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
424 t1.sort_priority ASC";
426 $result = $this->db->db_query($query_str);
430 $result = $this->db->db_query("
433 ORDER BY sort_priority ASC
437 while($row = $this->db->db_fetch_object($result)) {
439 $tag_id = $row['id'];
440 $tag_name = $row['name'];
442 /* if the user has specified to ignore this tag in phpfspot's
443 configuration, ignore it here so it does not get added to
446 if(in_array($row['name'], $this->cfg->hide_tags))
449 /* if you include the following if-clause and the user has specified
450 to only show certain tags which are specified in phpfspot's
451 configuration, ignore all others so they will not be added to the
453 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
454 !in_array($row['name'], $this->cfg->show_tags))
458 $this->tags[$tag_id] = $tag_name;
459 $this->avail_tags[$count] = $tag_id;
467 * get all photo details from F-Spot database
469 * this function queries the F-Spot database for all available
470 * details of the requested photo. It returns them as a object.
472 * Furthermore it takes care of the photo version to be requested.
473 * If photo version is not yet, it queries information for the
476 * @param integer $idx
477 * @return object|null
479 public function get_photo_details($idx, $version_idx = NULL)
481 /* ~ F-Spot version 0.3.x */
482 if($this->dbver < 9) {
484 SELECT p.id, p.name, p.time, p.directory_path, p.description
489 /* till F-Spot version 0.4.1 */
490 if($this->dbver < 11) {
492 SELECT p.id, p.uri, p.time, p.description
497 /* rating value got introduced */
499 SELECT p.id, p.uri, p.time, p.description, p.rating
505 /* if show_tags is set, only return details of photos which are
506 tagged with a tag that has been specified to be shown.
508 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
510 INNER JOIN photo_tags pt
514 WHERE p.id='". $idx ."'
515 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
519 WHERE p.id='". $idx ."'
523 if($row = $this->db->db_fetchSingleRow($query_str)) {
525 /* before F-Spot db version 9 there was no uri column but
526 seperated fields for directory_path and name (= filename).
528 if($this->dbver < 9) {
529 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
532 /* if version-idx has not yet been set, get the latest photo version */
533 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
534 $version_idx = $this->get_latest_version($idx);
536 /* if an alternative version has been requested */
537 if($version_idx > 0) {
539 /* check for alternative versions */
540 if($version = $this->db->db_fetchSingleRow("
542 version_id, name, uri
546 photo_id LIKE '". $idx ."'
548 version_id LIKE '". $version_idx ."'
551 $row['name'] = $version['name'];
552 $row['uri'] = $version['uri'];
563 } // get_photo_details()
566 * returns aligned photo names
568 * this function returns aligned (length) names for a specific photo.
569 * If the length of the name exceeds $limit the name will bei
572 * @param integer $idx
573 * @param integer $limit
574 * @return string|null
576 public function getPhotoName($idx, $limit = 0)
578 if($details = $this->get_photo_details($idx)) {
579 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
580 $name = $this->shrink_text($long_name, $limit);
590 * get photo rating level
592 * this function will return the integer-based rating level of a
593 * photo. This can only be done, if the F-Spot database is at a
594 * specific version. If rating value can not be found, zero will
595 * be returned indicating no rating value is available.
600 public function get_photo_rating($idx)
602 if($detail = $this->get_photo_details($idx)) {
603 if(isset($detail['rating']))
604 return $detail['rating'];
609 } // get_photo_rating()
612 * get rate-search bars
614 * this function will return the rating-bars for the search field.
618 public function get_rate_search()
622 for($i = 1; $i <= 5; $i++) {
624 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
626 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
627 $bar.= $this->cfg->web_path ."/resources/star.png";
629 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
632 onmouseover=\"show_rate('from', ". $i .");\"
633 onmouseout=\"reset_rate('from');\"
634 onclick=\"set_rate('from', ". $i .")\" />";
639 for($i = 1; $i <= 5; $i++) {
641 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
643 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
644 $bar.= $this->cfg->web_path ."/resources/star.png";
646 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
649 onmouseover=\"show_rate('to', ". $i .");\"
650 onmouseout=\"reset_rate('to');\"
651 onclick=\"set_rate('to', ". $i .");\" />";
656 } // get_rate_search()
659 * shrink text according provided limit
661 * If the length of the name exceeds $limit, text will be shortend
662 * and inner content will be replaced with "...".
665 * @param integer $limit
668 private function shrink_text($text, $limit)
670 if($limit != 0 && strlen($text) > $limit) {
671 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
679 * translate f-spoth photo path
681 * as the full-qualified path recorded in the f-spot database
682 * is usally not the same as on the webserver, this function
683 * will replace the path with that one specified in the cfg
684 * @param string $path
687 public function translate_path($path)
689 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
694 * control HTML ouput for a single photo
696 * this function provides all the necessary information
697 * for the single photo template.
698 * @param integer photo
700 public function showPhoto($photo)
702 /* get all photos from the current photo selection */
703 $all_photos = $this->getPhotoSelection();
704 $count = count($all_photos);
706 for($i = 0; $i < $count; $i++) {
708 // $get_next will be set, when the photo which has to
709 // be displayed has been found - this means that the
710 // next available is in fact the NEXT image (for the
712 if(isset($get_next)) {
713 $next_img = $all_photos[$i];
717 /* the next photo is our NEXT photo */
718 if($all_photos[$i] == $photo) {
722 $previous_img = $all_photos[$i];
725 if($photo == $all_photos[$i]) {
730 $details = $this->get_photo_details($photo);
737 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
739 /* if current version is already set, use it */
740 if($this->get_current_version() !== false)
741 $version = $this->get_current_version();
743 /* if version not set yet, we assume to display the latest version */
744 if(!isset($version) || !$this->is_valid_version($photo, $version))
745 $version = $this->get_latest_version($photo);
747 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
749 if(!file_exists($orig_path)) {
750 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
754 if(!is_readable($orig_path)) {
755 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
759 /* If the thumbnail doesn't exist yet, try to create it */
760 if(!file_exists($thumb_path)) {
761 $this->gen_thumb($photo, true);
762 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
765 /* get mime-type, height and width from the original photo */
766 $info = getimagesize($orig_path);
768 /* get EXIF information if JPEG */
769 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
770 $meta = $this->get_meta_informations($orig_path);
773 /* If EXIF data are available, use them */
774 if(isset($meta['ExifImageWidth'])) {
775 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
777 $meta_res = $info[0] ."x". $info[1];
780 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
781 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
783 $extern_link = "index.php?mode=showp&id=". $photo;
784 $current_tags = $this->getCurrentTags();
785 if($current_tags != "") {
786 $extern_link.= "&tags=". $current_tags;
788 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
789 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
792 $this->tmpl->assign('extern_link', $extern_link);
794 if(!file_exists($thumb_path)) {
795 $this->_error("Can't open file ". $thumb_path ."\n");
799 $info_thumb = getimagesize($thumb_path);
801 $this->tmpl->assign('description', $details['description']);
802 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
803 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
805 $this->tmpl->assign('width', $info_thumb[0]);
806 $this->tmpl->assign('height', $info_thumb[1]);
807 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
808 $this->tmpl->assign('ExifMadeWith', $meta_make);
809 $this->tmpl->assign('ExifOrigResolution', $meta_res);
810 $this->tmpl->assign('ExifFileSize', $meta_size);
812 if($this->is_user_friendly_url()) {
813 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
814 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
817 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
818 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
821 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
823 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
824 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
825 $this->tmpl->assign('current_img', $photo);
827 if(isset($previous_img)) {
828 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
829 $this->tmpl->assign('prev_img', $previous_img);
832 if(isset($next_img)) {
833 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
834 $this->tmpl->assign('next_img', $next_img);
837 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
838 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
839 $this->tmpl->assign('photo_number', $i);
840 $this->tmpl->assign('photo_count', count($all_photos));
841 $this->tmpl->assign('photo', $photo);
842 $this->tmpl->assign('version', $version);
844 /* if the photo as alternative versions, set a flag for the template */
845 if($this->get_photo_versions($photo))
846 $this->tmpl->assign('has_versions', true);
848 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
850 return $this->tmpl->fetch("single_photo.tpl");
855 * all available tags and tag cloud
857 * this function outputs all available tags (time ordered)
858 * and in addition output them as tag cloud (tags which have
859 * many photos will appears more then others)
861 public function getAvailableTags()
863 /* retrive tags from database */
868 $result = $this->db->db_query("
869 SELECT tag_id as id, count(tag_id) as quantity
879 while($row = $this->db->db_fetch_object($result)) {
880 $tags[$row['id']] = $row['quantity'];
883 // change these font sizes if you will
884 $max_size = 125; // max font size in %
885 $min_size = 75; // min font size in %
888 $max_sat = hexdec('cc');
889 $min_sat = hexdec('44');
891 // get the largest and smallest array values
892 $max_qty = max(array_values($tags));
893 $min_qty = min(array_values($tags));
895 // find the range of values
896 $spread = $max_qty - $min_qty;
897 if (0 == $spread) { // we don't want to divide by zero
901 // determine the font-size increment
902 // this is the increase per tag quantity (times used)
903 $step = ($max_size - $min_size)/($spread);
904 $step_sat = ($max_sat - $min_sat)/($spread);
906 // loop through our tag array
907 foreach ($tags as $key => $value) {
909 /* has the currently processed tag already been added to
910 the selected tag list? if so, ignore it here...
912 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
915 // calculate CSS font-size
916 // find the $value in excess of $min_qty
917 // multiply by the font-size increment ($size)
918 // and add the $min_size set above
919 $size = $min_size + (($value - $min_qty) * $step);
920 // uncomment if you want sizes in whole %:
923 $color = $min_sat + ($value - $min_qty) * $step_sat;
929 if(isset($this->tags[$key])) {
930 if($this->is_user_friendly_url()) {
931 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
932 onclick=\"Tags('add', ". $key ."); return false;\"
934 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
935 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
938 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
939 onclick=\"Tags('add', ". $key ."); return false;\"
941 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
942 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
947 $output = substr($output, 0, strlen($output)-2);
950 } // getAvailableTags()
953 * output all selected tags
955 * this function output all tags which have been selected
956 * by the user. the selected tags are stored in the
957 * session-variable $_SESSION['selected_tags']
960 public function getSelectedTags($type = 'link')
962 /* retrive tags from database */
967 foreach($this->avail_tags as $tag)
969 // return all selected tags
970 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
975 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
979 <div class=\"tagresulttag\">
980 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
981 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
991 $output = substr($output, 0, strlen($output)-2);
995 return "no tags selected";
998 } // getSelectedTags()
1001 * add tag to users session variable
1003 * this function will add the specified to users current
1004 * tag selection. if a date search has been made before
1005 * it will be now cleared
1008 public function addTag($tag)
1010 if(!isset($_SESSION['selected_tags']))
1011 $_SESSION['selected_tags'] = Array();
1013 if(isset($_SESSION['searchfor_tag']))
1014 unset($_SESSION['searchfor_tag']);
1016 // has the user requested to hide this tag, and still someone,
1017 // somehow tries to add it, don't allow this.
1018 if(!isset($this->cfg->hide_tags) &&
1019 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1022 if(!in_array($tag, $_SESSION['selected_tags']))
1023 array_push($_SESSION['selected_tags'], $tag);
1030 * remove tag to users session variable
1032 * this function removes the specified tag from
1033 * users current tag selection
1034 * @param string $tag
1037 public function delTag($tag)
1039 if(isset($_SESSION['searchfor_tag']))
1040 unset($_SESSION['searchfor_tag']);
1042 if(isset($_SESSION['selected_tags'])) {
1043 $key = array_search($tag, $_SESSION['selected_tags']);
1044 unset($_SESSION['selected_tags'][$key]);
1045 sort($_SESSION['selected_tags']);
1053 * reset tag selection
1055 * if there is any tag selection, it will be
1058 public function resetTags()
1060 if(isset($_SESSION['selected_tags']))
1061 unset($_SESSION['selected_tags']);
1066 * returns the value for the autocomplete tag-search
1069 public function get_xml_tag_list()
1071 if(!isset($_GET['search']) || !is_string($_GET['search']))
1072 $_GET['search'] = '';
1077 /* retrive tags from database */
1080 $matched_tags = Array();
1082 header("Content-Type: text/xml");
1084 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1085 $string.= "<results>\n";
1087 foreach($this->avail_tags as $tag)
1089 if(!empty($_GET['search']) &&
1090 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1091 count($matched_tags) < $length) {
1093 $count = $this->get_num_photos($tag);
1096 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1099 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1105 /* if we have collected enough items, break out */
1106 if(count($matched_tags) >= $length)
1110 $string.= "</results>\n";
1114 } // get_xml_tag_list()
1118 * reset single photo
1120 * if a specific photo was requested (external link)
1121 * unset the session variable now
1123 public function resetPhotoView()
1125 if(isset($_SESSION['current_photo']))
1126 unset($_SESSION['current_photo']);
1128 if(isset($_SESSION['current_version']))
1129 unset($_SESSION['current_version']);
1131 } // resetPhotoView();
1136 * if any tag search has taken place, reset it now
1138 public function resetTagSearch()
1140 if(isset($_SESSION['searchfor_tag']))
1141 unset($_SESSION['searchfor_tag']);
1143 } // resetTagSearch()
1148 * if any name search has taken place, reset it now
1150 public function resetNameSearch()
1152 if(isset($_SESSION['searchfor_name']))
1153 unset($_SESSION['searchfor_name']);
1155 } // resetNameSearch()
1160 * if any date search has taken place, reset it now.
1162 public function resetDateSearch()
1164 if(isset($_SESSION['from_date']))
1165 unset($_SESSION['from_date']);
1166 if(isset($_SESSION['to_date']))
1167 unset($_SESSION['to_date']);
1169 } // resetDateSearch();
1174 * if any rate search has taken place, reset it now.
1176 public function resetRateSearch()
1178 if(isset($_SESSION['rate_from']))
1179 unset($_SESSION['rate_from']);
1180 if(isset($_SESSION['rate_to']))
1181 unset($_SESSION['rate_to']);
1183 } // resetRateSearch();
1186 * return all photo according selection
1188 * this function returns all photos based on
1189 * the tag-selection, tag- or date-search.
1190 * the tag-search also has to take care of AND
1191 * and OR conjunctions
1194 public function getPhotoSelection()
1196 $matched_photos = Array();
1197 $additional_where_cond = "";
1199 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1200 $from_date = $_SESSION['from_date'];
1201 $to_date = $_SESSION['to_date'];
1202 $additional_where_cond.= "
1203 p.time>='". $from_date ."'
1205 p.time<='". $to_date ."'
1209 if(isset($_SESSION['searchfor_name'])) {
1211 /* check for previous conditions. if so add 'AND' */
1212 if(!empty($additional_where_cond)) {
1213 $additional_where_cond.= " AND ";
1216 if($this->dbver < 9) {
1217 $additional_where_cond.= "
1219 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1221 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1226 $additional_where_cond.= "
1228 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1230 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1236 /* limit result based on rate-search */
1237 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1239 if($this->dbver > 10) {
1241 /* check for previous conditions. if so add 'AND' */
1242 if(!empty($additional_where_cond)) {
1243 $additional_where_cond.= " AND ";
1246 $additional_where_cond.= "
1247 p.rating >= ". $_SESSION['rate_from'] ."
1249 p.rating <= ". $_SESSION['rate_to'] ."
1254 if(isset($_SESSION['sort_order'])) {
1255 $order_str = $this->get_sort_order();
1258 /* return a search result */
1259 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1261 SELECT DISTINCT pt1.photo_id
1263 INNER JOIN photo_tags pt2
1264 ON pt1.photo_id=pt2.photo_id
1268 ON pt1.photo_id=p.id
1271 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1273 if(!empty($additional_where_cond))
1274 $query_str.= "AND ". $additional_where_cond ." ";
1276 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1277 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1280 if(isset($order_str))
1281 $query_str.= $order_str;
1283 $result = $this->db->db_query($query_str);
1284 while($row = $this->db->db_fetch_object($result)) {
1285 array_push($matched_photos, $row['photo_id']);
1287 return $matched_photos;
1290 /* return according the selected tags */
1291 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1293 foreach($_SESSION['selected_tags'] as $tag)
1294 $selected.= $tag .",";
1295 $selected = substr($selected, 0, strlen($selected)-1);
1297 /* photo has to match at least on of the selected tags */
1298 if($_SESSION['tag_condition'] == 'or') {
1300 SELECT DISTINCT pt1.photo_id
1302 INNER JOIN photo_tags pt2
1303 ON pt1.photo_id=pt2.photo_id
1307 ON pt1.photo_id=p.id
1308 WHERE pt1.tag_id IN (". $selected .")
1310 if(!empty($additional_where_cond))
1311 $query_str.= "AND ". $additional_where_cond ." ";
1313 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1314 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1317 if(isset($order_str))
1318 $query_str.= $order_str;
1320 /* photo has to match all selected tags */
1321 elseif($_SESSION['tag_condition'] == 'and') {
1323 if(count($_SESSION['selected_tags']) >= 32) {
1324 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1325 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1329 /* Join together a table looking like
1331 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1333 so the query can quickly return all images matching the
1334 selected tags in an AND condition
1339 SELECT DISTINCT pt1.photo_id
1343 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1350 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1352 INNER JOIN photo_tags pt". ($i+2) ."
1353 ON pt1.photo_id=pt". ($i+2) .".photo_id
1358 ON pt1.photo_id=p.id
1360 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1361 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1363 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1366 if(!empty($additional_where_cond))
1367 $query_str.= "AND ". $additional_where_cond;
1369 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1370 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1373 if(isset($order_str))
1374 $query_str.= $order_str;
1378 $result = $this->db->db_query($query_str);
1379 while($row = $this->db->db_fetch_object($result)) {
1380 array_push($matched_photos, $row['photo_id']);
1382 return $matched_photos;
1385 /* return all available photos */
1387 SELECT DISTINCT p.id
1389 LEFT JOIN photo_tags pt
1395 if(!empty($additional_where_cond))
1396 $query_str.= "WHERE ". $additional_where_cond ." ";
1398 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1399 if(!empty($additional_where_cond))
1400 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1402 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1405 if(isset($order_str))
1406 $query_str.= $order_str;
1408 $result = $this->db->db_query($query_str);
1409 while($row = $this->db->db_fetch_object($result)) {
1410 array_push($matched_photos, $row['id']);
1412 return $matched_photos;
1414 } // getPhotoSelection()
1417 * control HTML ouput for photo index
1419 * this function provides all the necessary information
1420 * for the photo index template.
1423 public function showPhotoIndex()
1425 $photos = $this->getPhotoSelection();
1426 $current_tags = $this->getCurrentTags();
1428 $count = count($photos);
1430 /* if all thumbnails should be shown on one page */
1431 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1435 /* thumbnails should be splitted up in several pages */
1436 elseif($this->cfg->thumbs_per_page > 0) {
1438 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1442 $begin_with = $_SESSION['begin_with'];
1445 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1449 $images[$thumbs] = Array();
1450 $img_height[$thumbs] = Array();
1451 $img_width[$thumbs] = Array();
1452 $img_id[$thumbs] = Array();
1453 $img_name[$thumbs] = Array();
1454 $img_fullname[$thumbs] = Array();
1455 $img_title = Array();
1456 $img_rating = Array();
1458 for($i = $begin_with; $i < $end_with; $i++) {
1460 if(isset($photos[$i])) {
1462 $images[$thumbs] = $photos[$i];
1463 $img_id[$thumbs] = $i;
1464 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1465 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1466 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1467 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1469 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1471 if(file_exists($thumb_path)) {
1472 $info = getimagesize($thumb_path);
1473 $img_width[$thumbs] = $info[0];
1474 $img_height[$thumbs] = $info[1];
1480 // +1 for for smarty's selection iteration
1483 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1484 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1486 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1487 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1488 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1491 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1492 $this->tmpl->assign('tag_result', 1);
1495 /* do we have to display the page selector ? */
1496 if($this->cfg->thumbs_per_page != 0) {
1500 /* calculate the page switchers */
1501 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1502 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1504 if($begin_with != 0)
1505 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1506 if($end_with < $count)
1507 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1509 $photo_per_page = $this->cfg->thumbs_per_page;
1510 $last_page = ceil($count / $photo_per_page);
1512 /* get the current selected page */
1513 if($begin_with == 0) {
1517 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1524 for($i = 1; $i <= $last_page; $i++) {
1526 if($current_page == $i)
1527 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1528 elseif($current_page-1 == $i || $current_page+1 == $i)
1529 $style = "style=\"font-size: 105%;\"";
1530 elseif(($current_page-5 >= $i) && ($i != 1) ||
1531 ($current_page+5 <= $i) && ($i != $last_page))
1532 $style = "style=\"font-size: 75%;\"";
1536 $start_with = ($i*$photo_per_page)-$photo_per_page;
1538 if($this->is_user_friendly_url()) {
1539 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1542 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1544 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1548 $select.= ">". $i ."</a> ";
1550 // until 9 pages we show the selector from 1-9
1551 if($last_page <= 9) {
1552 $page_select.= $select;
1555 if($i == 1 /* first page */ ||
1556 $i == $last_page /* last page */ ||
1557 $i == $current_page /* current page */ ||
1558 $i == ceil($last_page * 0.25) /* first quater */ ||
1559 $i == ceil($last_page * 0.5) /* half */ ||
1560 $i == ceil($last_page * 0.75) /* third quater */ ||
1561 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1562 (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 */ ||
1563 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1564 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1566 $page_select.= $select;
1574 $page_select.= "......... ";
1579 /* only show the page selector if we have more then one page */
1581 $this->tmpl->assign('page_selector', $page_select);
1584 $extern_link = "index.php?mode=showpi";
1585 $rss_link = "index.php?mode=rss";
1586 if($current_tags != "") {
1587 $extern_link.= "&tags=". $current_tags;
1588 $rss_link.= "&tags=". $current_tags;
1590 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1591 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1592 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1595 $export_link = "index.php?mode=export";
1596 $slideshow_link = "index.php?mode=slideshow";
1598 $this->tmpl->assign('extern_link', $extern_link);
1599 $this->tmpl->assign('slideshow_link', $slideshow_link);
1600 $this->tmpl->assign('export_link', $export_link);
1601 $this->tmpl->assign('rss_link', $rss_link);
1602 $this->tmpl->assign('count', $count);
1603 $this->tmpl->assign('width', $this->cfg->thumb_width);
1604 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1605 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1606 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1607 $this->tmpl->assign('images', $images);
1608 $this->tmpl->assign('img_width', $img_width);
1609 $this->tmpl->assign('img_height', $img_height);
1610 $this->tmpl->assign('img_id', $img_id);
1611 $this->tmpl->assign('img_name', $img_name);
1612 $this->tmpl->assign('img_fullname', $img_fullname);
1613 $this->tmpl->assign('img_title', $img_title);
1614 $this->tmpl->assign('img_rating', $img_rating);
1615 $this->tmpl->assign('thumbs', $thumbs);
1616 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1618 $result = $this->tmpl->fetch("photo_index.tpl");
1620 /* if we are returning to photo index from an photo-view,
1621 scroll the window to the last shown photo-thumbnail.
1622 after this, unset the last_photo session variable.
1624 if(isset($_SESSION['last_photo'])) {
1625 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1626 unset($_SESSION['last_photo']);
1631 } // showPhotoIndex()
1634 * show credit template
1636 public function showCredits()
1638 $this->tmpl->assign('version', $this->cfg->version);
1639 $this->tmpl->assign('product', $this->cfg->product);
1640 $this->tmpl->assign('db_version', $this->dbver);
1641 $this->tmpl->show("credits.tpl");
1646 * create thumbnails for the requested width
1648 * this function creates image thumbnails of $orig_image
1649 * stored as $thumb_image. It will check if the image is
1650 * in a supported format, if necessary rotate the image
1651 * (based on EXIF orientation meta headers) and re-sizing.
1652 * @param string $orig_image
1653 * @param string $thumb_image
1654 * @param integer $width
1657 public function create_thumbnail($orig_image, $thumb_image, $width)
1659 if(!file_exists($orig_image)) {
1663 $mime = $this->get_mime_info($orig_image);
1665 /* check if original photo is a support image type */
1666 if(!$this->checkifImageSupported($mime))
1673 $meta = $this->get_meta_informations($orig_image);
1679 if(isset($meta['Orientation'])) {
1680 switch($meta['Orientation']) {
1681 case 1: /* top, left */
1682 /* nothing to do */ break;
1683 case 2: /* top, right */
1684 $rotate = 0; $flip_hori = true; break;
1685 case 3: /* bottom, left */
1686 $rotate = 180; break;
1687 case 4: /* bottom, right */
1688 $flip_vert = true; break;
1689 case 5: /* left side, top */
1690 $rotate = 90; $flip_vert = true; break;
1691 case 6: /* right side, top */
1692 $rotate = 90; break;
1693 case 7: /* left side, bottom */
1694 $rotate = 270; $flip_vert = true; break;
1695 case 8: /* right side, bottom */
1696 $rotate = 270; break;
1700 $src_img = @imagecreatefromjpeg($orig_image);
1706 $src_img = @imagecreatefrompng($orig_image);
1710 case 'image/x-portable-pixmap':
1712 $src_img = new Imagick($orig_image);
1713 $handler = "imagick";
1718 if(!isset($src_img) || empty($src_img)) {
1719 print "Can't load image from ". $orig_image ."\n";
1727 /* grabs the height and width */
1728 $cur_width = imagesx($src_img);
1729 $cur_height = imagesy($src_img);
1731 // If requested width is more then the actual image width,
1732 // do not generate a thumbnail, instead safe the original
1733 // as thumbnail but with lower quality. But if the image
1734 // is to heigh too, then we still have to resize it.
1735 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1736 $result = imagejpeg($src_img, $thumb_image, 75);
1737 imagedestroy($src_img);
1744 $cur_width = $src_img->getImageWidth();
1745 $cur_height = $src_img->getImageHeight();
1747 // If requested width is more then the actual image width,
1748 // do not generate a thumbnail, instead safe the original
1749 // as thumbnail but with lower quality. But if the image
1750 // is to heigh too, then we still have to resize it.
1751 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1752 $src_img->setCompressionQuality(75);
1753 $src_img->setImageFormat('jpeg');
1754 $src_img->writeImage($thumb_image);
1756 $src_img->destroy();
1763 // If the image will be rotate because EXIF orientation said so
1764 // 'virtually rotate' the image for further calculations
1765 if($rotate == 90 || $rotate == 270) {
1767 $cur_width = $cur_height;
1771 /* calculates aspect ratio */
1772 $aspect_ratio = $cur_height / $cur_width;
1775 if($aspect_ratio < 1) {
1777 $new_h = abs($new_w * $aspect_ratio);
1779 /* 'virtually' rotate the image and calculate it's ratio */
1780 $tmp_w = $cur_height;
1781 $tmp_h = $cur_width;
1782 /* now get the ratio from the 'rotated' image */
1783 $tmp_ratio = $tmp_h/$tmp_w;
1784 /* now calculate the new dimensions */
1786 $tmp_h = abs($tmp_w * $tmp_ratio);
1788 // now that we know, how high they photo should be, if it
1789 // gets rotated, use this high to scale the image
1791 $new_w = abs($new_h / $aspect_ratio);
1793 // If the image will be rotate because EXIF orientation said so
1794 // now 'virtually rotate' back the image for the image manipulation
1795 if($rotate == 90 || $rotate == 270) {
1806 /* creates new image of that size */
1807 $dst_img = imagecreatetruecolor($new_w, $new_h);
1809 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1811 /* copies resized portion of original image into new image */
1812 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1814 /* needs the image to be flipped horizontal? */
1816 $this->_debug("(FLIP)");
1817 $dst_img = $this->flipImage($dst_img, 'hori');
1819 /* needs the image to be flipped vertical? */
1821 $this->_debug("(FLIP)");
1822 $dst_img = $this->flipImage($dst_img, 'vert');
1826 $this->_debug("(ROTATE)");
1827 $dst_img = $this->rotateImage($dst_img, $rotate);
1830 /* write down new generated file */
1831 $result = imagejpeg($dst_img, $thumb_image, 75);
1833 /* free your mind */
1834 imagedestroy($dst_img);
1835 imagedestroy($src_img);
1837 if($result === false) {
1838 print "Can't write thumbnail ". $thumb_image ."\n";
1848 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1850 /* needs the image to be flipped horizontal? */
1852 $this->_debug("(FLIP)");
1853 $src_img->rotateImage(new ImagickPixel(), 90);
1854 $src_img->flipImage();
1855 $src_img->rotateImage(new ImagickPixel(), -90);
1857 /* needs the image to be flipped vertical? */
1859 $this->_debug("(FLIP)");
1860 $src_img->flipImage();
1864 $this->_debug("(ROTATE)");
1865 $src_img->rotateImage(new ImagickPixel(), $rotate);
1868 $src_img->setCompressionQuality(75);
1869 $src_img->setImageFormat('jpeg');
1871 if(!$src_img->writeImage($thumb_image)) {
1872 print "Can't write thumbnail ". $thumb_image ."\n";
1877 $src_img->destroy();
1884 } // create_thumbnail()
1887 * return all exif meta data from the file
1888 * @param string $file
1891 public function get_meta_informations($file)
1893 return exif_read_data($file);
1895 } // get_meta_informations()
1898 * create phpfspot own sqlite database
1900 * this function creates phpfspots own sqlite database
1901 * if it does not exist yet. this own is used to store
1902 * some necessary informations (md5 sum's, ...).
1904 public function check_phpfspot_db()
1906 // if the config table doesn't exist yet, create it
1907 if(!$this->cfg_db->db_check_table_exists("images")) {
1908 $this->cfg_db->db_exec("
1909 CREATE TABLE images (
1911 img_version_idx int,
1912 img_md5 varchar(32),
1913 UNIQUE(img_idx, img_version_idx)
1918 if(!$this->cfg_db->db_check_table_exists("meta")) {
1919 $this->cfg_db->db_exec("
1921 meta_key varchar(255),
1922 meta_value varchar(255)
1926 /* db_version was added with phpfspot 1.7, before changes
1927 on the phpfspot database where not necessary.
1930 $this->cfg_db->db_exec("
1932 meta_key, meta_value
1934 'phpfspot Database Version',
1935 '". $this->cfg->db_version ."'
1940 /* if version <= 2 and column img_version_idx does not exist yet */
1941 if($this->get_db_version() <= 2 &&
1942 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
1944 if(!$this->cfg_db->db_start_transaction())
1945 die("Can not start database transaction");
1947 $result = $this->cfg_db->db_exec("
1948 CREATE TEMPORARY TABLE images_temp (
1950 img_version_idx int,
1951 img_md5 varchar(32),
1952 UNIQUE(img_idx, img_version_idx)
1957 $this->cfg_db->db_rollback_transaction();
1958 die("Upgrade failed - transaction rollback");
1961 $result = $this->cfg_db->db_exec("
1962 INSERT INTO images_temp
1971 $this->cfg_db->db_rollback_transaction();
1972 die("Upgrade failed - transaction rollback");
1975 $result = $this->cfg_db->db_exec("
1980 $this->cfg_db->db_rollback_transaction();
1981 die("Upgrade failed - transaction rollback");
1984 $result = $this->cfg_db->db_exec("
1985 CREATE TABLE images (
1987 img_version_idx int,
1988 img_md5 varchar(32),
1989 UNIQUE(img_idx, img_version_idx)
1994 $this->cfg_db->db_rollback_transaction();
1995 die("Upgrade failed - transaction rollback");
1998 $result = $this->cfg_db->db_exec("
2005 $this->cfg_db->db_rollback_transaction();
2006 die("Upgrade failed - transaction rollback");
2009 $result = $this->cfg_db->db_exec("
2010 DROP TABLE images_temp
2014 $this->cfg_db->db_rollback_transaction();
2015 die("Upgrade failed - transaction rollback");
2018 if(!$this->cfg_db->db_commit_transaction())
2019 die("Can not commit database transaction");
2023 } // check_phpfspot_db
2026 * generates thumbnails
2028 * This function generates JPEG thumbnails from
2029 * provided F-Spot photo indize and its alternative
2032 * 1. Check if all thumbnail generations (width) are already in place and
2034 * 2. Check if the md5sum of the original file has changed
2035 * 3. Generate the thumbnails if needed
2036 * @param integer $idx
2037 * @param integer $force
2038 * @param boolean $overwrite
2040 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2043 $versions = Array(0);
2045 $resolutions = Array(
2046 $this->cfg->thumb_width,
2047 $this->cfg->photo_width,
2048 $this->cfg->mini_width,
2052 if($alt_versions = $this->get_photo_versions($idx))
2053 $versions = array_merge($versions, $alt_versions);
2055 foreach($versions as $version) {
2057 /* get details from F-Spot's database */
2058 $details = $this->get_photo_details($idx, $version);
2060 /* calculate file MD5 sum */
2061 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2063 if(!file_exists($full_path)) {
2064 $this->_error("File ". $full_path ." does not exist\n");
2068 if(!is_readable($full_path)) {
2069 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2073 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2075 /* If Nikon NEF format, we need to treat it another way */
2076 if(isset($this->cfg->dcraw_bin) &&
2077 file_exists($this->cfg->dcraw_bin) &&
2078 is_executable($this->cfg->dcraw_bin) &&
2079 preg_match('/\.nef$/i', $details['uri'])) {
2081 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2083 /* if PPM file does not exist, let dcraw convert it from NEF */
2084 if(!file_exists($ppm_path)) {
2085 system($this->cfg->dcraw_bin ." -a ". $full_path);
2088 /* for now we handle the PPM instead of the NEF */
2089 $full_path = $ppm_path;
2093 $file_md5 = md5_file($full_path);
2096 foreach($resolutions as $resolution) {
2098 $generate_it = false;
2100 $thumb_sub_path = substr($file_md5, 0, 2);
2101 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2103 /* if thumbnail-subdirectory does not exist yet, create it */
2104 if(!file_exists(dirname($thumb_path))) {
2105 mkdir(dirname($thumb_path), 0755);
2108 /* if the thumbnail file doesn't exist, create it */
2109 if(!file_exists($thumb_path) || $force) {
2110 $generate_it = true;
2112 elseif($file_md5 != $this->getMD5($idx, $version)) {
2113 $generate_it = true;
2116 if($generate_it || $overwrite) {
2118 $this->_debug(" ". $resolution ."px");
2119 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2127 $this->_debug(" already exist");
2130 /* set the new/changed MD5 sum for the current photo */
2132 $this->setMD5($idx, $file_md5, $version);
2135 $this->_debug("\n");
2142 * returns stored md5 sum for a specific photo
2144 * this function queries the phpfspot database for a stored MD5
2145 * checksum of the specified photo. It also takes care of the
2146 * requested photo version - original or alternative photo.
2148 * @param integer $idx
2149 * @return string|null
2151 public function getMD5($idx, $version_idx = 0)
2153 $result = $this->cfg_db->db_query("
2159 img_idx='". $idx ."'
2161 img_version_idx='". $version_idx ."'
2167 if($img = $this->cfg_db->db_fetch_object($result))
2168 return $img['img_md5'];
2175 * set MD5 sum for the specific photo
2176 * @param integer $idx
2177 * @param string $md5
2179 private function setMD5($idx, $md5, $version_idx = 0)
2181 $result = $this->cfg_db->db_exec("
2182 INSERT OR REPLACE INTO images (
2183 img_idx, img_version_idx, img_md5
2186 '". $version_idx ."',
2194 * store current tag condition
2196 * this function stores the current tag condition
2197 * (AND or OR) in the users session variables
2198 * @param string $mode
2201 public function setTagCondition($mode)
2203 $_SESSION['tag_condition'] = $mode;
2207 } // setTagCondition()
2210 * invoke tag & date search
2212 * this function will return all matching tags and store
2213 * them in the session variable selected_tags. furthermore
2214 * it also handles the date search.
2215 * getPhotoSelection() will then only return the matching
2219 public function startSearch()
2222 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2223 $date_from = $_POST['date_from'];
2225 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2226 $date_to = $_POST['date_to'];
2229 /* tag-name search */
2230 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2231 $searchfor_tag = $_POST['for_tag'];
2232 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2235 unset($_SESSION['searchfor_tag']);
2238 /* file-name search */
2239 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2240 $_SESSION['searchfor_name'] = $_POST['for_name'];
2243 unset($_SESSION['searchfor_name']);
2247 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2249 $_SESSION['rate_from'] = $_POST['rate_from'];
2251 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2252 $_SESSION['rate_to'] = $_POST['rate_to'];
2256 /* delete any previously set value */
2257 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2262 if(isset($date_from) && !empty($date_from))
2263 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2265 unset($_SESSION['from_date']);
2267 if(isset($date_to) && !empty($date_to))
2268 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2270 unset($_SESSION['to_date']);
2272 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2273 /* new search, reset the current selected tags */
2274 $_SESSION['selected_tags'] = Array();
2275 foreach($this->avail_tags as $tag) {
2276 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2277 array_push($_SESSION['selected_tags'], $tag);
2286 * updates sort order in session variable
2288 * this function is invoked by RPC and will sort the requested
2289 * sort order in the session variable.
2290 * @param string $sort_order
2293 public function updateSortOrder($order)
2295 if(isset($this->sort_orders[$order])) {
2296 $_SESSION['sort_order'] = $order;
2300 return "unkown error";
2302 } // updateSortOrder()
2305 * update photo version in session variable
2307 * this function is invoked by RPC and will set the requested
2308 * photo version in the session variable.
2309 * @param string $photo_version
2312 public function update_photo_version($photo_idx, $photo_version)
2314 if($this->is_valid_version($photo_idx, $photo_version)) {
2315 $_SESSION['current_version'] = $photo_version;
2319 return "incorrect photo version provided";
2321 } // update_photo_version()
2326 * this function rotates the image according the
2328 * @param string $img
2329 * @param integer $degress
2332 private function rotateImage($img, $degrees)
2334 if(function_exists("imagerotate")) {
2335 $img = imagerotate($img, $degrees, 0);
2337 function imagerotate($src_img, $angle)
2339 $src_x = imagesx($src_img);
2340 $src_y = imagesy($src_img);
2341 if ($angle == 180) {
2345 elseif ($src_x <= $src_y) {
2349 elseif ($src_x >= $src_y) {
2354 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2355 imagealphablending($rotate, false);
2360 for ($y = 0; $y < ($src_y); $y++) {
2361 for ($x = 0; $x < ($src_x); $x++) {
2362 $color = imagecolorat($src_img, $x, $y);
2363 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2369 for ($y = 0; $y < ($src_y); $y++) {
2370 for ($x = 0; $x < ($src_x); $x++) {
2371 $color = imagecolorat($src_img, $x, $y);
2372 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2378 for ($y = 0; $y < ($src_y); $y++) {
2379 for ($x = 0; $x < ($src_x); $x++) {
2380 $color = imagecolorat($src_img, $x, $y);
2381 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2395 $img = imagerotate($img, $degrees);
2404 * returns flipped image
2406 * this function will return an either horizontal or
2407 * vertical flipped truecolor image.
2408 * @param string $image
2409 * @param string $mode
2412 private function flipImage($image, $mode)
2414 $w = imagesx($image);
2415 $h = imagesy($image);
2416 $flipped = imagecreatetruecolor($w, $h);
2420 for ($y = 0; $y < $h; $y++) {
2421 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2425 for ($x = 0; $x < $w; $x++) {
2426 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2436 * return all assigned tags for the specified photo
2437 * @param integer $idx
2440 private function get_photo_tags($idx)
2442 $result = $this->db->db_query("
2445 INNER JOIN photo_tags pt
2447 WHERE pt.photo_id='". $idx ."'
2452 while($row = $this->db->db_fetch_object($result)) {
2453 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2455 $tags[$row['id']] = $row['name'];
2460 } // get_photo_tags()
2463 * create on-the-fly images with text within
2464 * @param string $txt
2465 * @param string $color
2466 * @param integer $space
2467 * @param integer $font
2470 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2472 if (strlen($color) != 6)
2475 $int = hexdec($color);
2476 $h = imagefontheight($font);
2477 $fw = imagefontwidth($font);
2478 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2479 $lines = count($txt);
2480 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2481 $bg = imagecolorallocate($im, 255, 255, 255);
2482 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2485 foreach ($txt as $text) {
2486 $x = (($w - ($fw * strlen($text))) / 2);
2487 imagestring($im, $font, $x, $y, $text, $color);
2488 $y += ($h + $space);
2491 Header("Content-type: image/png");
2494 } // showTextImage()
2497 * check if all requirements are met
2500 private function check_requirements()
2502 if(!function_exists("imagecreatefromjpeg")) {
2503 print "PHP GD library extension is missing<br />\n";
2507 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2508 print "PHP SQLite3 library extension is missing<br />\n";
2512 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2513 ini_set('track_errors', 1);
2514 @include_once 'HTML/AJAX/Server.php';
2515 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2516 print "PEAR HTML_AJAX package is missing<br />\n";
2519 @include_once 'Calendar/Calendar.php';
2520 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2521 print "PEAR Calendar package is missing<br />\n";
2524 @include_once 'Console/Getopt.php';
2525 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2526 print "PEAR Console_Getopt package is missing<br />\n";
2529 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2530 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2531 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2534 ini_restore('track_errors');
2541 } // check_requirements()
2543 private function _debug($text)
2545 if(isset($this->fromcmd)) {
2552 * check if specified MIME type is supported
2553 * @param string $mime
2556 public function checkifImageSupported($mime)
2558 $supported_types = Array(
2561 "image/x-portable-pixmap",
2565 if(in_array($mime, $supported_types))
2570 } // checkifImageSupported()
2574 * @param string $text
2576 public function _error($text)
2578 switch($this->cfg->logging) {
2581 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2582 print $text ."<br />\n";
2588 error_log($text, 3, $this->cfg->log_file);
2592 $this->runtime_error = true;
2597 * get calendar input-text fields
2599 * this function returns a text-field used for the data selection.
2600 * Either it will be filled with the current date or, if available,
2601 * filled with the date user entered previously.
2603 * @param string $mode
2606 private function get_date_text_field($mode)
2608 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2610 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2612 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2614 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2615 if(!isset($_SESSION[$mode .'_date']))
2616 $output.= " disabled=\"disabled\"";
2621 } // get_date_text_field()
2624 * output calendar matrix
2625 * @param integer $year
2626 * @param integer $month
2627 * @param integer $day
2629 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2631 if (!isset($year)) $year = date('Y');
2632 if (!isset($month)) $month = date('m');
2633 if (!isset($day)) $day = date('d');
2638 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2639 require_once CALENDAR_ROOT.'Day.php';
2642 $month = new Calendar_Month_Weekdays($year,$month);
2645 $prevStamp = $month->prevMonth(true);
2646 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2647 $nextStamp = $month->nextMonth(true);
2648 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2650 $selectedDays = array (
2651 new Calendar_Day($year,$month,$day),
2652 new Calendar_Day($year,12,25),
2655 // Build the days in the month
2656 $month->build($selectedDays);
2658 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2659 $this->tmpl->assign('prev_month', $prev);
2660 $this->tmpl->assign('next_month', $next);
2662 while ( $day = $month->fetch() ) {
2664 if(!isset($matrix[$rows]))
2665 $matrix[$rows] = Array();
2669 $dayStamp = $day->thisDay(true);
2670 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2672 // isFirst() to find start of week
2673 if ( $day->isFirst() )
2676 if ( $day->isSelected() ) {
2677 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2678 } else if ( $day->isEmpty() ) {
2679 $string.= "<td> </td>\n";
2681 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2684 // isLast() to find end of week
2685 if ( $day->isLast() )
2686 $string.= "</tr>\n";
2688 $matrix[$rows][$cols] = $string;
2698 $this->tmpl->assign('matrix', $matrix);
2699 $this->tmpl->assign('rows', $rows);
2700 $this->tmpl->show("calendar.tpl");
2702 } // get_calendar_matrix()
2705 * output export page
2706 * @param string $mode
2708 public function getExport($mode)
2710 $pictures = $this->getPhotoSelection();
2711 $current_tags = $this->getCurrentTags();
2713 foreach($pictures as $picture) {
2715 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2716 if($current_tags != "") {
2717 $orig_url.= "&tags=". $current_tags;
2719 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2720 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2723 if($this->is_user_friendly_url()) {
2724 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2727 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2733 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2734 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2738 // "[%pictureurl% %thumbnailurl%]"
2739 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2742 case 'MoinMoinList':
2743 // " * [%pictureurl% %thumbnailurl%]"
2744 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2755 public function getRSSFeed()
2757 Header("Content-type: text/xml; charset=utf-8");
2758 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2761 xmlns:media="http://search.yahoo.com/mrss/"
2762 xmlns:dc="http://purl.org/dc/elements/1.1/"
2765 <title>phpfspot</title>
2766 <description>phpfspot RSS feed</description>
2767 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2768 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2769 <generator>phpfspot</generator>
2772 $pictures = $this->getPhotoSelection();
2773 $current_tags = $this->getCurrentTags();
2775 foreach($pictures as $picture) {
2777 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2778 if($current_tags != "") {
2779 $orig_url.= "&tags=". $current_tags;
2781 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2782 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2785 $details = $this->get_photo_details($picture);
2787 if($this->is_user_friendly_url()) {
2788 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2791 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2794 $thumb_html = htmlspecialchars("
2795 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2797 ". $details['description']);
2799 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2801 /* get EXIF information if JPEG */
2802 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2803 $meta = $this->get_meta_informations($orig_path);
2808 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2809 <link><?php print htmlspecialchars($orig_url); ?></link>
2810 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2811 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2813 <?php print $thumb_html; ?>
2815 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2830 * get all selected tags
2832 * This function will return all selected tags as one string, seperated
2836 private function getCurrentTags()
2839 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2840 foreach($_SESSION['selected_tags'] as $tag)
2841 $current_tags.= $tag .",";
2842 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2844 return $current_tags;
2846 } // getCurrentTags()
2849 * return the current photo
2851 public function get_current_photo()
2853 if(isset($_SESSION['current_photo'])) {
2854 return $_SESSION['current_photo'];
2859 } // get_current_photo()
2862 * current selected photo version
2864 * this function returns the current selected photo version
2865 * from the session variables.
2869 public function get_current_version()
2871 /* if current version is set, return it, if the photo really has that version */
2872 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2873 return $_SESSION['current_version'];
2877 } // get_current_version()
2880 * returns latest available photo version
2882 * this function returns the latested available version
2883 * for the requested photo.
2887 public function get_latest_version($photo_idx)
2889 /* try to get the lasted version for the current photo */
2890 if($versions = $this->get_photo_versions($photo_idx))
2891 return $versions[count($versions)-1];
2893 /* if no alternative version were found, return original version */
2896 } // get_current_version()
2899 * tells the client browser what to do
2901 * this function is getting called via AJAX by the
2902 * client browsers. it will tell them what they have
2903 * to do next. This is necessary for directly jumping
2904 * into photo index or single photo view when the are
2905 * requested with specific URLs
2908 public function whatToDo()
2910 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2912 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2913 return "showpi_tags";
2915 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2922 * return the current process-user
2925 private function getuid()
2927 if($uid = posix_getuid()) {
2928 if($user = posix_getpwuid($uid)) {
2929 return $user['name'];
2938 * photo version select list
2940 * this function returns a HTML select list (drop down)
2941 * to select a alternative photo version of the original photo.
2943 * @param array $params
2944 * @param smarty $smarty
2947 public function smarty_photo_version_select_list($params, &$smarty)
2949 if(!isset($params['photo']) || !isset($params['current']))
2952 $output = "<option value=\"0\">Original</option>";
2953 $versions = $this->get_photo_versions($params['photo']);
2955 foreach($versions as $version) {
2957 $output.= "<option value=\"". $version ."\"";
2958 if($version == $params['current']) {
2959 $output.= " selected=\"selected\"";
2961 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
2966 } // smarty_photo_version_select_list()
2969 * returns a select-dropdown box to select photo index sort parameters
2970 * @param array $params
2971 * @param smarty $smarty
2974 public function smarty_sort_select_list($params, &$smarty)
2978 foreach($this->sort_orders as $key => $value) {
2979 $output.= "<option value=\"". $key ."\"";
2980 if($key == $_SESSION['sort_order']) {
2981 $output.= " selected=\"selected\"";
2983 $output.= ">". $value ."</option>";
2988 } // smarty_sort_select_list()
2991 * returns the currently selected sort order
2994 private function get_sort_order()
2996 switch($_SESSION['sort_order']) {
2998 return " ORDER BY p.time ASC";
3001 return " ORDER BY p.time DESC";
3004 if($this->dbver < 9) {
3005 return " ORDER BY p.name ASC";
3008 return " ORDER BY basename(p.uri) ASC";
3012 if($this->dbver < 9) {
3013 return " ORDER BY p.name DESC";
3016 return " ORDER BY basename(p.uri) DESC";
3020 return " ORDER BY t.name ASC ,p.time ASC";
3023 return " ORDER BY t.name DESC ,p.time ASC";
3026 return " ORDER BY p.rating ASC, t.name ASC";
3029 return " ORDER BY p.rating DESC, t.name ASC";
3033 } // get_sort_order()
3036 * return the next to be shown slide show image
3038 * this function returns the URL of the next image
3039 * in the slideshow sequence.
3042 public function getNextSlideShowImage()
3044 $all_photos = $this->getPhotoSelection();
3046 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3047 $_SESSION['slideshow_img'] = 0;
3049 $_SESSION['slideshow_img']++;
3051 if($this->is_user_friendly_url()) {
3052 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3055 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3057 } // getNextSlideShowImage()
3060 * return the previous to be shown slide show image
3062 * this function returns the URL of the previous image
3063 * in the slideshow sequence.
3066 public function getPrevSlideShowImage()
3068 $all_photos = $this->getPhotoSelection();
3070 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3071 $_SESSION['slideshow_img'] = 0;
3073 $_SESSION['slideshow_img']--;
3075 if($this->is_user_friendly_url()) {
3076 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3079 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3081 } // getPrevSlideShowImage()
3083 public function resetSlideShow()
3085 if(isset($_SESSION['slideshow_img']))
3086 unset($_SESSION['slideshow_img']);
3088 } // resetSlideShow()
3093 * this function will get all photos from the fspot
3094 * database and randomly return ONE entry
3096 * saddly there is yet no sqlite3 function which returns
3097 * the bulk result in array, so we have to fill up our
3101 public function get_random_photo()
3110 /* if show_tags is set, only return details for photos which
3111 are specified to be shown
3113 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3115 INNER JOIN photo_tags pt
3120 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3123 $result = $this->db->db_query($query_str);
3125 while($row = $this->db->db_fetch_object($result)) {
3126 array_push($all, $row['id']);
3129 return $all[array_rand($all)];
3131 } // get_random_photo()
3134 * get random photo tag photo
3136 * this function will get all photos tagged with the requested
3137 * tag from the fspot database and randomly return ONE entry
3139 * saddly there is yet no sqlite3 function which returns
3140 * the bulk result in array, so we have to fill up our
3144 public function get_random_tag_photo($tagidx)
3148 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3151 DISTINCT pt1.photo_id as id
3154 INNER JOIN photo_tags
3155 pt2 ON pt1.photo_id=pt2.photo_id
3161 pt1.tag_id LIKE '". $tagidx ."'
3163 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3165 t1.sort_priority ASC";
3173 INNER JOIN photo_tags pt
3176 pt.tag_id LIKE '". $tagidx ."'";
3179 $result = $this->db->db_query($query_str);
3181 while($row = $this->db->db_fetch_object($result)) {
3182 array_push($all, $row['id']);
3185 return $all[array_rand($all)];
3187 } // get_random_tag_photo()
3190 * validates provided date
3192 * this function validates if the provided date
3193 * contains a valid date and will return true
3195 * @param string $date_str
3198 public function isValidDate($date_str)
3200 $timestamp = strtotime($date_str);
3202 if(is_numeric($timestamp))
3210 * timestamp to string conversion
3211 * @param integer $timestamp
3214 private function ts2str($timestamp)
3216 if(!empty($timestamp) && is_numeric($timestamp))
3217 return strftime("%Y-%m-%d", $timestamp);
3222 * extract tag-names from $_GET['tags']
3223 * @param string $tags_str
3226 private function extractTags($tags_str)
3228 $not_validated = split(',', $tags_str);
3229 $validated = array();
3231 foreach($not_validated as $tag) {
3232 if(is_numeric($tag))
3233 array_push($validated, $tag);
3241 * returns the full path to a thumbnail
3242 * @param integer $width
3243 * @param integer $photo
3246 public function get_thumb_path($width, $photo_idx, $version_idx)
3248 $md5 = $this->getMD5($photo_idx, $version_idx);
3249 $sub_path = substr($md5, 0, 2);
3250 return $this->cfg->thumb_path
3258 } // get_thumb_path()
3261 * returns server's virtual host name
3264 private function get_server_name()
3266 return $_SERVER['SERVER_NAME'];
3267 } // get_server_name()
3270 * returns type of webprotocol which is currently used
3273 private function get_web_protocol()
3275 if(!isset($_SERVER['HTTPS']))
3279 } // get_web_protocol()
3282 * return url to this phpfspot installation
3285 private function get_phpfspot_url()
3287 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3289 } // get_phpfspot_url()
3292 * returns the number of photos which are tagged with $tag_id
3293 * @param integer $tag_id
3296 public function get_num_photos($tag_id)
3298 if($result = $this->db->db_fetchSingleRow("
3299 SELECT count(*) as number
3302 tag_id LIKE '". $tag_id ."'")) {
3304 return $result['number'];
3310 } // get_num_photos()
3313 * check file exists and is readable
3315 * returns true, if everything is ok, otherwise false
3316 * if $silent is not set, this function will output and
3318 * @param string $file
3319 * @param boolean $silent
3322 private function check_readable($file, $silent = null)
3324 if(!file_exists($file)) {
3326 print "File \"". $file ."\" does not exist.\n";
3330 if(!is_readable($file)) {
3332 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3338 } // check_readable()
3341 * check if all needed indices are present
3343 * this function checks, if some needed indices are already
3344 * present, or if not, create them on the fly. they are
3345 * necessary to speed up some queries like that one look for
3346 * all tags, when show_tags is specified in the configuration.
3348 private function checkDbIndices()
3350 $result = $this->db->db_exec("
3351 CREATE INDEX IF NOT EXISTS
3358 } // checkDbIndices()
3361 * retrive F-Spot database version
3363 * this function will return the F-Spot database version number
3364 * It is stored within the sqlite3 database in the table meta
3365 * @return string|null
3367 public function getFspotDBVersion()
3369 if($result = $this->db->db_fetchSingleRow("
3370 SELECT data as version
3373 name LIKE 'F-Spot Database Version'
3375 return $result['version'];
3379 } // getFspotDBVersion()
3382 * parse the provided URI and will returned the requested chunk
3383 * @param string $uri
3384 * @param string $mode
3387 public function parse_uri($uri, $mode)
3389 if(($components = parse_url($uri)) !== false) {
3393 return basename($components['path']);
3396 return dirname($components['path']);
3399 return $components['path'];
3409 * validate config options
3411 * this function checks if all necessary configuration options are
3412 * specified and set.
3415 private function check_config_options()
3417 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3418 $this->_error("Please set \$page_title in phpfspot_cfg");
3420 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3421 $this->_error("Please set \$base_path in phpfspot_cfg");
3423 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3424 $this->_error("Please set \$web_path in phpfspot_cfg");
3426 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3427 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3429 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3430 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3432 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3433 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3435 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3436 $this->_error("Please set \$db_access in phpfspot_cfg");
3438 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3439 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3441 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3442 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3444 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3445 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3447 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3448 $this->_error("Please set \$photo_width in phpfspot_cfg");
3450 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3451 $this->_error("Please set \$mini_width in phpfspot_cfg");
3453 if(!isset($this->cfg->thumbs_per_page))
3454 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3456 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3457 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3459 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3460 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3462 if(!isset($this->cfg->hide_tags))
3463 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3465 if(!isset($this->cfg->theme_name))
3466 $this->_error("Please set \$theme_name in phpfspot_cfg");
3468 if(!isset($this->cfg->logging))
3469 $this->_error("Please set \$logging in phpfspot_cfg");
3471 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3473 if(!isset($this->cfg->log_file))
3474 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3476 if(!is_writeable($this->cfg->log_file))
3477 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3481 /* remove trailing slash, if set */
3482 if($this->cfg->web_path == "/")
3483 $this->cfg->web_path = "";
3484 elseif(preg_match('/\/$/', $this->cfg->web_path))
3485 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3487 return $this->runtime_error;
3489 } // check_config_options()
3492 * cleanup phpfspot own database
3494 * When photos are getting delete from F-Spot, there will remain
3495 * remain some residues in phpfspot own database. This function
3496 * will try to wipe them out.
3498 public function cleanup_phpfspot_db()
3500 $to_delete = Array();
3502 $result = $this->cfg_db->db_query("
3505 ORDER BY img_idx ASC
3508 while($row = $this->cfg_db->db_fetch_object($result)) {
3509 if(!$this->db->db_fetchSingleRow("
3512 WHERE id='". $row['img_idx'] ."'")) {
3514 array_push($to_delete, $row['img_idx'], ',');
3518 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3520 $this->cfg_db->db_exec("
3522 WHERE img_idx IN (". implode($to_delete) .")
3525 } // cleanup_phpfspot_db()
3528 * return first image of the page, the $current photo
3531 * this function is used to find out the first photo of the
3532 * current page, in which the $current photo lies. this is
3533 * used to display the correct photo, when calling showPhotoIndex()
3535 * @param integer $current
3536 * @param integer $max
3539 private function getCurrentPage($current, $max)
3541 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3542 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3543 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3549 } // getCurrentPage()
3554 * this function tries to find out the correct mime-type
3555 * for the provided file.
3556 * @param string $file
3559 public function get_mime_info($file)
3561 $details = getimagesize($file);
3563 /* if getimagesize() returns empty, try at least to find out the
3566 if(empty($details) && function_exists('mime_content_type')) {
3568 // mime_content_type is marked as deprecated in the documentation,
3569 // but is it really necessary to force users to install a PECL
3571 $details['mime'] = mime_content_type($file);
3574 return $details['mime'];
3576 } // get_mime_info()
3579 * return tag-name by tag-idx
3581 * this function returns the tag-name for the requested
3582 * tag specified by tag-idx.
3583 * @param integer $idx
3586 public function get_tag_name($idx)
3588 if($result = $this->db->db_fetchSingleRow("
3592 id LIKE '". $idx ."'")) {
3594 return $result['name'];
3603 * parse user friendly url which got rewritten by the websever
3604 * @param string $request_uri
3607 private function parse_user_friendly_url($request_uri)
3609 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3611 $options = explode('/', $request_uri);
3613 switch($options[1]) {
3615 if(is_numeric($options[2])) {
3616 $this->session_cleanup();
3617 //unset($_SESSION['start_action']);
3618 //unset($_SESSION['selected_tags']);
3619 $_GET['mode'] = 'showp';
3620 return $this->showPhoto($options[2]);
3624 if(is_numeric($options[2])) {
3627 if(isset($options[3]) && is_numeric($options[3]))
3628 $width = $options[3];
3629 if(isset($options[4]) && is_numeric($options[4]))
3630 $version = $options[4];
3631 require_once "phpfspot_img.php";
3632 $img = new PHPFSPOT_IMG;
3633 $img->showImg($options[2], $width, $version);
3638 if(is_numeric($options[2])) {
3639 $this->session_cleanup();
3640 $_GET['tags'] = $options[2];
3641 $_SESSION['selected_tags'] = Array($options[2]);
3642 if(isset($options[3]) && is_numeric($options[3]))
3643 $_SESSION['begin_with'] = $options[3];
3644 return $this->showPhotoIndex();
3650 } // parse_user_friendly_url()
3653 * check if user-friendly-urls are enabled
3655 * this function will return true, if the config option
3656 * $user_friendly_url has been set. Otherwise false.
3659 private function is_user_friendly_url()
3661 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3666 } // is_user_friendly_url()
3671 * this function will cleanup user's session information
3673 private function session_cleanup()
3675 unset($_SESSION['begin_with']);
3676 $this->resetDateSearch();
3677 $this->resetPhotoView();
3678 $this->resetTagSearch();
3679 $this->resetNameSearch();
3680 $this->resetDateSearch();
3683 } // session_cleanup()
3686 * get database version
3688 * this function queries the meta table
3689 * and returns the current database version.
3693 public function get_db_version()
3695 if($row = $this->cfg_db->db_fetchSingleRow("
3700 meta_key LIKE 'phpfspot Database Version'
3703 return $row['meta_value'];
3709 } // get_db_version()
3712 * get photo versions
3714 * this function returns an array of all available
3715 * alterntaive versions of the provided photo id.
3716 * has alternative photo versions available
3721 public function get_photo_versions($idx)
3723 $versions = Array();
3725 $result = $this->db->db_query("
3731 photo_id LIKE '". $idx ."'");
3733 while($row = $this->cfg_db->db_fetch_object($result)) {
3734 array_push($versions, $row['version_id']);
3739 } // get_photo_versions()
3742 * check for invalid version of photo
3744 * this function validates the provided photo-id and version-id
3746 * @param int $photo_idx
3747 * @param int $version_idx
3750 public function is_valid_version($photo_idx, $version_idx)
3752 /* the original version is always valid */
3753 if($version_idx == 0)
3756 if($versions = $this->get_photo_versions($photo_idx)) {
3757 if(in_array($version_idx, $versions))
3763 } // is_valid_version()
3766 * get photo version name
3768 * this function returns the name of the version
3769 * identified by the photo-id and version-id.
3771 * @param int $photo_idx
3772 * @param int $version_idx
3775 public function get_photo_version_name($photo_idx, $version_idx)
3777 if($row = $this->db->db_fetchSingleRow("
3783 photo_id LIKE '". $photo_idx ."'
3785 version_id LIKE '". $version_idx ."'")) {
3787 return $row['name'];
3793 } // get_photo_version_name()
3797 public function is_valid_width($image_width)
3799 if(in_array($image_width,
3800 Array($this->cfg->thumb_width,
3801 $this->cfg->photo_width,
3802 $this->cfg->mini_width,
3809 } // is_valid_width()