diff --git a/includes/config.php b/includes/config.php index fe85029..cf9dfb7 100644 --- a/includes/config.php +++ b/includes/config.php @@ -1,278 +1,281 @@ * @copyright 2010 Sébastien Santoro aka Dereckson * @license http://www.opensource.org/licenses/bsd-license.php BSD * @version 0.1 * @link http://scherzo.dereckson.be/doc/zed * @link http://zed.dereckson.be/ * @filesource */ use Keruald\OmniTools\OS\Environment; use Keruald\Database\Engines\MySQLiEngine; //////////////////////////////////////////////////////////////////////////////// /// /// /// I. SQL configuration /// /// /// //////////////////////////////////////////////////////////////////////////////// //SQL configuration $Config['database']['engine'] = MySQLiEngine::class; $Config['database']['host'] = 'localhost'; $Config['database']['username'] = 'zed'; $Config['database']['password'] = 'zed'; $Config['database']['database'] = 'zed'; $Config['database']['dontThrowExceptions'] = true; //SQL tables $prefix = ''; define('TABLE_API_KEYS', $prefix . 'api_keys'); define('TABLE_COMMENTS', $prefix . 'comments'); define('TABLE_CONTENT_FILES', $prefix . 'content_files'); define('TABLE_CONTENT_LOCATIONS', $prefix . 'content_locations'); define('TABLE_CONTENT_ZONES', $prefix . 'content_zones'); define('TABLE_CONTENT_ZONES_LOCATIONS', $prefix . 'content_zones_locations'); define('TABLE_LOG', $prefix . 'log'); define('TABLE_LOG_SMARTLINE', $prefix . 'log_smartline'); define('TABLE_MESSAGES', $prefix . 'messages'); define('TABLE_MOTD', $prefix . 'motd'); define('TABLE_PAGES', $prefix . 'pages'); define('TABLE_PAGES_EDITS', $prefix . 'pages_edits'); define('TABLE_PERSOS', $prefix . 'persos'); define('TABLE_PERSOS_FLAGS', $prefix . 'persos_flags'); define('TABLE_PERSOS_NOTES', $prefix . 'persos_notes'); define('TABLE_PORTS', $prefix . 'ports'); define('TABLE_PROFILES', $prefix . 'profiles'); define('TABLE_PROFILES_COMMENTS', $prefix . 'profiles_comments'); define('TABLE_PROFILES_PHOTOS', $prefix . 'profiles_photos'); define('TABLE_PROFILES_TAGS', $prefix . 'profiles_tags'); define('TABLE_REGISTRY', $prefix . 'registry'); define('TABLE_REQUESTS', $prefix . 'requests'); define('TABLE_REQUESTS_REPLIES', $prefix . 'requests_replies'); define('TABLE_SESSIONS', $prefix . 'sessions'); define('TABLE_SHIPS', $prefix . 'ships'); define('TABLE_USERS', $prefix . 'users'); define('TABLE_USERS_INVITES', $prefix . 'users_invites'); define('TABLE_USERS_AUTH', $prefix . 'users_auth'); //Geo tables define('TABLE_BODIES', $prefix . 'geo_bodies'); define('TABLE_LOCATIONS', $prefix . 'geo_locations'); //Well... it's a view define('TABLE_PLACES', $prefix . 'geo_places'); //////////////////////////////////////////////////////////////////////////////// /// /// /// II. Site configuration /// /// /// //////////////////////////////////////////////////////////////////////////////// //Default theme $Config['DefaultTheme'] = "Zed"; //Dates date_default_timezone_set("UTC"); //Secret key, used for some verification hashes in URLs or forms. $Config['SecretKey'] = 'Lorem ipsum dolor'; //When reading files, buffer size define('BUFFER_SIZE', 4096); //////////////////////////////////////////////////////////////////////////////// /// /// /// III. Script URLs /// /// /// //////////////////////////////////////////////////////////////////////////////// /* * Apache httpd, without mod_rewrite: * * Subdirectory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/hypership/index.php'; * - $Config['BaseURL'] = '/hypership/index.php'; * * Root directory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/index.php'; * - $Config['BaseURL'] = '/index.php'; * * Apache httpd, with mod_rewrite: * * Subdirectory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/hypership'; * - $Config['BaseURL'] = '/hypership'; * * In .htaccess or your vhost definition: * RewriteEngine On * RewriteBase /hypership/ * RewriteCond %{REQUEST_FILENAME} !-f * RewriteCond %{REQUEST_FILENAME} !-d * RewriteRule . /hypership/index.php [L] * * Root directory: * - $Config['SiteURL'] = 'http://zed.dereckson.be'; * - $Config['BaseURL'] = ''; * * In .htaccess or your vhost definition: * RewriteEngine On * RewriteBase / * RewriteCond %{REQUEST_FILENAME} !-f * RewriteCond %{REQUEST_FILENAME} !-d * RewriteRule . /index.php [L] * * nginx: * * Use same config.php settings than Apache httpd, with mod_rewrite. * * In your server block: * location / { * #Serves static files if they exists, with one month cache * if (-f $request_filename) { * expires 30d; * break; * } * * #Sends all non existing file or directory requests to index.php * if (!-e request_filename) { * rewrite ^(.+)$ /index.php last; * #Or if you use a subdirectory: * #rewrite ^(.+)$ /hypership/index.php last; * } * } * * location ~ \.php$ { * #Your instructions to pass query to your FastCGI process, like: * fastcgi_pass 127.0.0.1:9000; * fastcgi_param SCRIPT_FILENAME /var/www/zed$fastcgi_script_name; * include fastcgi_params; * } * * * If you don't want to specify the server domain, you can use get_server_url: * $Config['SiteURL'] = get_server_url() . '/hypership'; * $Config['SiteURL'] = get_server_url(); * * * * !!! No trailing slash !!! * */ $Config['SiteURL'] = get_server_url(); $Config['BaseURL'] = ''; //AJAX callbacks URL $Config['DoURL'] = $Config['SiteURL'] . "/do.php"; //////////////////////////////////////////////////////////////////////////////// /// /// /// IV. Static content /// /// /// //////////////////////////////////////////////////////////////////////////////// //Where the static content is located? //Static content = 4 directories: js, css, img and content //On default installation, those directories are at site root. //To improve site performance, you can use a CDN for that. // //Recommended setting: $Config['StaticContentURL'] = $Config['SiteURL']; //Or if Zed is the site root: $Config['StaticContentURL'] = ''; //With CoralCDN: $Config['StaticContentURL'] = . '.nyud.net'; // $Config['StaticContentURL'] = ''; //$Config['StaticContentURL'] = get_server_url() . '.nyud.net'; //Site content define('CONTENT_DIR', Environment::getOr('CONTENT_DIR', 'content')); +define('CONTENT_USERS_DIR', Environment::getOr( + 'CONTENT_USERS_DIR', CONTENT_DIR . '/users' +)); //Scenes define('SCENE_DIR', CONTENT_DIR . "/scenes"); define('SCENE_URL', $Config['StaticContentURL'] . '/content/scenes'); //Stories define('STORIES_DIR', CONTENT_DIR . "/stories"); //Profile's photos define('PHOTOS_DIR', CONTENT_DIR . "/users/_photos"); define('PHOTOS_URL', $Config['StaticContentURL'] . '/content/users/_photos'); //ImageMagick paths //Be careful on Windows platform convert could match the NTFS convert command. $Config['ImageMagick']['convert'] = 'convert'; $Config['ImageMagick']['mogrify'] = 'mogrify'; $Config['ImageMagick']['composite'] = 'composite'; $Config['ImageMagick']['identify'] = 'identify'; //////////////////////////////////////////////////////////////////////////////// /// /// /// V. Caching /// /// /// //////////////////////////////////////////////////////////////////////////////// /* * Some data (Smarty, OpenID and sessions) are cached in the cache directory. * * Security tip: you can move this cache directory outside the webserver tree. */ define('CACHE_DIR', Environment::getOr('CACHE_DIR', 'cache')); /* * Furthermore, you can also enable a cache engine, like memcached, to store * data from heavy database queries, or frequently accessed stuff. * * To use memcached: * - $Config['cache']['engine'] = 'memcached'; * - $Config['cache']['server'] = 'localhost'; * - $Config['cache']['port'] = 11211; * * To disable cache: * - $Config['cache']['engine'] = 'void'; * (or don't write nothing at all) */ $Config['cache']['engine'] = 'void'; //////////////////////////////////////////////////////////////////////////////// /// /// /// VI. Sessions and authentication code /// /// /// //////////////////////////////////////////////////////////////////////////////// //If you want to use a common table of sessions / user handling //with several websites, specify a different resource id for each site. $Config['ResourceID'] = 21; //Enable OpenID authentication //$Config['OpenID'] = true; //Enable YubiKey authentication //API 12940 //For YubiCloud API key - create yours at https://upgrade.yubico.com/getapikey/ //$Config['YubiCloud']['ClientID'] = 12345; //$Config['YubiCloud']['SecretKey'] = 'Base64SecretKeyHere'; //PHP variables ini_set('session.save_path', CACHE_DIR . '/sessions'); //4 days, for week-end story pause and continue url ini_set('session.gc_maxlifetime', 345600); //////////////////////////////////////////////////////////////////////////////// /// /// /// VII. Builder /// /// /// //////////////////////////////////////////////////////////////////////////////// //Zed can invoke a slightly modified version of HOTGLUE to build zones. $Config['builder']['hotglue']['enable'] = true; $Config['builder']['hotglue']['URL'] = '/apps/hotglue/index.php'; diff --git a/includes/objects/content.php b/includes/objects/content.php index 79e458b..06602d0 100644 --- a/includes/objects/content.php +++ b/includes/objects/content.php @@ -1,314 +1,343 @@ * @copyright 2010 Sébastien Santoro aka Dereckson * @license http://www.opensource.org/licenses/bsd-license.php BSD * @version 0.1 * @link http://scherzo.dereckson.be/doc/zed * @link http://zed.dereckson.be/ * @filesource * * @deprecated */ /** * Content class * * This class maps the content view. * * This view shows the content_files and content_locations tables. * * This class also provides helper methods, to handle files, generate thumbnails * or get local content from a specific location. * * [DESIGN BY CONTRACT] This class works only with the following assertions: * i. Each content have EXACTLY ONE location * ii. Location fields will not be modified * * If a content have more than one location, only the first occurrence in * content_locations table will be considered. * * If a content have no location, it will be ignored. * * If you edit content location, then call saveToDatabase, you will create * a new location but future instances will contain first not deleted location. * * @todo remove dbc temporary limitations (cf. /do.php upload_content and infra) * @todo create a class ContentLocation and move location fields there * @todo validate SQL schema and add in config.php TABLE_CONTENT tables * * @deprecated */ class Content { /* ------------------------------------------------------------- Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ public $id; public $path; public $user_id; public $perso_id; public $title; public $location_global = null; public $location_local = null; public $location_k = null; public $perso_name; public $perso_nickname; /* ------------------------------------------------------------- Constructor, __toString - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** * Initializes a new Content instance * * @param int $id the primary key */ function __construct ($id = null) { if ($id) { $this->id = $id; $this->load_from_database(); } } /** * Returns a string representation of current Content instance * * @return string the content title or path if title is blank. */ function __toString () { return $this->title ?: $this->path; } /* ------------------------------------------------------------- Load/save class - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** * Loads the object Content (ie fill the properties) from the $_POST array * * @param boolean $allowSensibleFields if false, allow only location_local, location_k and title to be defined ; otherwise, allow all fields. */ function load_from_form ($allowSensibleFields = false) { if (array_key_exists('title', $_POST)) { $this->title = $_POST['title']; } if (array_key_exists('location_local', $_POST)) { $this->location_local = $_POST['location_local']; } if (array_key_exists('location_k', $_POST)) { $this->location_k = $_POST['location_k']; } if ($allowSensibleFields) { if (array_key_exists('path', $_POST)) { $this->path = $_POST['path']; } if (array_key_exists('user_id', $_POST)) { $this->user_id = $_POST['user_id']; } if (array_key_exists('perso_id', $_POST)) { $this->perso_id = $_POST['perso_id']; } if (array_key_exists('location_global', $_POST)) { $this->location_global = $_POST['location_global']; } } } /** * Loads the object Content (ie fill the properties) from the database */ function load_from_database () { global $db; $id = $db->escape($this->id); $sql = "SELECT * FROM content WHERE content_id = '" . $id . "'"; if ( !($result = $db->query($sql)) ) { message_die(SQL_ERROR, "Unable to query content", '', __LINE__, __FILE__, $sql); } if (!$row = $db->fetchRow($result)) { $this->lastError = "Content unknown: " . $this->id; return false; } $this->load_from_row($row); return true; } /** * Loads the object from row */ function load_from_row ($row) { $this->id = $row['content_id']; $this->path = $row['content_path']; $this->user_id = $row['user_id']; $this->perso_id = $row['perso_id']; $this->title = $row['content_title']; $this->location_global = $row['location_global']; $this->location_local = $row['location_local']; $this->location_k = $row['location_k']; if (array_key_exists('perso_name', $row)) { $this->perso_name = $row['perso_name']; } if (array_key_exists('perso_nickname', $row)) { $this->perso_nickname = $row['perso_nickname']; } } /** * Saves to database */ function save_to_database () { global $db; $id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL'; $path = $db->escape($this->path); $user_id = $db->escape($this->user_id); $perso_id = $db->escape($this->perso_id); $title = $db->escape($this->title); $location_global = ($this->location_global !== null) ? "'" . $db->escape($this->location_global) . "'" : 'NULL'; $location_local = ($this->location_local !== null) ? "'" . $db->escape($this->location_local) . "'" : 'NULL'; $location_k = ($this->location_k !== null) ? "'" . $db->escape($this->location_k) . "'" : 'NULL'; //Updates or inserts $sql = "REPLACE INTO content_files (`content_id`, `content_path`, `user_id`, `perso_id`, `content_title`) VALUES ($id, '$path', '$user_id', '$perso_id', '$title')"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't save content", '', __LINE__, __FILE__, $sql); } if (!$this->id) { //Gets new record id value $this->id = $db->nextId(); } //Saves location $id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL'; $sql = "REPLACE INTO content_locations (location_global, location_local, location_k, content_id) VALUES ($location_global, $location_local, $location_k, $id)"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't save content location", '', __LINE__, __FILE__, $sql); } } /* ------------------------------------------------------------- File handling helper methods - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** * Determines if the extension is valid * * @param string $ext The extension (without dot) * @return boolean true if this extension is valid ; otherwise, false. */ public static function is_valid_extension ($ext) { return match (strtolower($ext)) { 'jpg', 'gif', 'png', 'bmp', 'xbm' => true, default => false, }; } /** * Creates a directory * * @param string $dir the directory to create */ function create_directory ($directory) { if (!file_exists($directory)) { @mkdir($directory); //Creates new directory, chmod 777 } } /** * Handles uploaded file * * @return bool true if the file have been handled */ function handle_uploaded_file ($fileArray) { - if (count($fileArray) && $fileArray['error'] == 0) { - $this->create_directory("content/users/$this->user_id"); - $this->path = "content/users/$this->user_id/$fileArray[name]"; - if (!self::is_valid_extension(get_extension($fileArray[name]))) { - return false; - } - if (move_uploaded_file($fileArray['tmp_name'], $this->path)) { - return true; - } else { - $this->path = null; - return false; - } - } else { + if (!is_array($fileArray) || $fileArray['error'] != 0) { + return false; + } + + $this->path = $this->getLogicalPath($fileArray["name"]); + + if (!self::is_valid_extension(get_extension($this->path))) { return false; } + + $storagePath = $this->getStoragePath(); + if (move_uploaded_file($fileArray['tmp_name'], $storagePath)) { + return true; + } + + return false; + } + + /** + * @return string The storage path for this file on disk + */ + function getStoragePath () : string { + $storageDir = CONTENT_USERS_DIR . "/" . $this->user_id; + $this->create_directory($storageDir); + + $logicalDir = 'content/users/' . $this->user_id; + if (!str_starts_with($this->path, $logicalDir)) { + throw new InvalidArgumentException(<<path', but should start with '$logicalDir'. +EOF +); + } + + return str_replace($logicalDir, $storageDir, $this->path); + } + + /** + * @param string $name The final destination filename + * + * @return string The URL to use to serve the file. + */ + function getLogicalPath (string $name) : string { + return "content/users/$this->user_id/$name"; } /** * Generates a thumbnail using ImageMagick binary * * @return boolean true if the thumbnail command returns 0 as program exit code ; otherwise, false */ function generate_thumbnail () { global $Config; //Builds thumbnail filename - $sourceFile = $this->path; + $sourceFile = $this->getStoragePath(); $pos = strrpos($this->path, '.'); $thumbnailFile = substr($sourceFile, 0, $pos) . 'Square' . substr($sourceFile, $pos); //Executes imagemagick command $command = $Config['ImageMagick']['convert'] . " \"$sourceFile\" -resize 162x162 \"$thumbnailFile\""; @system($command, $code); //Returns true if the command have exited with errorcode 0 (= ok) return ($code == 0); } /* ------------------------------------------------------------- Gets content - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /** * Gets content at specified location * * @param string $location_global global content location * @param string $location_local local content location * @return Array array of Content instances */ static function get_local_content ($location_global, $location_local) { global $db; //Get contents at this location $location_global = $db->escape($location_global); $location_local = $db->escape($location_local); $sql = "SELECT c.*, p.perso_nickname, p.perso_name FROM content c, persos p WHERE c.location_global = '$location_global' AND c.location_local = '$location_local' AND p.perso_id = c.perso_id ORDER BY location_k ASC"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't get content", '', __LINE__, __FILE__, $sql); } //Fills content array $contents = []; while ($row = $db->fetchRow($result)) { $content = new Content(); $content->load_from_row($row); $contents[] = $content; } return $contents; } }