/** * This function can be used to safely write a file to disk. * * For writing the file, a swap file is used during the write phase. * Only if writing the swap file fully succeeded without running * into errors, the swap file is moved to its final location. * This way, we cannot get broken data because of scripts that crash * during writing or because of disks that are full. * * @param string $file * The name of the file to write to. * * @param string $data * The data to put in the file. * * @return boolean * TRUE in case the file was written successfully to disk. * FALSE if writing the file failed. The function * {@link phorum_api_error_message()} can be used to retrieve * information about the error that occurred. */ function phorum_api_write_file($file, $data) { global $PHORUM; // Reset error storage. $PHORUM['API']['errno'] = NULL; $PHORUM['API']['error'] = NULL; ini_set('track_errors', 1); // Generate the swap file name. $stamp = preg_replace('/\\D/', '', microtime()); $swpfile = $file . '.swp' . $stamp; // Open the swap file. $fp = @fopen($swpfile, 'w'); if (!$fp) { @unlink($swpfile); return phorum_api_error(PHORUM_ERRNO_ERROR, "Cannot create swap file \"{$swpfile}\": {$php_errormsg}"); } // Write file data to disk. @fputs($fp, $data); // Close the swap file. if (!@fclose($fp)) { @unlink($swpfile); return phorum_api_error(PHORUM_ERRNO_ERROR, "Error on closing swap file \"{$swpfile}\": disk full?"); } // A special check on the created outputfile. We have seen strange // things happen on Windows2000 where the webserver could not read // the file it just had written :-/ if (!($fp = @fopen($swpfile, 'r'))) { @unlink($swpfile); return phorum_api_error(PHORUM_ERRNO_ERROR, "Cannot read swap file \"{$swpfile}\", although it was just " . "written to disk. This is probably due to a problem with " . "the file permissions for the storage directory."); } @fclose($fp); // Move the swap file to its final location. if (!@rename($swpfile, $file)) { @unlink($swpfile); return phorum_api_error(PHORUM_ERRNO_ERROR, "Cannot move swap file \"{$swpfile}\": {$php_errormsg}"); } return TRUE; }
/** * This method is the central method for handling database * interaction. The method can be used for setting up a database * connection, for running a SQL query and for returning query rows. * Which of these actions the method will handle and what the method * return data will be, is determined by the $return method parameter. * * @param $return - What to return. Options are the following constants: * DB_RETURN_CONN a db connection handle * DB_RETURN_QUOTED a quoted parameter * DB_RETURN_RES result resource handle * DB_RETURN_ROW single row as array * DB_RETURN_ROWS all rows as arrays * DB_RETURN_ASSOC single row as associative array * DB_RETURN_ASSOCS all rows as associative arrays * DB_RETURN_VALUE single row, single column * DB_RETURN_ROWCOUNT number of selected rows * DB_RETURN_NEWID new row id for insert query * DB_RETURN_ERROR an error message if the query * failed or NULL if there was * no error * DB_CLOSE_CONN close the connection, no * return data * * @param $sql - The SQL query to run or the parameter to quote if * DB_RETURN_QUOTED is used. * * @param $keyfield - When returning an array of rows, the indexes are * numerical by default (0, 1, 2, etc.). However, if * the $keyfield parameter is set, then from each * row the $keyfield index is taken as the key for the * return array. This way, you can create a direct * mapping between some id field and its row in the * return data. Mind that there is no error checking * at all, so you have to make sure that you provide * a valid $keyfield here! * * @param $flags - Special flags for modifying the method's behavior. * These flags can be OR'ed if multiple flags are needed. * DB_NOCONNECTOK Failure to connect is not fatal * but lets the call return FALSE * (useful in combination with * DB_RETURN_CONN). * DB_MISSINGTABLEOK Missing table errors not fatal. * DB_DUPFIELDNAMEOK Duplicate field errors not fatal. * DB_DUPKEYNAMEOK Duplicate key name errors * not fatal. * DB_DUPKEYOK Duplicate key errors not fatal. * * @param $limit - The maximum number of rows to return. * @param $offset - The number of rows to skip in the result set, * before returning rows to the caller. * * @return $res - The result of the query, based on the $return * parameter. */ public function interact($return, $sql = NULL, $keyfield = NULL, $flags = 0, $limit = 0, $offset = 0) { static $conn; static $querytrack; // Close the database connection. if ($return == DB_CLOSE_CONN) { if (!empty($conn)) { mysqli_close($conn); $conn = null; } return; } $debug = empty($GLOBALS['PHORUM']['DBCONFIG']['dbdebug']) ? 0 : $GLOBALS['PHORUM']['DBCONFIG']['dbdebug']; if (!empty($debug)) { if (!isset($querytrack) || !is_array($querytrack)) { $querytrack = array('count' => 0, 'time' => 0, 'queries' => array()); } } // Setup a database connection if no database connection is // available yet. if (empty($conn)) { global $PHORUM; // we suppress errors from the mysqli_connect command as errors // are catched differently. $conn = mysqli_connect($PHORUM['DBCONFIG']['server'], $PHORUM['DBCONFIG']['user'], $PHORUM['DBCONFIG']['password'], $PHORUM['DBCONFIG']['name'], $PHORUM['DBCONFIG']['port'], $PHORUM['DBCONFIG']['socket']); if ($conn === FALSE) { if ($flags & DB_NOCONNECTOK) { return FALSE; } phorum_api_error(PHORUM_ERRNO_DATABASE, 'Failed to connect to the database.'); exit; } if (!empty($PHORUM['DBCONFIG']['charset'])) { $set_names = "SET NAMES '{$PHORUM['DBCONFIG']['charset']}'"; mysqli_query($conn, $set_names); if ($debug) { $querytrack['count'] += 2; if ($debug > 1) { $querytrack['queries'][] = array('number' => '001', 'query' => htmlspecialchars($set_names), 'raw_query' => $set_names, 'time' => '0.000'); } } } // putting this here for testing mainly // All of Phorum should work in strict mode if (!empty($PHORUM["DBCONFIG"]["strict_mode"])) { mysqli_query($conn, "SET SESSION sql_mode='STRICT_ALL_TABLES'"); } } // RETURN: quoted parameter. if ($return === DB_RETURN_QUOTED) { return mysqli_real_escape_string($conn, $sql); } // RETURN: database connection handle if ($return === DB_RETURN_CONN) { return $conn; } // By now, we really need a SQL query. if ($sql === NULL) { trigger_error(__METHOD__ . ': Internal error: ' . 'missing sql query statement!', E_USER_ERROR); } // Apply limit and offset to the query. settype($limit, 'int'); settype($offset, 'int'); if ($limit > 0) { $sql .= " LIMIT {$limit}"; } if ($offset > 0) { $sql .= " OFFSET {$offset}"; } // Execute the SQL query. $tries = 0; $res = FALSE; while ($res === FALSE && $tries < 3) { // Time the query for debug level 2 and up. if ($debug > 1) { $t1 = microtime(TRUE); } // For queries where we are going to retrieve multiple rows, we // use an unuffered query result. if ($return === DB_RETURN_ASSOCS || $return === DB_RETURN_ROWS) { $res = FALSE; if (mysqli_real_query($conn, $sql) !== FALSE) { $res = mysqli_use_result($conn); } } else { $res = mysqli_query($conn, $sql); } if ($debug) { $querytrack['count']++; if ($debug > 1) { $t2 = microtime(TRUE); $time = sprintf("%0.3f", $t2 - $t1); $querytrack['time'] += $time; $querytrack['queries'][] = array('number' => sprintf("%03d", $querytrack['count']), 'query' => htmlspecialchars($sql), 'raw_query' => $sql, 'time' => $time); } $GLOBALS['PHORUM']['DATA']['DBDEBUG'] = $querytrack; } // Handle errors. if ($res === FALSE) { $errno = mysqli_errno($conn); // if we have an error due to a transactional storage engine, // retry the query for those errors up to 2 more times if ($tries < 3 && ($errno == 1422 || $errno == 1213 || $errno == 1205)) { // 1205 Lock wait timeout $tries++; } else { // See if the $flags tell us to ignore the error. $ignore_error = FALSE; switch ($errno) { // Table does not exist. case 1146: if ($flags & DB_MISSINGTABLEOK) { $ignore_error = TRUE; } break; // Table already exists. // Table already exists. case 1050: if ($flags & DB_TABLEEXISTSOK) { $ignore_error = TRUE; } break; // Duplicate column name. // Duplicate column name. case 1060: if ($flags & DB_DUPFIELDNAMEOK) { $ignore_error = TRUE; } break; // Duplicate key name. // Duplicate key name. case 1061: if ($flags & DB_DUPKEYNAMEOK) { $ignore_error = TRUE; } break; // Duplicate entry for key. // Duplicate entry for key. case 1062: // For MySQL server versions 5.1.15 up to 5.1.20. See // bug #28842 (http://bugs.mysql.com/bug.php?id=28842) // For MySQL server versions 5.1.15 up to 5.1.20. See // bug #28842 (http://bugs.mysql.com/bug.php?id=28842) case 1582: if ($flags & DB_DUPKEYOK) { $ignore_error = TRUE; } break; } // Handle this error if it's not to be ignored. if (!$ignore_error) { $err = mysqli_error($conn); // RETURN: error message. if ($return === DB_RETURN_ERROR) { return $err; } // Trigger an error. phorum_api_error(PHORUM_ERRNO_DATABASE, "{$err} ({$errno}): {$sql}"); exit; } // break while break; } } } // RETURN: NULL (no error). if ($return === DB_RETURN_ERROR) { return NULL; } // RETURN: query resource handle if ($return === DB_RETURN_RES) { return $res; } // RETURN: number of rows if ($return === DB_RETURN_ROWCOUNT) { return $res ? mysqli_num_rows($res) : 0; } // RETURN: array rows or single value if ($return === DB_RETURN_ROW || $return === DB_RETURN_ROWS || $return === DB_RETURN_VALUE) { // Keyfields are only valid for DB_RETURN_ROWS. if ($return !== DB_RETURN_ROWS) { $keyfield = NULL; } $rows = array(); if ($res) { while ($row = mysqli_fetch_row($res)) { if ($keyfield === NULL) { $rows[] = $row; } else { $rows[$row[$keyfield]] = $row; } } } // Return all rows. if ($return === DB_RETURN_ROWS) { /* Might be FALSE in case of ignored errors. */ if (!is_bool($res)) { mysqli_free_result($res); } return $rows; } // Return a single row. if ($return === DB_RETURN_ROW) { if (count($rows) == 0) { return NULL; } else { return $rows[0]; } } // Return a single value. if (count($rows) == 0) { return NULL; } else { return $rows[0][0]; } } // RETURN: associative array rows if ($return === DB_RETURN_ASSOC || $return === DB_RETURN_ASSOCS) { // Keyfields are only valid for DB_RETURN_ASSOCS. if ($return !== DB_RETURN_ASSOCS) { $keyfield = NULL; } $rows = array(); if ($res) { while ($row = mysqli_fetch_assoc($res)) { if ($keyfield === NULL) { $rows[] = $row; } else { $rows[$row[$keyfield]] = $row; } } } // Return all rows. if ($return === DB_RETURN_ASSOCS) { /* Might be FALSE in case of ignored errors. */ if (!is_bool($res)) { mysqli_free_result($res); } return $rows; } // Return a single row. if ($return === DB_RETURN_ASSOC) { if (count($rows) == 0) { return NULL; } else { return $rows[0]; } } } // RETURN: new id after inserting a new record if ($return === DB_RETURN_NEWID) { return mysqli_insert_id($conn); } trigger_error(__METHOD__ . ': Internal error: ' . 'illegal return type specified!', E_USER_ERROR); }
/** * Write a new message to the event logging table. * * This function will automatically fill the log information with * user_id, ip, hostname (if hostname resolving is enabled for the log module) * datestamp and vroot information. Other log info can be provided throught * the $loginfo argument. * * @param $loginfo - An array containing logging information. This array * can contain the following fields: * * message A short log message on one line. * details Details about the log message, which can * span multiple lines. This could for example * be used for providing a debug backtrace. * source The source of the log message. This is a * free 32 char text field, which can be used * to specifiy what part of Phorum generated the * log message (e.g. "mod_smileys"). If no * source is provided, the "phorum_page" * constant will be used instead. * category A high level category for the message. * Options for this field are: * EVENTLOG_CAT_APPLICATION (default) * EVENTLOG_CAT_DATABASE * EVENTLOG_CAT_SECURITY * EVENTLOG_CAT_SYSTEM * EVENTLOG_CAT_MODULE * loglevel This indicates the severety of the message. * Options for this field are: * EVENTLOG_LVL_DEBUG * Messages that are used by programmers * for tracking low level Phorum operation. * EVENTLOG_LVL_INFO * Messages that provide logging for events * that occur during normal operation. These * messages could be harvested for usage * reporting and other types of reports. * EVENTLOG_LVL_WARNING * Warning messages do not indicate errors, * but they do report events that are not * considered to belong to normal operation * (e.g. a user which enters a wrong password * or a duplicate message being posted). * EVENTLOG_LVL_ERROR * Error messages indicate non urgent failures * in Phorum operation. These should be * relayed to administrators and/or developers * to have them solved. * EVENTLOG_LVL_ALERT * Alert messages indicate errors which should * be corrected as soon as possible (e.g. loss * of network connectivity or a full disk). * These should be relayed to the system * administrator). * * vroot vroot for which a message is generated. * forum_id forum_id for which a message is generated. * thread_id thread_id for which a message is generated * message_id message_id for which a message is generated * * user_id Filled automatically, but can be overridden * ip Filled automatically, but can be overridden * hostname Filled automatically, but can be overridden * datestamp Filled automatically, but can be overridden */ function event_logging_writelog($loginfo) { global $PHORUM; // Check the minimum log level. Only write to the log if the // log level of the event is at or above the configured minimum. $lvl = isset($loginfo["loglevel"]) ? (int) $loginfo["loglevel"] : 0; if ($lvl < $PHORUM["mod_event_logging"]["min_log_level"]) { return; } $loginfo = phorum_api_hook("event_logging_writelog", $loginfo); // The record that we will insert in the database. $record = array(); // Handle messages that exceed the maximum message length. if ($loginfo["message"] !== NULL && strlen($loginfo["message"]) > 255) { if (!isset($loginfo["details"])) { $loginfo["details"] = ''; } $loginfo["details"] = "Message:\n\n{$loginfo["message"]}\n\n" . $loginfo["details"]; $loginfo["message"] = substr($loginfo["message"], 0, 100) . "... (see event details for the full message)\n"; } elseif (isset($loginfo["details"])) { $loginfo["details"] = "Message:\n\n{$loginfo["message"]}\n\n" . $loginfo["details"]; } // Add the fields from the $loginfo argument. foreach ($loginfo as $key => $val) { switch ($key) { case "datestamp": case "user_id": case "vroot": case "forum_id": case "thread_id": case "message_id": case "category": case "loglevel": settype($val, "int"); $record[$key] = $val; break; case "message": case "details": case "source": case "ip": case "hostname": $record[$key] = "'" . $PHORUM['DB']->interact(DB_RETURN_QUOTED, $val) . "'"; break; default: phorum_api_error(PHORUM_ERRNO_DATABASE, "event_logging_log(): Illegal key " . "field \"{$key}\" in the \$loginfo argument"); } } // Add the message source. $from_module = FALSE; if (!isset($record["source"])) { list($source, $from_module) = event_logging_find_source(1); $record["source"] = "'" . $PHORUM['DB']->interact(DB_RETURN_QUOTED, $source) . "'"; } // Add the category. if (!isset($record["category"])) { $record["category"] = $from_module ? EVENTLOG_CAT_MODULE : EVENTLOG_CAT_APPLICATION; } // Add the datestamp. if (!isset($record["datestamp"])) { $record["datestamp"] = time(); } // Add the IP address for the current visitor. if (!isset($record["ip"]) && isset($_SERVER["REMOTE_ADDR"])) { $ip = $_SERVER["REMOTE_ADDR"]; $record["ip"] = "'" . $PHORUM['DB']->interact(DB_RETURN_QUOTED, $ip) . "'"; } // Add the hostname for the current visitor. if (!isset($record["hostname"]) && isset($record["ip"]) && $PHORUM["mod_event_logging"]["resolve_hostnames"]) { $hostname = gethostbyaddr($ip); if ($hostname != $ip) { $record["hostname"] = "'" . $PHORUM['DB']->interact(DB_RETURN_QUOTED, $hostname) . "'"; } } // Add the user_id in case the visitor is an authenticated user. if (!isset($record["user_id"]) && isset($PHORUM["user"]["user_id"]) && $PHORUM["user"]["user_id"]) { $record["user_id"] = $PHORUM["user"]["user_id"]; } // Add the current vroot. if (!isset($record["vroot"]) && isset($PHORUM["vroot"])) { $record["vroot"] = $PHORUM["vroot"]; } // Insert the logging record in the database. $PHORUM['DB']->interact(DB_RETURN_RES, "INSERT INTO {$PHORUM["event_logging_table"]}\n (" . implode(', ', array_keys($record)) . ")\n VALUES (" . implode(', ', $record) . ")", NULL, DB_MASTERQUERY); }
/** * This method is the central method for handling database * interaction. The method can be used for setting up a database * connection, for running a SQL query and for returning query rows. * Which of these actions the method will handle and what the method * return data will be, is determined by the $return method parameter. * * @param $return - What to return. Options are the following constants: * DB_RETURN_CONN a db connection handle * DB_RETURN_QUOTED a quoted parameter * DB_RETURN_RES result resource handle * DB_RETURN_ROW single row as array * DB_RETURN_ROWS all rows as arrays * DB_RETURN_ASSOC single row as associative array * DB_RETURN_ASSOCS all rows as associative arrays * DB_RETURN_VALUE single row, single column * DB_RETURN_ROWCOUNT number of selected rows * DB_RETURN_NEWID new row id for insert query * DB_RETURN_ERROR an error message if the query * failed or NULL if there was * no error * DB_CLOSE_CONN close the connection, no * return data * * @param $sql - The SQL query to run or the parameter to quote if * DB_RETURN_QUOTED is used. * * @param $keyfield - When returning an array of rows, the indexes are * numerical by default (0, 1, 2, etc.). However, if * the $keyfield parameter is set, then from each * row the $keyfield index is taken as the key for the * return array. This way, you can create a direct * mapping between some id field and its row in the * return data. Mind that there is no error checking * at all, so you have to make sure that you provide * a valid $keyfield here! * * @param $flags - Special flags for modifying the method's behavior. * These flags can be OR'ed if multiple flags are needed. * DB_NOCONNECTOK Failure to connect is not fatal * but lets the call return FALSE * (useful in combination with * DB_RETURN_CONN). * DB_MISSINGTABLEOK Missing table errors not fatal. * DB_DUPFIELDNAMEOK Duplicate field errors not fatal. * DB_DUPKEYNAMEOK Duplicate key name errors * not fatal. * DB_DUPKEYOK Duplicate key errors not fatal. * * @param $limit - The maximum number of rows to return. * @param $offset - The number of rows to skip in the result set, * before returning rows to the caller. * * @return $res - The result of the query, based on the $return * parameter. */ public function interact($return, $sql = NULL, $keyfield = NULL, $flags = 0, $limit = 0, $offset = 0) { global $PHORUM; static $conn; // Close the database connection. if ($return == DB_CLOSE_CONN) { if (!empty($conn)) { pg_close($conn); $conn = null; } return; } // Setup a database connection if no database connection is // available yet. if (empty($conn)) { // Format the connection string for pg_connect. $conn_string = ''; if ($PHORUM['DBCONFIG']['server']) { $conn_string .= ' host=' . $PHORUM['DBCONFIG']['server']; } if ($PHORUM['DBCONFIG']['user']) { $conn_string .= ' user='******'DBCONFIG']['user']; } if ($PHORUM['DBCONFIG']['password']) { $conn_string .= ' password='******'DBCONFIG']['password']; } if ($PHORUM['DBCONFIG']['name']) { $conn_string .= ' dbname=' . $PHORUM['DBCONFIG']['name']; } // Try to setup a connection to the database. $conn = @pg_connect($conn_string, PGSQL_CONNECT_FORCE_NEW); if ($conn === FALSE) { if ($flags & DB_NOCONNECTOK) { return FALSE; } phorum_api_error(PHORUM_ERRNO_DATABASE, 'Failed to connect to the database.'); exit; } if (!empty($PHORUM['DBCONFIG']['charset'])) { $charset = $PHORUM['DBCONFIG']['charset']; pg_query($conn, "SET CLIENT_ENCODING TO '{$charset}'"); } } // RETURN: quoted parameter. if ($return === DB_RETURN_QUOTED) { return pg_escape_string($conn, $sql); } // RETURN: database connection handle. if ($return === DB_RETURN_CONN) { return $conn; } // By now, we really need a SQL query. if ($sql === NULL) { trigger_error(__METHOD__ . ': Internal error: ' . 'missing sql query statement!', E_USER_ERROR); } // Apply limit and offset to the query. settype($limit, 'int'); settype($offset, 'int'); if ($limit > 0) { $sql .= " LIMIT {$limit}"; } if ($offset > 0) { $sql .= " OFFSET {$offset}"; } // Execute the SQL query. if (!@pg_send_query($conn, $sql)) { trigger_error(__METHOD__ . ': Internal error: ' . 'pg_send_query() failed!', E_USER_ERROR); } // Check if an error occurred. $res = pg_get_result($conn); $errno = pg_result_error_field($res, PGSQL_DIAG_SQLSTATE); if ($errno != 0) { // See if the $flags tell us to ignore the error. $ignore_error = FALSE; switch ($errno) { // Table does not exist. case '42P01': if ($flags & DB_MISSINGTABLEOK) { $ignore_error = TRUE; } break; // Table already exists or duplicate key name. // These two cases use the same error code. // Table already exists or duplicate key name. // These two cases use the same error code. case '42P07': if ($flags & DB_TABLEEXISTSOK) { $ignore_error = TRUE; } if ($flags & DB_DUPKEYNAMEOK) { $ignore_error = TRUE; } break; // Duplicate column name. // Duplicate column name. case '42701': if ($flags & DB_DUPFIELDNAMEOK) { $ignore_error = TRUE; } break; // Duplicate entry for key. // Duplicate entry for key. case '23505': if ($flags & DB_DUPKEYOK) { $ignore_error = TRUE; # the code expects res to have no value upon error $res = NULL; } break; } // Handle this error if it's not to be ignored. if (!$ignore_error) { $errmsg = pg_result_error($res); // RETURN: error message if ($return === DB_RETURN_ERROR) { return $errmsg; } // Trigger an error. phorum_api_error(PHORUM_ERRNO_DATABASE, "{$errmsg} ({$errno}): {$sql}"); exit; } } // RETURN: NULL (no error) if ($return === DB_RETURN_ERROR) { return NULL; } // RETURN: query resource handle if ($return === DB_RETURN_RES) { return $res; } // RETURN: number of rows if ($return === DB_RETURN_ROWCOUNT) { return $res ? pg_num_rows($res) : 0; } // RETURN: array rows or single value if ($return === DB_RETURN_ROW || $return === DB_RETURN_ROWS || $return === DB_RETURN_VALUE) { // Keyfields are only valid for DB_RETURN_ROWS. if ($return !== DB_RETURN_ROWS) { $keyfield = NULL; } $rows = array(); if ($res) { while ($row = pg_fetch_row($res)) { if ($keyfield === NULL) { $rows[] = $row; } else { $rows[$row[$keyfield]] = $row; } } } // Return all rows. if ($return === DB_RETURN_ROWS) { return $rows; } // Return a single row. if ($return === DB_RETURN_ROW) { if (count($rows) == 0) { return NULL; } else { return $rows[0]; } } // Return a single value. if (count($rows) == 0) { return NULL; } else { return $rows[0][0]; } } // RETURN: associative array rows if ($return === DB_RETURN_ASSOC || $return === DB_RETURN_ASSOCS) { // Keyfields are only valid for DB_RETURN_ASSOCS. if ($return !== DB_RETURN_ASSOCS) { $keyfield = NULL; } $rows = array(); if ($res) { while ($row = pg_fetch_assoc($res)) { if ($keyfield === NULL) { $rows[] = $row; } else { $rows[$row[$keyfield]] = $row; } } } // Return all rows. if ($return === DB_RETURN_ASSOCS) { return $rows; } // Return a single row. if ($return === DB_RETURN_ASSOC) { if (count($rows) == 0) { return NULL; } else { return $rows[0]; } } } // RETURN: new id after inserting a new record if ($return === DB_RETURN_NEWID) { $res = pg_exec($conn, "SELECT lastval()"); if ($res === FALSE) { phorum_api_error(PHORUM_ERRNO_DATABASE, 'Failed to get a lastval() result.'); } $row = pg_fetch_row($res); if ($row === FALSE) { phorum_api_error(PHORUM_ERRNO_DATABASE, 'No rows returned from LASTVAL().'); } return $row[0]; } trigger_error(__METHOD__ . ': Internal error: ' . 'illegal return type specified!', E_USER_ERROR); }
/** * @deprecated Replaced by {@link phorum_api_error()}. */ function phorum_api_error_set($errno, $error = NULL) { return phorum_api_error($errno, $error); }
/** * Retrieve a Phorum file. * * This function can handle Phorum file retrieval in multiple ways: * either return the file to the caller or send it directly to the user's * browser (based on the $flags parameter). Sending it directly to the * browser allows for the implementation of modules that don't have to buffer * the full file data before sending it (a.k.a. streaming, which provides the * advantage of using less memory for sending files). * * @param mixed $file * This is either an array containing at least the fields "file_id" * and "filename" or a numerical file_id value. Note that you can * use the return value of the function * {@link phorum_api_file_check_read_access()} as input for this function. * * @param integer $flags * These are flags that influence aspects of the function call. It is * a bitflag value, so you can OR multiple flags together. Available * flags for this function are: {@link PHORUM_FLAG_IGNORE_PERMS}, * {@link PHORUM_FLAG_GET}, {@link PHORUM_FLAG_SEND} and * {@link PHORUM_FLAG_FORCE_DOWNLOAD}. The SEND flag has precedence * over the GET flag. * * @return mixed * On error, this function will return FALSE. * The functions {@link phorum_api_error_message()} and * {@link phorum_api_error_code()} can be used to retrieve information * about the error that occurred. * * If the {@link PHORUM_FLAG_SEND} flag is used, then the function will * return NULL. * * If the {@link PHORUM_FLAG_GET} flag is used, then the function * will return a file description array, containing the fields "file_id", * "username", "file_data", "mime_type". * If the {@link $file} parameter was an array, then all fields from that * array will be included as well. */ function phorum_api_file_retrieve($file, $flags = PHORUM_FLAG_GET) { global $PHORUM; // Reset error storage. $PHORUM["API"]["errno"] = NULL; $PHORUM["API"]["error"] = NULL; // If $file is not an array, we are handling a numerical file_id. // In that case, first retrieve the file data through the access check // function. All the function flags are passed on to that function, // so the PHORUM_FLAG_IGNORE_PERMS flag can be set for ignoring access // permissions. if (!is_array($file)) { $file_id = (int) $file; $file = phorum_api_file_check_read_access($file_id, $flags); // Return in case of errors. if ($file === FALSE) { return FALSE; } } // A small basic check to see if we have a proper $file array. if (!isset($file["file_id"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"file_id\" field.", E_USER_ERROR); } if (!isset($file["filename"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"filename\" field.", E_USER_ERROR); } settype($file["file_id"], "int"); /* * [hook] * file_retrieve * * [description] * This hook allows modules to handle the file data retrieval. * The hook can use <literal>phorum_api_error()</literal> * to return an error. Hooks should be aware that their input might * not be <literal>$file</literal>, but <literal>FALSE</literal> * instead, in which case they should immediately return * <literal>FALSE</literal> themselves. * * [category] * File storage * * [when] * In * <filename>include/api/file.php</filename>, * right before a file attachment is retrieved from the database. * * [input] * Two part array where the first element is an empty file array * and the second element is the flags variable. * * [output] * Same as input with file_data filled in. * */ $file["result"] = 0; $file["mime_type"] = NULL; $file["file_data"] = NULL; if (isset($PHORUM["hooks"]["file_retrieve"])) { list($file, $flags) = phorum_api_hook("file_retrieve", array($file, $flags)); if ($file === FALSE) { return FALSE; } // If a module sent the file data to the browser, then we are done. if ($file["result"] == PHORUM_FLAG_SEND) { return NULL; } } // If no module handled file retrieval, we will retrieve the // file from the Phorum database. if ($file["file_data"] === NULL) { $dbfile = $PHORUM['DB']->file_get($file["file_id"], TRUE); if (empty($dbfile)) { return phorum_api_error(PHORUM_ERRNO_NOTFOUND, "Phorum file (id {$file["file_id"]}) could not be " . "retrieved from the database."); } // Phorum stores the files in base64 format in the database, to // prevent problems with dumping and restoring databases. $file["file_data"] = base64_decode($dbfile["file_data"]); } // Set the MIME type information if it was not set by a module. $mime_type_verified = FALSE; if ($file["mime_type"] === NULL) { // Determine the MIME type based on the file extension using // the MIME types as defined in the Phorum API code. $extension_mime_type = phorum_api_file_get_mimetype($file["filename"]); // The MIME magic file to use for the fileinfo extension. if (!empty($PHORUM['mime_magic_file'])) { $mime_magic_file = $PHORUM['mime_magic_file']; } else { $mime_magic_file = NULL; } // Retrieve the MIME-type using the fileinfo extension if // it is available and enabled. if (function_exists("finfo_open") && (!isset($PHORUM['file_fileinfo_ext']) || !empty($PHORUM['file_fileinfo_ext'])) && ($finfo = @finfo_open(FILEINFO_MIME, $mime_magic_file))) { $file["mime_type"] = finfo_buffer($finfo, $file['file_data']); finfo_close($finfo); if ($file["mime_type"] === FALSE) { return phorum_api_error(PHORUM_ERRNO_ERROR, "The mime-type of file {$file["file_id"]} couldn't be " . "determined through the fileinfo-extension"); } // The MIME-type for the file extension doesn't fit the // MIME-type as determined by the file's magic signature. // Because it is not safe to view this file in a browser, // we force a download. if ($extension_mime_type != $file["mime_type"]) { $flags = $flags | PHORUM_FLAG_FORCE_DOWNLOAD; } $mime_type_verified = TRUE; } else { $file["mime_type"] = $extension_mime_type; } } // If the file is not requested for downloading, then check if it is // safe for the browser to view the file inline. If it is not, then // enable the force download flag to make sure that the browser will // download the file instead. $safe_to_cache = TRUE; $safe_to_view = TRUE; if (!($flags & PHORUM_FLAG_FORCE_DOWNLOAD) && !$mime_type_verified) { list($safe_to_view, $safe_to_cache) = phorum_api_file_safe_to_view($file); if (!$safe_to_view) { $flags = $flags | PHORUM_FLAG_FORCE_DOWNLOAD; } } // Allow for post processing on the retrieved file. /** * @todo document the file_after_retrieve hook. */ if (!empty($PHORUM['hooks']['file_after_retrieve'])) { list($file, $flags) = phorum_api_hook("file_after_retrieve", array($file, $flags)); } // In "send" mode, we directly send the file contents to the browser. if ($flags & PHORUM_FLAG_SEND) { // Avoid using any output compression or handling on the sent data. ini_set('zlib.output_compression', '0'); ini_set('output_handler', ''); // Get rid of any buffered output so far (there shouldn't be any). phorum_api_buffer_clear(); $time = (int) $file['add_datetime']; // Handle client side caching. if ($safe_to_cache) { // Check if an If-Modified-Since header is in the request. If yes, // then check if the file has changed, based on the date from // the file data. If nothing changed, then we return a 304 header, // to tell the browser to use the cached data. phorum_api_output_last_modify_time($time); // Send caching headers, so files can be cached by the browser. phorum_api_output_cache_max_age(3600 * 24 * 60); } else { phorum_api_output_cache_disable(); } if ($flags & PHORUM_FLAG_FORCE_DOWNLOAD) { $disposition = 'attachment; '; $type = 'application/octet-stream'; } else { $disposition = ''; $type = $file['mime_type']; } header("Content-Disposition: " . "{$disposition}filename=\"{$file['filename']}\""); header("Content-Type: {$type}"); header('Content-Length: ' . strlen($file['file_data'])); print $file['file_data']; return NULL; } elseif ($flags & PHORUM_FLAG_GET) { return $file; } else { trigger_error("phorum_api_file_retrieve(): no retrieve mode specified in the " . "flags (either use PHORUM_FLAG_GET or PHORUM_FLAG_SEND).", E_USER_ERROR); } }
/** * Create a Phorum user session. * * Before calling this function, the variable $PHORUM['use_cookies'] * should be set to one of {@link PHORUM_NO_COOKIES}, * {@link PHORUM_USE_COOKIES} or {@link PHORUM_REQUIRE_COOKIES}. * * Phorum does not use PHP sessions. Instead, it uses its own session * management system for remembering logged in users. There are * multiple reasons for that, amongst which are: * * - the lack of session support (on some PHP installs); * - missing out of the box load balancing support (sessions are normally * written to local session state files, so multiple machines would not * work well together); * - file I/O problems (both performance and file system permissions can * be a problem); * - the amount of unneeded overhead that is caused by the PHP session system; * - the fact that Phorum also supports URI based sessions (without cookie). * * This function can be used to create or maintain a login session for a * Phorum user. A prerequisite is that an active Phorum user is set through * the {@link phorum_api_user_set_active_user()} function, before calling * this function. * * There are two session types available: {@link PHORUM_FORUM_SESSION} * (used for the front end application) and {@link PHORUM_ADMIN_SESSION} * (used for the administrative back end). * * Admin sessions are used for the administrative back end system. For * security reasons, the back end does not share the front end session, * but uses a fully separate session instead. This session does not * have a timeout restriction, but it does not survive closing the * browser. It is always tracked using a cookie, never using URI * authentication (for security reasons). * * The forum sessions can be split up into long term and short term sessions: * * - Long term session: * The standard Phorum user session. This session is long lasting and will * survive after closing the browser (unless the long term session timeout * is set to zero). If tighter security is not enabled, then this session * is all a user needs to fully use all forum options. This session is * tracked using either a cookie or URI authentication. * * - Short term session: * This session has a limited life time and will not survive closing the * browser. If tighter security is enabled, then the user will not be able * to use all forum functions, unless there is a short term session active * (e.g. posting forum messages and reading/writing private messages are * restricted). This session is tracked using a cookie. If URI authentication * is in use (because of admin config or cookie-less browsers) Phorum will * only look at the long term session (even in tighter security mode), since * URI authentication can be considered to be short term by nature. * * @example user_login.php Handle a user forum login * * @param string $type * The type of session to initialize. This must be one of * {@link PHORUM_FORUM_SESSION} or {@link PHORUM_ADMIN_SESSION}. * * @param integer $reset * If it is set to 0 (zero, the default), then existing session_ids * will be reused if possible. * * If this parameter is set to PHORUM_SESSID_RESET_LOGIN, then a new * session id will be generated for short term forum sessions and if * cookies are disabled for some reason, for long term forum sessions * as well (to prevent accidental distribution of URLs with auth info * in them). This is the type of session id reset that is appropriate * after handling a login action. * * If this parameter is set to PHORUM_SESSID_RESET_ALL, then all session * ids will be reset to new values. This is for example * appropriate after a user changed the password (so active sessions on * other computers or browsers will be ended). * * @return boolean * TRUE in case the session was initialized successfully. * Otherwise, FALSE will be returned. The functions * {@link phorum_api_error_message()} and {@link phorum_api_error_code()} * can be used to retrieve information about the error which occurred. */ function phorum_api_user_session_create($type, $reset = 0) { global $PHORUM; /** * [hook] * user_session_create * * [description] * Allow modules to override Phorum's session create management or * to even fully omit creating a session (for example useful * if the hook <hook>user_session_restore</hook> is used * to inherit an external session from some 3rd party application). * * [category] * User authentication and session handling * * [when] * Just before Phorum runs its own session initialization code * in the user API function * <literal>phorum_api_user_session_create()</literal>. * * [input] * The session type for which a session must be created. * This can be either <literal>PHORUM_FORUM_SESSION</literal> * or <literal>PHORUM_ADMIN_SESSION</literal>. * * [output] * Same as input if Phorum has to run its standard session * initialization code or NULL if that code should be fully skipped. * * [example] * <hookcode> * function phorum_mod_foo_user_session_create($type) * { * global $PHORUM; * * // Let Phorum handle admin sessions on its own. * if ($type == PHORUM_ADMIN_SESSION) return $type; * * // Override the session handling for front end forum sessions. * // We could for example put the session in a standard PHP * // session by first starting a PHP session if that was * // not done yet... * if (!session_id()) session_start(); * * // ...and then storing the user_id of the current user in the * // PHP session data. The user_id is really the only thing * // that needs to be remembered for a Phorum session, because * // all other data for the user is stored in the database. * $phorum_user_id = $PHORUM["user"]["user_id"]; * $_SESSION['phorum_user_id'] = $phorum_user_id; * * // Tell Phorum not to run its own session initialization code. * return NULL; * } * </hookcode> * * See the <hook>user_session_restore</hook> hook for an example * of how to let Phorum pick up this PHP based session. */ if (isset($PHORUM['hooks']['user_session_create'])) { if (phorum_api_hook('user_session_create', $type) === NULL) { return TRUE; } } // Reset error storage. $PHORUM['API']['errno'] = NULL; $PHORUM['API']['error'] = NULL; // Check if we have a valid session type. if ($type != PHORUM_FORUM_SESSION && $type != PHORUM_ADMIN_SESSION) { trigger_error('phorum_api_user_session_create(): Illegal session type: ' . htmlspecialchars($type), E_USER_ERROR); return NULL; } // Check if the active Phorum user was set. if (empty($PHORUM['user']) || empty($PHORUM['user']['user_id'])) { trigger_error('phorum_api_user_session_create(): Missing user in environment', E_USER_ERROR); return NULL; } // Check if the user is activated. if ($PHORUM['user']['active'] != PHORUM_USER_ACTIVE) { return phorum_api_error(PHORUM_ERRNO_NOACCESS, 'The user is not (yet) activated (user id ' . $PHORUM['user']['user_id'] . ')'); } // For admin sessions, check if the user has administrator rights. // This is also checked from phorum_api_user_set_active_user(), but // one can never be too sure about this. if ($type == PHORUM_ADMIN_SESSION && empty($PHORUM['user']['admin'])) { return phorum_api_error(PHORUM_ERRNO_NOACCESS, 'The user is not an administrator (user id ' . $PHORUM['user']['user_id'] . ')'); } // Shortcut for checking if session ids are stored in cookies. // Note that the software that uses this function is responsible for // setting $PHORUM["use_cookies"] to PHORUM_NO_COOKIES if the client // does not support cookies. $use_cookies = isset($PHORUM['use_cookies']) && $PHORUM['use_cookies'] > PHORUM_NO_COOKIES; // ---------------------------------------------------------------------- // Retrieve or generate required session id(s). // ---------------------------------------------------------------------- $user = $PHORUM['user']; // Generate a long term session id. This one is used by all session types. // Create a new long term session id if no session id is available yet or // if a refresh was requested and cookies are disabled (with cookies // enabled, we always reuse the existing long term session, so the session // can be remembered and shared between multiple browsers / computers). $refresh_sessid_lt = empty($user['sessid_lt']) || !$use_cookies && $reset == PHORUM_SESSID_RESET_LOGIN || $reset == PHORUM_SESSID_RESET_ALL; if ($refresh_sessid_lt) { $sessid_lt = md5($user['username'] . microtime() . $user['password']); phorum_api_user_save_raw(array('user_id' => $user['user_id'], 'sessid_lt' => $sessid_lt)); $PHORUM['user']['sessid_lt'] = $sessid_lt; } else { $sessid_lt = $user['sessid_lt']; } // For forum sessions, generate a short term session id if tight // security is enabled in the configuration and cookies are enabled // (with URI authentication, the tight security system is bypassed // since the user will have to login on every visit already). $refresh_sessid_st = FALSE; if ($type == PHORUM_FORUM_SESSION && !empty($PHORUM['tight_security']) && $use_cookies) { // How much longer is the existing short term session id valid? $timeleft = empty($user['sessid_st_timeout']) ? 0 : $user['sessid_st_timeout'] - time(); // Create a new short term session id if .. if (empty($user['sessid_st']) || $reset) { // .. any type of reset was requested $sessid_st = md5($user['username'] . microtime() . $user['password']); $refresh_sessid_st = TRUE; } else { // Reuse the existing short term session id $sessid_st = $user['sessid_st']; // Have the session timeout reset if more than one third of the // session's life time has passed and if the session has not // yet expired. if ($timeleft > 0 && $timeleft < $PHORUM['short_session_timeout'] * 60 / 2) { $refresh_sessid_st = TRUE; } } // The session data needs updating. if ($refresh_sessid_st) { $timeout = time() + $PHORUM['short_session_timeout'] * 60; phorum_api_user_save_raw(array('user_id' => $user['user_id'], 'sessid_st' => $sessid_st, 'sessid_st_timeout' => $timeout)); $PHORUM['user']['sessid_st'] = $sessid_st; $PHORUM['user']['sessid_st_timeout'] = $timeout; } } // For admin sessions, the session id is computed using the long term // session id and some random data that was generated at install time. if ($type == PHORUM_ADMIN_SESSION) { $sessid_admin = md5($sessid_lt . $PHORUM['admin_session_salt']); } // ---------------------------------------------------------------------- // Route the required session id(s) to the user. // ---------------------------------------------------------------------- $user = $PHORUM['user']; if ($type == PHORUM_FORUM_SESSION) { // The long term session can be stored in either a cookie or // URL / form posting data. if ($use_cookies) { $timeout = empty($PHORUM['session_timeout']) ? 0 : time() + 86400 * $PHORUM['session_timeout']; setcookie(PHORUM_SESSION_LONG_TERM, $user['user_id'] . ':' . $sessid_lt, $timeout, $PHORUM['session_path'], $PHORUM['session_domain']); } else { // Add the session id to the URL building GET variables. $PHORUM['DATA']['GET_VARS'][PHORUM_SESSION_LONG_TERM] = PHORUM_SESSION_LONG_TERM . '=' . urlencode($user['user_id'] . ':' . $sessid_lt); // Add the session id to the form POST variables. $PHORUM['DATA']['POST_VARS'] .= '<input type="hidden" name="' . PHORUM_SESSION_LONG_TERM . '" ' . 'value="' . $user['user_id'] . ':' . $sessid_lt . '" />'; } // The short term session id is always put in a cookie. if ($refresh_sessid_st) { setcookie(PHORUM_SESSION_SHORT_TERM, $user['user_id'] . ':' . $user['sessid_st'], $user['sessid_st_timeout'], $PHORUM['session_path'], $PHORUM['session_domain']); } } elseif ($type == PHORUM_ADMIN_SESSION) { setcookie(PHORUM_SESSION_ADMIN, $user['user_id'] . ':' . $sessid_admin, 0, $PHORUM['session_path'], $PHORUM['session_domain']); } return TRUE; }
/** * Restore a previously deleted custom field. * * If a custom field is deleted, it's settings and data are not deleted. * The field is only flagged as deleted. This function can be used for * reverting the delete action. * * @param int $id * The id of the custom field to restore. * * @return bool * TRUE if the restore was successfull or FALSE if there was an error. * The functions {@link phorum_api_error_message()} and * {@link phorum_api_error_code()} can be used to retrieve information * about the error that occurred. */ function phorum_api_custom_field_restore($id) { global $PHORUM; settype($id, "int"); $field = $PHORUM['DB']->custom_field_config_get($id); if ($field !== NULL) { $field['deleted'] = FALSE; $PHORUM['DB']->custom_field_config_set($field); phorum_api_custom_field_rebuild_cache(); } else { return phorum_api_error(PHORUM_ERRNO_NOTFOUND, "Unable to restore custom field {$id}: no configuration found."); } return TRUE; }
/** * Clip a part from an image and optionally, resize it. * * This function can be used to clip a part of an image. You can specifiy * width and/or a height to which to scale the clipped image. * * Below, you see an image of the way in which a clip is defined by the * function parameters. $clip_x, $clip_y, $clip_w and $clip_h. * <code> * +------------------------------------------+ * | ^ | * | | | * | clip_y | * | | | * | v | * | +-------------------+ ^ | * | | | | | * |<--clip_x-->| | | | * | | CLIPPED | | | * | | IMAGE | clip_h | * | | | | | * | | | | | * | +-------------------+ v | * | | * | <-------clip_w------> | * | | * +------------------------------------------+ * </code> * * @param string $image * The raw binary image data. * * @param integer $clip_x * The X-offset for the clip, where 0 indicates the left of the image. * @param integer $clip_y * The Y-offset for the clip, where 0 indicates the top of the image. * @param integer $clip_w * The width of the clip to take out of the image. * @param integer $clip_h * The height of the clip to take out of the image. * * @param integer $dst_w * The width for the created clip image in pixels. * Use NULL or 0 (zero) to indicate that the width should be * the same as the $clip_w parameter. * @param integer $dst_h * The height for the created clip image in pixels. * Use NULL or 0 (zero) to indicate that the height should be * the same as the $clip_h parameter. * * @param string $method * The method to use for scaling the image. By default, this function * will try to autodetect a working method. Providing a $method parameter * is mostly useful for debugging purposes. Available methods (in the * order in which they are probed in the code) are: * - imagick : using the ImageMagick library (requires extension "imagick") * - gd : using the GD library (requires extension "gd") * - convert : using the ImageMagick "convert" tool (requires the * ImageMagick package to be installed on the server and does * not work in combination with some PHP safety restrictions). * * @return mixed * FALSE is returned in case creating the clip image failed. The function * {@link phorum_api_error_message()} can be used to retrieve information * about the error that occurred. * * An array is returned in case creating the clip image did work. * This array contains the following fields: * - image : The scaled down image. NULL if no scaling was needed. * - method : The method that was used to create the thumbnail. * - cur_w : The width of the original $image. * - cur_h : The height of the original $image. * - cur_mime : The MIME type of the original $image. * - new_w : The width of the scaled down image. * - new_h : The height of the scaled down image. * - new_mime : The MIME type of the scaled down image, */ function phorum_api_image_clip($image, $clip_x = 0, $clip_y = 0, $clip_w = NULL, $clip_h = NULL, $dst_w = NULL, $dst_h = NULL, $method = NULL) { global $PHORUM; settype($clip_x, 'int'); settype($clip_y, 'int'); settype($clip_w, 'int'); settype($clip_h, 'int'); settype($dst_w, 'int'); settype($dst_h, 'int'); // Reset error storage. $PHORUM['API']['errno'] = NULL; $PHORUM['API']['error'] = NULL; $error = NULL; // Initialize the return array. $img = array('image' => NULL, 'new_mime' => NULL); // Retrieve image info. $image_info = phorum_api_image_info($image); if ($image_info === FALSE) { return FALSE; } // Derive a name for the image type. switch ($image_info['type']) { case IMAGETYPE_JPEG: $type = 'jpeg'; break; case IMAGETYPE_GIF: $type = 'gif'; break; case IMAGETYPE_PNG: $type = 'png'; break; default: $type = 'unknown'; // should not occur } // The clip width and height are inherited from the image // width and height, unless they are set. if (empty($clip_w)) { $clip_w = $image_info['width'] - $clip_x; } if (empty($clip_h)) { $clip_h = $image_info['height'] - $clip_y; } // The target image width and height are inherited from the clip // width and height, unless they are set. if (empty($dst_w)) { $dst_w = $clip_w; } if (empty($dst_h)) { $dst_h = $clip_h; } // Add the image data to the return array. $img['cur_w'] = $image_info['width']; $img['cur_h'] = $image_info['height']; $img['new_w'] = $dst_w; $img['new_h'] = $dst_h; $img['cur_mime'] = $img['new_mime'] = $image_info['mime']; // Check if the requested clip fits the source image size. if ($clip_x + $clip_w > $img['cur_w']) { return phorum_api_error(PHROM_ERRNO_ERROR, "The clip X offset {$clip_x} + clip width {$clip_w} exceeds " . "the source image width {$img['cur_w']}"); } if ($clip_y + $clip_h > $img['cur_h']) { return phorum_api_error(PHROM_ERRNO_ERROR, "The clip Y offset {$clip_y} + clip height {$clip_h} exceeds " . "the source image height {$img['cur_h']}"); } // ----------------------------------------------------------------- // Try to use the imagick library tools // ----------------------------------------------------------------- if (($method === NULL || $method == 'imagick') && extension_loaded('imagick') && class_exists('Imagick')) { $method = NULL; $imagick = new Imagick(); $imagick->readImageBlob($image); $imagick->flattenImages(); $tmp = new Imagick(); $tmp->newPseudoImage($img['cur_w'], $img['cur_h'], 'xc:white'); $tmp->compositeImage($imagick, imagick::COMPOSITE_OVER, 0, 0); $imagick = $tmp; $imagick->cropImage($clip_w, $clip_h, $clip_x, $clip_y); $imagick->thumbnailImage($dst_w, $dst_h, FALSE); $imagick->setImagePage($clip_w, $clip_h, 0, 0); $imagick->setFormat("jpg"); $img['image'] = $imagick->getImagesBlob(); $img['new_mime'] = 'image/jpeg'; $img['method'] = 'imagick'; return $img; } // ----------------------------------------------------------------- // Try to use the GD library tools // ----------------------------------------------------------------- if (($method === NULL || $method == 'gd') && extension_loaded('gd') && function_exists('gd_info')) { // We need gd_info() to check whether GD has the required // image support for the type of image that we are handling. $gd = gd_info(); // We always need JPEG support for the scaled down image. if (empty($gd['JPG Support']) && empty($gd['JPEG Support'])) { $error = "GD: no JPEG support available for processing images"; } elseif ($type == 'gif' && empty($gd['GIF Read Support']) || $type == 'jpeg' && (empty($gd['JPG Support']) && empty($gd['JPEG Support'])) || $type == 'png' && empty($gd['PNG Support'])) { $error = "GD: no support available for image type \"{$type}\""; } else { // Create a GD image handler based on the image data. // imagecreatefromstring() spawns PHP warnings if the file cannot // be processed. We do not care to see that in the event logging, // so if event logging is loaded, we suspend it here. // Instead, we catch error output and try to mangle it into a // usable error message. if (defined('EVENT_LOGGING')) { phorum_mod_event_logging_suspend(); } ob_start(); $original = @imagecreatefromstring($image); $error = ob_get_contents(); ob_end_clean(); $error = trim(preg_replace('!(^(?:\\w+:\\s*).*?:|in /.*$)!', '', trim($error))); if (defined('EVENT_LOGGING')) { phorum_mod_event_logging_resume(); } if (!$original) { if ($error == '') { $error = "GD: Cannot process the {$type} image using GD"; } else { $error = 'GD: ' . $error; } } else { // Create the scaled image. $scaled = imagecreatetruecolor($dst_w, $dst_h); // Fill the image to have a background for transparent pixels. $white = imagecolorallocate($scaled, 255, 255, 255); imagefill($scaled, 0, 0, $white); // Scale the image. imagecopyresampled($scaled, $original, 0, 0, $clip_x, $clip_y, $dst_w, $dst_h, $clip_w, $clip_h); // Create the jpeg output data for the scaled image. ob_start(); imagejpeg($scaled); $image = ob_get_contents(); $size = ob_get_length(); ob_end_clean(); imagedestroy($original); imagedestroy($scaled); $img['image'] = $image; $img['new_mime'] = 'image/jpeg'; $img['method'] = 'gd'; return $img; } } } // ----------------------------------------------------------------- // Try to use the ImageMagick "convert" tool // ----------------------------------------------------------------- if ($method === NULL || $method == 'convert') { // Try to find the "convert" utility. // First, check if it is configured in the Phorum settings. $convert = NULL; if (isset($PHORUM['imagemagick_convert_path'])) { $path = $PHORUM['imagemagick_convert_path']; if (is_executable($path)) { $convert = $path; } } // Not found? Then simply use "convert" and hope that it // can be found in the OS' path. if ($convert === NULL) { $convert = 'convert'; } // Build the command line. $cmd = escapeshellcmd($convert) . ' ' . '- ' . '-background white -flatten ' . "-crop {$clip_w}x{$clip_h}+{$clip_x}+{$clip_y} " . '+repage ' . '-thumbnail ' . $dst_w . 'x' . $dst_h . '\\! ' . 'jpeg:-'; // Run the command. $descriptors = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open($cmd, $descriptors, $pipes); if ($process == FALSE) { $error = 'Failed to execute "convert".'; } else { // Feed convert the image data on STDIN. fwrite($pipes[0], $image); fclose($pipes[0]); // Read the scaled image from STDOUT. $scaled = stream_get_contents($pipes[1]); fclose($pipes[1]); // Read errors. $errors = trim(stream_get_contents($pipes[2])); fclose($pipes[2]); $exit = proc_close($process); if ($exit == 0) { $img['image'] = $scaled; $img['new_mime'] = 'image/jpeg'; $img['method'] = 'convert'; return $img; } // Some error occurred. if ($errors == '') { $error = 'Got exit code ' . $exit . ' from "convert".'; } else { $error = $errors; } } } // ----------------------------------------------------------------- // Safety nets. // ----------------------------------------------------------------- // Return error if one was set. if ($error) { return phorum_api_error(PHORUM_ERRNO_ERROR, $error); } // Catch illegal methods if ($method !== NULL) { return phorum_api_error(PHORUM_ERRNO_ERROR, 'Illegal scaling method: ' . $method); } // If we get here, then we were totally out of luck. return phorum_api_error(PHORUM_ERRNO_ERROR, 'No working image scaling method found'); }
/** * This function can be used to retrieve data from a URL using * an HTTP GET request. * * The way in which data can be retrieved for URLs, depends a lot on * the features and the configuration for PHP on the system. This * function will try to autodetect a way that will work for the * running system automatically. * * @param string $url * The URL to retrieve. * * @param string $method * The method to use for retrieving the data. By default, this function * will try to autodetect a working method. Providing a $method parameter * is mostly useful for debugging purposes. Available methods (in the * order in which they are probed in the code) are: * - curl: using the curl library (requires extension "curl") * - socket: using PHP socket programming (requires extension "sockets") * - file: using fopen() (requires option "allow_url_fopen" to be enabled) * * @return string $data * The data that was loaded from the URL or NULL if an error occurred. * The function {@link phorum_api_error_message()} can be used to retrieve * information about the error that occurred. */ function phorum_api_http_get($url, $method = NULL) { global $PHORUM; // Reset error storage. $PHORUM['API']['errno'] = NULL; $PHORUM['API']['error'] = NULL; // For keeping track of errors in this function. $error = NULL; $fatal = FALSE; // ----------------------------------------------------------------- // Try to use the CURL library tools // ----------------------------------------------------------------- if (($method === NULL || $method == 'curl') && extension_loaded('curl')) { $method = NULL; $curl = @curl_init(); if ($curl === FALSE) { $error = 'Failed to initialize curl request'; } else { // We don't care a lot about certificates for retrieving data. @curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); @curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // Return the header and content when doing a request. @curl_setopt($curl, CURLOPT_HEADER, 1); @curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // We do not let the curl library follow Location: redirects. // Because (at the time of writing) PHP in safe mode disables // redirect following (to prevent redirection to file://), // we implement our own redirect handling here. @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0); // The maximum number of redirects that we want to follow. // This has to be limited to prevent looping. $max_redirects = 10; // Also, track URLs that we have already seen as an extra // anti looping system. $seen = array(); $work_url = $url; for (;;) { // Only HTTP allowed. if (!preg_match('!^https?://!i', $work_url)) { $error = "Denying non-HTTP URL: {$work_url}"; $fatal = TRUE; break; } // Looping prevention. if ($max_redirects-- == 0) { $error = "Bailed out after too many page redirects."; $fatal = TRUE; break; } if (isset($seen[$work_url])) { $error = "Following the URL results in a loop."; $fatal = TRUE; break; } $seen[$work_url] = 1; // Retrieve the page. @curl_setopt($curl, CURLOPT_URL, $work_url); $data = @curl_exec($curl); if ($data == FALSE) { $error = "The curl HTTP request failed."; break; } list($header, $body) = explode("\r\n\r\n", $data, 2); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); $requested_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); // If we got the data, we can return it. if ($code == 200) { return $body; } // Analyze the result data to see what we should to next. $res = phorum_api_http_get_analyze($requested_url, $code, $header); // An error was returned. Bail out. if (isset($res['error'])) { $fatal = empty($res['fatal']) ? 0 : 1; $error = $res['error']; break; } // A redirect was returned. if (isset($res['redirect'])) { $work_url = $res['redirect']; continue; } // Shouldn't get here. trigger_error('phorum_api_http_get_nalyze() returned an ' . 'unexpected result.', E_USER_ERROR); } } } // Return fatal errors. For non fatal errors, we fall through // to the next method. if ($error !== NULL && $fatal) { return phorum_api_error(PHORUM_ERRNO_ERROR, $error); } // ----------------------------------------------------------------- // Try to use a direct fsockopen call. // ----------------------------------------------------------------- if (($method === NULL || $method == 'socket') && extension_loaded('sockets')) { $method = NULL; // The maximum number of redirects that we want to follow. // This has to be limited to prevent looping. $max_redirects = 10; // Also, track URLs that we have already seen as an extra // anti looping system. $seen = array(); $work_url = $url; for (;;) { // Looping prevention. if ($max_redirects-- == 0) { $error = "Bailed out after too many page redirects."; $fatal = TRUE; break; } if (isset($seen[$work_url])) { $error = "Following the URL results in a loop."; $fatal = TRUE; break; } $seen[$work_url] = 1; // Format the HTTP request for retrieving the URL. $parsed = @parse_url(strtoupper($work_url)); if (!isset($parsed['host'])) { $error = "Cannot parse URL"; $fatal = TRUE; break; } $uri = preg_replace('!^\\w+://[^/]+!', '', $work_url); if ($uri == '') { $uri = '/'; } $req = "GET {$uri} HTTP/1.1\r\n" . "Connection: close\r\n" . "Host: " . strtolower($parsed['host']) . "\r\n" . "\r\n"; // Determine protocol and port for the request. $port = NULL; $proto = ''; if (!empty($parsed['port'])) { $port = (int) $parsed['port']; } if (!empty($parsed['scheme'])) { $s = strtolower($parsed['scheme']); if ($s == 'http') { $proto = ''; if ($port === NULL) { $port = 80; } } elseif ($s == 'https') { if (!extension_loaded('openssl')) { $error = "PHP lacks SSL support"; $fatal = TRUE; break; } $proto = 'ssl://'; if ($port === NULL) { $port = 443; } } } if ($port === NULL) { $port = 80; } // Connect to the webserver. $fp = @fsockopen($proto . $parsed['host'], $port, $errno, $errstr, 10); if (!$fp) { $error = "Connection to server failed ({$errstr})"; $fatal = TRUE; break; } // Send the HTTP request. fwrite($fp, $req); // Read the HTTP response. $response = ''; while (is_resource($fp) && !feof($fp)) { $response .= fread($fp, 1024); } fclose($fp); if ($response == '') { $error = "No data retrieved from server"; $fatal = TRUE; break; } // Parse the response. list($header, $body) = explode("\r\n\r\n", $response, 2); list($status, $header) = explode("\r\n", $header, 2); if (preg_match('!^HTTP/\\d+\\.\\d+\\s+(\\d+)\\s!', $status, $m)) { $code = $m[1]; } else { $error = "Unexpected status from server ({$status})"; $fatal = TRUE; break; } // If we got the data, we can return it. if ($code == 200) { // Check if we need to handle chunked transfers // (see RFC 2616, section 3.6.1: Chunked Transfer Coding). if (preg_match('/^Transfer-Encoding:\\s*chunked\\s*$/m', $header)) { $unchunked = ''; for (;;) { // Check if there is another chunk. // There should be, but let's protect against // bad chunked data. if (strstr($body, "\r\n") === FALSE) { break; } // Get the size of the next chunk. list($sz, $rest) = explode("\r\n", $body, 2); $sz = preg_replace('/;.*$/', '', $sz); $sz = hexdec($sz); // Size 0 indicates end of body data. if ($sz == 0) { break; } // Add the chunk to the unchunked body data. $unchunked .= substr($rest, 0, $sz); $body = substr($rest, $sz + 2); // +2 = skip \r\n } // Return the unchunked body content. return $unchunked; } // Return the body content. return $body; } // Analyze the result data to see what we should to next. $res = phorum_api_http_get_analyze($work_url, $code, $header); // An error was returned. Bail out. if (isset($res['error'])) { $fatal = empty($res['fatal']) ? 0 : 1; $error = $res['error']; break; } // A redirect was returned. if (isset($res['redirect'])) { $work_url = $res['redirect']; continue; } // Shouldn't get here. trigger_error('phorum_api_http_get_analyze() returned an ' . 'unexpected result.', E_USER_ERROR); } } // Return fatal errors. For non fatal errors, we fall through // to the next method. if ($error !== NULL && $fatal) { return phorum_api_error(PHORUM_ERRNO_ERROR, $error); } // ----------------------------------------------------------------- // Try to use file_get_contents // ----------------------------------------------------------------- if (($method === NULL || $method == 'fopen') && ini_get('allow_url_fopen')) { $method = NULL; $track = ini_get('track_errors'); ini_set('track_errors', TRUE); $php_errormsg = ''; $contents = @file_get_contents($url); ini_set('track_errors', $track); if ($contents === FALSE || $php_errormsg != '') { $error = preg_replace('/(^.*?\\:\\s+|[\\r\\n])/', '', $php_errormsg); $error = "[{$error}]"; } else { return $contents; } } // Return errors. if ($error !== NULL) { return phorum_api_error(PHORUM_ERRNO_ERROR, $error); } // Catch illegal methods if ($method !== NULL) { return phorum_api_error(PHORUM_ERRNO_ERROR, 'Illegal method: ' . $method); } return phorum_api_error(PHORUM_ERRNO_ERROR, 'No working HTTP request method found'); }