PHP Cross Reference - Moodle - Source:...

download PHP Cross Reference - Moodle - Source: /lib/filelib.phpphpcrossref.com/xref/moodle/lib/filelib.php.htmlPHP Cross Reference - Moodle - Source: /lib ... * or pix/f/FILENAME.png must

If you can't read please download the document

Transcript of PHP Cross Reference - Moodle - Source:...

PHP Cross Reference - Moodle - Source: /lib/filelib.php

MoodlePHP Cross ReferenceLearning Management Systems

Statistics - IndexVariables - Functions - Classes - Constants

Source: /lib/filelib.php - 4640 lines - 186451 bytes - Summary - Text - Print

Description: Functions for file handling.

1 '-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');1604 1605 $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1606 if (empty($filetype)) {1607 $filetype = 'xxx'; // file without extension1608 }1609 if (preg_match('/^icon(\d*)$/', $element, $iconsizematch)) {1610 $iconsize = max(array(16, (int)$iconsizematch[1]));1611 $filenames = array($mimeinfo['xxx']['icon']);1612 if ($filetype != 'xxx' && isset($mimeinfo[$filetype]['icon'])) {1613 array_unshift($filenames, $mimeinfo[$filetype]['icon']);1614 }1615 // find the file with the closest size, first search for specific icon then for default icon1616 foreach ($filenames as $filename) {1617 foreach ($iconpostfixes as $size => $postfix) {1618 $fullname = $CFG->dirroot.'/pix/f/'.$filename.$postfix;1619 if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {1620 return $filename.$postfix;1621 }1622 }1623 }1624 } else if (isset($mimeinfo[$filetype][$element])) {1625 return $mimeinfo[$filetype][$element];1626 } else if (isset($mimeinfo['xxx'][$element])) {1627 return $mimeinfo['xxx'][$element]; // By default1628 } else {1629 return null;1630 }1631 }1632 1633 /**1634 * Obtains information about a filetype based on the MIME type rather than1635 * the other way around.1636 *1637 * @category files1638 * @param string $element Desired information ('extension', 'icon', 'icon-24', etc.)1639 * @param string $mimetype MIME type we're looking up1640 * @return string Requested piece of information from array1641 */1642 function mimeinfo_from_type($element, $mimetype) {1643 /* array of cached mimetype->extension associations */1644 static $cached = array();1645 $mimeinfo = & get_mimetypes_array();1646 1647 if (!array_key_exists($mimetype, $cached)) {1648 $cached[$mimetype] = null;1649 foreach($mimeinfo as $filetype => $values) {1650 if ($values['type'] == $mimetype) {1651 if ($cached[$mimetype] === null) {1652 $cached[$mimetype] = '.'.$filetype;1653 }1654 if (!empty($values['defaulticon'])) {1655 $cached[$mimetype] = '.'.$filetype;1656 break;1657 }1658 }1659 }1660 if (empty($cached[$mimetype])) {1661 $cached[$mimetype] = '.xxx';1662 }1663 }1664 if ($element === 'extension') {1665 return $cached[$mimetype];1666 } else {1667 return mimeinfo($element, $cached[$mimetype]);1668 }1669 }1670 1671 /**1672 * Return the relative icon path for a given file1673 *1674 * Usage:1675 * 1676 * // $file - instance of stored_file or file_info1677 * $icon = $OUTPUT->pix_url(file_file_icon($file))->out();1678 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($file)));1679 * 1680 * or1681 * 1682 * echo $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file));1683 * 1684 *1685 * @param stored_file|file_info|stdClass|array $file (in case of object attributes $file->filename1686 * and $file->mimetype are expected)1687 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561688 * @return string1689 */1690 function file_file_icon($file, $size = null) {1691 if (!is_object($file)) {1692 $file = (object)$file;1693 }1694 if (isset($file->filename)) {1695 $filename = $file->filename;1696 } else if (method_exists($file, 'get_filename')) {1697 $filename = $file->get_filename();1698 } else if (method_exists($file, 'get_visible_name')) {1699 $filename = $file->get_visible_name();1700 } else {1701 $filename = '';1702 }1703 if (isset($file->mimetype)) {1704 $mimetype = $file->mimetype;1705 } else if (method_exists($file, 'get_mimetype')) {1706 $mimetype = $file->get_mimetype();1707 } else {1708 $mimetype = '';1709 }1710 $mimetypes = &get_mimetypes_array();1711 if ($filename) {1712 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1713 if ($extension && !empty($mimetypes[$extension])) {1714 // if file name has known extension, return icon for this extension1715 return file_extension_icon($filename, $size);1716 }1717 }1718 return file_mimetype_icon($mimetype, $size);1719 }1720 1721 /**1722 * Return the relative icon path for a folder image1723 *1724 * Usage:1725 * 1726 * $icon = $OUTPUT->pix_url(file_folder_icon())->out();1727 * echo html_writer::empty_tag('img', array('src' => $icon));1728 * 1729 * or1730 * 1731 * echo $OUTPUT->pix_icon(file_folder_icon(32));1732 * 1733 *1734 * @param int $iconsize The size of the icon. Defaults to 16 can also be 24, 32, 48, 64, 72, 80, 96, 128, 2561735 * @return string1736 */1737 function file_folder_icon($iconsize = null) {1738 global $CFG;1739 static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');1740 static $cached = array();1741 $iconsize = max(array(16, (int)$iconsize));1742 if (!array_key_exists($iconsize, $cached)) {1743 foreach ($iconpostfixes as $size => $postfix) {1744 $fullname = $CFG->dirroot.'/pix/f/folder'.$postfix;1745 if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {1746 $cached[$iconsize] = 'f/folder'.$postfix;1747 break;1748 }1749 }1750 }1751 return $cached[$iconsize];1752 }1753 1754 /**1755 * Returns the relative icon path for a given mime type1756 *1757 * This function should be used in conjunction with $OUTPUT->pix_url to produce1758 * a return the full path to an icon.1759 *1760 * 1761 * $mimetype = 'image/jpg';1762 * $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype))->out();1763 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($mimetype)));1764 * 1765 *1766 * @category files1767 * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered1768 * to conform with that.1769 * @param string $mimetype The mimetype to fetch an icon for1770 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561771 * @return string The relative path to the icon1772 */1773 function file_mimetype_icon($mimetype, $size = NULL) {1774 return 'f/'.mimeinfo_from_type('icon'.$size, $mimetype);1775 }1776 1777 /**1778 * Returns the relative icon path for a given file name1779 *1780 * This function should be used in conjunction with $OUTPUT->pix_url to produce1781 * a return the full path to an icon.1782 *1783 * 1784 * $filename = '.jpg';1785 * $icon = $OUTPUT->pix_url(file_extension_icon($filename))->out();1786 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => '...'));1787 * 1788 *1789 * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered1790 * to conform with that.1791 * @todo MDL-31074 Implement $size1792 * @category files1793 * @param string $filename The filename to get the icon for1794 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561795 * @return string1796 */1797 function file_extension_icon($filename, $size = NULL) {1798 return 'f/'.mimeinfo('icon'.$size, $filename);1799 }1800 1801 /**1802 * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the1803 * mimetypes.php language file.1804 *1805 * @param mixed $obj - instance of stored_file or file_info or array/stdClass with field1806 * 'filename' and 'mimetype', or just a string with mimetype (though it is recommended to1807 * have filename); In case of array/stdClass the field 'mimetype' is optional.1808 * @param bool $capitalise If true, capitalises first character of result1809 * @return string Text description1810 */1811 function get_mimetype_description($obj, $capitalise=false) {1812 $filename = $mimetype = '';1813 if (is_object($obj) && method_exists($obj, 'get_filename') && method_exists($obj, 'get_mimetype')) {1814 // this is an instance of stored_file1815 $mimetype = $obj->get_mimetype();1816 $filename = $obj->get_filename();1817 } else if (is_object($obj) && method_exists($obj, 'get_visible_name') && method_exists($obj, 'get_mimetype')) {1818 // this is an instance of file_info1819 $mimetype = $obj->get_mimetype();1820 $filename = $obj->get_visible_name();1821 } else if (is_array($obj) || is_object ($obj)) {1822 $obj = (array)$obj;1823 if (!empty($obj['filename'])) {1824 $filename = $obj['filename'];1825 }1826 if (!empty($obj['mimetype'])) {1827 $mimetype = $obj['mimetype'];1828 }1829 } else {1830 $mimetype = $obj;1831 }1832 $mimetypefromext = mimeinfo('type', $filename);1833 if (empty($mimetype) || $mimetypefromext !== 'document/unknown') {1834 // if file has a known extension, overwrite the specified mimetype1835 $mimetype = $mimetypefromext;1836 }1837 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1838 if (empty($extension)) {1839 $mimetypestr = mimeinfo_from_type('string', $mimetype);1840 $extension = str_replace('.', '', mimeinfo_from_type('extension', $mimetype));1841 } else {1842 $mimetypestr = mimeinfo('string', $filename);1843 }1844 $chunks = explode('/', $mimetype, 2);1845 $chunks[] = '';1846 $attr = array(1847 'mimetype' => $mimetype,1848 'ext' => $extension,1849 'mimetype1' => $chunks[0],1850 'mimetype2' => $chunks[1],1851 );1852 $a = array();1853 foreach ($attr as $key => $value) {1854 $a[$key] = $value;1855 $a[strtoupper($key)] = strtoupper($value);1856 $a[ucfirst($key)] = ucfirst($value);1857 }1858 1859 // MIME types may include + symbol but this is not permitted in string ids.1860 $safemimetype = str_replace('+', '_', $mimetype);1861 $safemimetypestr = str_replace('+', '_', $mimetypestr);1862 if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) {1863 $result = get_string($safemimetype, 'mimetypes', (object)$a);1864 } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) {1865 $result = get_string($safemimetypestr, 'mimetypes', (object)$a);1866 } else if (get_string_manager()->string_exists('default', 'mimetypes')) {1867 $result = get_string('default', 'mimetypes', (object)$a);1868 } else {1869 $result = $mimetype;1870 }1871 if ($capitalise) {1872 $result=ucfirst($result);1873 }1874 return $result;1875 }1876 1877 /**1878 * Returns array of elements of type $element in type group(s)1879 *1880 * @param string $element name of the element we are interested in, usually 'type' or 'extension'1881 * @param string|array $groups one group or array of groups/extensions/mimetypes1882 * @return array1883 */1884 function file_get_typegroup($element, $groups) {1885 static $cached = array();1886 if (!is_array($groups)) {1887 $groups = array($groups);1888 }1889 if (!array_key_exists($element, $cached)) {1890 $cached[$element] = array();1891 }1892 $result = array();1893 foreach ($groups as $group) {1894 if (!array_key_exists($group, $cached[$element])) {1895 // retrieive and cache all elements of type $element for group $group1896 $mimeinfo = & get_mimetypes_array();1897 $cached[$element][$group] = array();1898 foreach ($mimeinfo as $extension => $value) {1899 $value['extension'] = '.'.$extension;1900 if (empty($value[$element])) {1901 continue;1902 }1903 if (($group === '.'.$extension || $group === $value['type'] ||1904 (!empty($value['groups']) && in_array($group, $value['groups']))) &&1905 !in_array($value[$element], $cached[$element][$group])) {1906 $cached[$element][$group][] = $value[$element];1907 }1908 }1909 }1910 $result = array_merge($result, $cached[$element][$group]);1911 }1912 return array_values(array_unique($result));1913 }1914 1915 /**1916 * Checks if file with name $filename has one of the extensions in groups $groups1917 *1918 * @see get_mimetypes_array()1919 * @param string $filename name of the file to check1920 * @param string|array $groups one group or array of groups to check1921 * @param bool $checktype if true and extension check fails, find the mimetype and check if1922 * file mimetype is in mimetypes in groups $groups1923 * @return bool1924 */1925 function file_extension_in_typegroup($filename, $groups, $checktype = false) {1926 $extension = pathinfo($filename, PATHINFO_EXTENSION);1927 if (!empty($extension) && in_array('.'.strtolower($extension), file_get_typegroup('extension', $groups))) {1928 return true;1929 }1930 return $checktype && file_mimetype_in_typegroup(mimeinfo('type', $filename), $groups);1931 }1932 1933 /**1934 * Checks if mimetype $mimetype belongs to one of the groups $groups1935 *1936 * @see get_mimetypes_array()1937 * @param string $mimetype1938 * @param string|array $groups one group or array of groups to check1939 * @return bool1940 */1941 function file_mimetype_in_typegroup($mimetype, $groups) {1942 return !empty($mimetype) && in_array($mimetype, file_get_typegroup('type', $groups));1943 }1944 1945 /**1946 * Requested file is not found or not accessible, does not return, terminates script1947 *1948 * @global stdClass $CFG1949 * @global stdClass $COURSE1950 */1951 function send_file_not_found() {1952 global $CFG, $COURSE;1953 send_header_404();1954 print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??1955 }1956 /**1957 * Helper function to send correct 404 for server.1958 */1959 function send_header_404() {1960 if (substr(php_sapi_name(), 0, 3) == 'cgi') {1961 header("Status: 404 Not Found");1962 } else {1963 header('HTTP/1.0 404 not found');1964 }1965 }1966 1967 /**1968 * The readfile function can fail when files are larger than 2GB (even on 64-bit1969 * platforms). This wrapper uses readfile for small files and custom code for1970 * large ones.1971 *1972 * @param string $path Path to file1973 * @param int $filesize Size of file (if left out, will get it automatically)1974 * @return int|bool Size read (will always be $filesize) or false if failed1975 */1976 function readfile_allow_large($path, $filesize = -1) {1977 // Automatically get size if not specified.1978 if ($filesize === -1) {1979 $filesize = filesize($path);1980 }1981 if ($filesize 0) {1992 $size = min($left, 65536);1993 $buffer = fread($handle, $size);1994 if ($buffer === false) {1995 return false;1996 }1997 echo $buffer;1998 $left -= $size;1999 }2000 return $filesize;2001 }2002 }2003 2004 /**2005 * Enhanced readfile() with optional acceleration.2006 * @param string|stored_file $file2007 * @param string $mimetype2008 * @param bool $accelerate2009 * @return void2010 */2011 function readfile_accel($file, $mimetype, $accelerate) {2012 global $CFG;2013 2014 if ($mimetype === 'text/plain') {2015 // there is no encoding specified in text files, we need something consistent2016 header('Content-Type: text/plain; charset=utf-8');2017 } else {2018 header('Content-Type: '.$mimetype);2019 }2020 2021 $lastmodified = is_object($file) ? $file->get_timemodified() : filemtime($file);2022 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');2023 2024 if (is_object($file)) {2025 header('Etag: "' . $file->get_contenthash() . '"');2026 if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $file->get_contenthash()) {2027 header('HTTP/1.1 304 Not Modified');2028 return;2029 }2030 }2031 2032 // if etag present for stored file rely on it exclusively2033 if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (empty($_SERVER['HTTP_IF_NONE_MATCH']) or !is_object($file))) {2034 // get unixtime of request header; clip extra junk off first2035 $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]));2036 if ($since && $since >= $lastmodified) {2037 header('HTTP/1.1 304 Not Modified');2038 return;2039 }2040 }2041 2042 if ($accelerate and !empty($CFG->xsendfile)) {2043 if (empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {2044 header('Accept-Ranges: bytes');2045 } else {2046 header('Accept-Ranges: none');2047 }2048 2049 if (is_object($file)) {2050 $fs = get_file_storage();2051 if ($fs->xsendfile($file->get_contenthash())) {2052 return;2053 }2054 2055 } else {2056 require_once("$CFG->libdir/xsendfilelib.php");2057 if (xsendfile($file)) {2058 return;2059 }2060 }2061 }2062 2063 $filesize = is_object($file) ? $file->get_filesize() : filesize($file);2064 2065 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');2066 2067 if ($accelerate and empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {2068 header('Accept-Ranges: bytes');2069 2070 if (!empty($_SERVER['HTTP_RANGE']) and strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {2071 // byteserving stuff - for acrobat reader and download accelerators2072 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.352073 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php2074 $ranges = false;2075 if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {2076 foreach ($ranges as $key=>$value) {2077 if ($ranges[$key][1] == '') {2078 //suffix case2079 $ranges[$key][1] = $filesize - $ranges[$key][2];2080 $ranges[$key][2] = $filesize - 1;2081 } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {2082 //fix range length2083 $ranges[$key][2] = $filesize - 1;2084 }2085 if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {2086 //invalid byte-range ==> ignore header2087 $ranges = false;2088 break;2089 }2090 //prepare multipart header2091 $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";2092 $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";2093 }2094 } else {2095 $ranges = false;2096 }2097 if ($ranges) {2098 if (is_object($file)) {2099 $handle = $file->get_content_file_handle();2100 } else {2101 $handle = fopen($file, 'rb');2102 }2103 byteserving_send_file($handle, $mimetype, $ranges, $filesize);2104 }2105 }2106 } else {2107 // Do not byteserve2108 header('Accept-Ranges: none');2109 }2110 2111 header('Content-Length: '.$filesize);2112 2113 if ($filesize > 10000000) {2114 // for large files try to flush and close all buffers to conserve memory2115 while(@ob_get_level()) {2116 if (!@ob_end_flush()) {2117 break;2118 }2119 }2120 }2121 2122 // send the whole file content2123 if (is_object($file)) {2124 $file->readfile();2125 } else {2126 readfile_allow_large($file, $filesize);2127 }2128 }2129 2130 /**2131 * Similar to readfile_accel() but designed for strings.2132 * @param string $string2133 * @param string $mimetype2134 * @param bool $accelerate2135 * @return void2136 */2137 function readstring_accel($string, $mimetype, $accelerate) {2138 global $CFG;2139 2140 if ($mimetype === 'text/plain') {2141 // there is no encoding specified in text files, we need something consistent2142 header('Content-Type: text/plain; charset=utf-8');2143 } else {2144 header('Content-Type: '.$mimetype);2145 }2146 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');2147 header('Accept-Ranges: none');2148 2149 if ($accelerate and !empty($CFG->xsendfile)) {2150 $fs = get_file_storage();2151 if ($fs->xsendfile(sha1($string))) {2152 return;2153 }2154 }2155 2156 header('Content-Length: '.strlen($string));2157 echo $string;2158 }2159 2160 /**2161 * Handles the sending of temporary file to user, download is forced.2162 * File is deleted after abort or successful sending, does not return, script terminated2163 *2164 * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself2165 * @param string $filename proposed file name when saving file2166 * @param bool $pathisstring If the path is string2167 */2168 function send_temp_file($path, $filename, $pathisstring=false) {2169 global $CFG;2170 2171 if (core_useragent::is_firefox()) {2172 // only FF is known to correctly save to disk before opening...2173 $mimetype = mimeinfo('type', $filename);2174 } else {2175 $mimetype = 'application/x-forcedownload';2176 }2177 2178 // close session - not needed anymore2179 \core\session\manager::write_close();2180 2181 if (!$pathisstring) {2182 if (!file_exists($path)) {2183 send_header_404();2184 print_error('filenotfound', 'error', $CFG->wwwroot.'/');2185 }2186 // executed after normal finish or abort2187 core_shutdown_manager::register_function('send_temp_file_finished', array($path));2188 }2189 2190 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2191 if (core_useragent::is_ie()) {2192 $filename = urlencode($filename);2193 }2194 2195 header('Content-Disposition: attachment; filename="'.$filename.'"');2196 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312197 header('Cache-Control: private, max-age=10, no-transform');2198 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2199 header('Pragma: ');2200 } else { //normal http - prevent caching at all cost2201 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2202 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2203 header('Pragma: no-cache');2204 }2205 2206 // send the contents - we can not accelerate this because the file will be deleted asap2207 if ($pathisstring) {2208 readstring_accel($path, $mimetype, false);2209 } else {2210 readfile_accel($path, $mimetype, false);2211 @unlink($path);2212 }2213 2214 die; //no more chars to output2215 }2216 2217 /**2218 * Internal callback function used by send_temp_file()2219 *2220 * @param string $path2221 */2222 function send_temp_file_finished($path) {2223 if (file_exists($path)) {2224 @unlink($path);2225 }2226 }2227 2228 /**2229 * Handles the sending of file data to the user's browser, including support for2230 * byteranges etc.2231 *2232 * @category files2233 * @param string $path Path of file on disk (including real filename), or actual content of file as string2234 * @param string $filename Filename to send2235 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)2236 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only2237 * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname2238 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin2239 * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename2240 * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.2241 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,2242 * you must detect this case when control is returned using connection_aborted. Please not that session is closed2243 * and should not be reopened.2244 * @return null script execution stopped unless $dontdie is true2245 */2246 function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {2247 global $CFG, $COURSE;2248 2249 if ($dontdie) {2250 ignore_user_abort(true);2251 }2252 2253 if ($lifetime === 'default' or is_null($lifetime)) {2254 $lifetime = $CFG->filelifetime;2255 }2256 2257 \core\session\manager::write_close(); // Unlock session during file serving.2258 2259 // Use given MIME type if specified, otherwise guess it using mimeinfo.2260 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O2261 // only Firefox saves all files locally before opening when content-disposition: attachment stated2262 $isFF = core_useragent::is_firefox(); // only FF properly tested2263 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :2264 ($mimetype ? $mimetype : mimeinfo('type', $filename));2265 2266 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2267 if (core_useragent::is_ie()) {2268 $filename = rawurlencode($filename);2269 }2270 2271 if ($forcedownload) {2272 header('Content-Disposition: attachment; filename="'.$filename.'"');2273 } else {2274 header('Content-Disposition: inline; filename="'.$filename.'"');2275 }2276 2277 if ($lifetime > 0) {2278 $private = '';2279 if (isloggedin() and !isguestuser()) {2280 $private = ' private,';2281 }2282 $nobyteserving = false;2283 header('Cache-Control:'.$private.' max-age='.$lifetime.', no-transform');2284 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');2285 header('Pragma: ');2286 2287 } else { // Do not cache files in proxies and browsers2288 $nobyteserving = true;2289 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312290 header('Cache-Control: private, max-age=10, no-transform');2291 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2292 header('Pragma: ');2293 } else { //normal http - prevent caching at all cost2294 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2295 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2296 header('Pragma: no-cache');2297 }2298 }2299 2300 if (empty($filter)) {2301 // send the contents2302 if ($pathisstring) {2303 readstring_accel($path, $mimetype, !$dontdie);2304 } else {2305 readfile_accel($path, $mimetype, !$dontdie);2306 }2307 2308 } else {2309 // Try to put the file through filters2310 if ($mimetype == 'text/html') {2311 $options = new stdClass();2312 $options->noclean = true;2313 $options->nocache = true; // temporary workaround for MDL-51362314 $text = $pathisstring ? $path : implode('', file($path));2315 2316 $text = file_modify_html_header($text);2317 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);2318 2319 readstring_accel($output, $mimetype, false);2320 2321 } else if (($mimetype == 'text/plain') and ($filter == 1)) {2322 // only filter text if filter all files is selected2323 $options = new stdClass();2324 $options->newlines = false;2325 $options->noclean = true;2326 $text = htmlentities($pathisstring ? $path : implode('', file($path)), ENT_QUOTES, 'UTF-8');2327 $output = ''. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'';2328 2329 readstring_accel($output, $mimetype, false);2330 2331 } else {2332 // send the contents2333 if ($pathisstring) {2334 readstring_accel($path, $mimetype, !$dontdie);2335 } else {2336 readfile_accel($path, $mimetype, !$dontdie);2337 }2338 }2339 }2340 if ($dontdie) {2341 return;2342 }2343 die; //no more chars to output!!!2344 }2345 2346 /**2347 * Handles the sending of file data to the user's browser, including support for2348 * byteranges etc.2349 *2350 * The $options parameter supports the following keys:2351 * (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail)2352 * (string|null) filename - overrides the implicit filename2353 * (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.2354 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,2355 * you must detect this case when control is returned using connection_aborted. Please not that session is closed2356 * and should not be reopened.2357 *2358 * @category files2359 * @param stored_file $stored_file local file object2360 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)2361 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only2362 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin2363 * @param array $options additional options affecting the file serving2364 * @return null script execution stopped unless $options['dontdie'] is true2365 */2366 function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownload=false, array $options=array()) {2367 global $CFG, $COURSE;2368 2369 if (empty($options['filename'])) {2370 $filename = null;2371 } else {2372 $filename = $options['filename'];2373 }2374 2375 if (empty($options['dontdie'])) {2376 $dontdie = false;2377 } else {2378 $dontdie = true;2379 }2380 2381 if ($lifetime === 'default' or is_null($lifetime)) {2382 $lifetime = $CFG->filelifetime;2383 }2384 2385 if (!empty($options['preview'])) {2386 // replace the file with its preview2387 $fs = get_file_storage();2388 $preview_file = $fs->get_file_preview($stored_file, $options['preview']);2389 if (!$preview_file) {2390 // unable to create a preview of the file, send its default mime icon instead2391 if ($options['preview'] === 'tinyicon') {2392 $size = 24;2393 } else if ($options['preview'] === 'thumb') {2394 $size = 90;2395 } else {2396 $size = 256;2397 }2398 $fileicon = file_file_icon($stored_file, $size);2399 send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');2400 } else {2401 // preview images have fixed cache lifetime and they ignore forced download2402 // (they are generated by GD and therefore they are considered reasonably safe).2403 $stored_file = $preview_file;2404 $lifetime = DAYSECS;2405 $filter = 0;2406 $forcedownload = false;2407 }2408 }2409 2410 // handle external resource2411 if ($stored_file && $stored_file->is_external_file() && !isset($options['sendcachedexternalfile'])) {2412 $stored_file->send_file($lifetime, $filter, $forcedownload, $options);2413 die;2414 }2415 2416 if (!$stored_file or $stored_file->is_directory()) {2417 // nothing to serve2418 if ($dontdie) {2419 return;2420 }2421 die;2422 }2423 2424 if ($dontdie) {2425 ignore_user_abort(true);2426 }2427 2428 \core\session\manager::write_close(); // Unlock session during file serving.2429 2430 // Use given MIME type if specified, otherwise guess it using mimeinfo.2431 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O2432 // only Firefox saves all files locally before opening when content-disposition: attachment stated2433 $filename = is_null($filename) ? $stored_file->get_filename() : $filename;2434 $isFF = core_useragent::is_firefox(); // only FF properly tested2435 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :2436 ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));2437 2438 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2439 if (core_useragent::is_ie()) {2440 $filename = rawurlencode($filename);2441 }2442 2443 if ($forcedownload) {2444 header('Content-Disposition: attachment; filename="'.$filename.'"');2445 } else {2446 header('Content-Disposition: inline; filename="'.$filename.'"');2447 }2448 2449 if ($lifetime > 0) {2450 $private = '';2451 if (isloggedin() and !isguestuser()) {2452 $private = ' private,';2453 }2454 header('Cache-Control:'.$private.' max-age='.$lifetime.', no-transform');2455 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');2456 header('Pragma: ');2457 2458 } else { // Do not cache files in proxies and browsers2459 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312460 header('Cache-Control: private, max-age=10, no-transform');2461 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2462 header('Pragma: ');2463 } else { //normal http - prevent caching at all cost2464 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2465 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2466 header('Pragma: no-cache');2467 }2468 }2469 2470 if (empty($filter)) {2471 // send the contents2472 readfile_accel($stored_file, $mimetype, !$dontdie);2473 2474 } else { // Try to put the file through filters2475 if ($mimetype == 'text/html') {2476 $options = new stdClass();2477 $options->noclean = true;2478 $options->nocache = true; // temporary workaround for MDL-51362479 $text = $stored_file->get_content();2480 $text = file_modify_html_header($text);2481 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);2482 2483 readstring_accel($output, $mimetype, false);2484 2485 } else if (($mimetype == 'text/plain') and ($filter == 1)) {2486 // only filter text if filter all files is selected2487 $options = new stdClass();2488 $options->newlines = false;2489 $options->noclean = true;2490 $text = $stored_file->get_content();2491 $output = ''. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'';2492 2493 readstring_accel($output, $mimetype, false);2494 2495 } else { // Just send it out raw2496 readfile_accel($stored_file, $mimetype, !$dontdie);2497 }2498 }2499 if ($dontdie) {2500 return;2501 }2502 die; //no more chars to output!!!2503 }2504 2505 /**2506 * Retrieves an array of records from a CSV file and places2507 * them into a given table structure2508 *2509 * @global stdClass $CFG2510 * @global moodle_database $DB2511 * @param string $file The path to a CSV file2512 * @param string $table The table to retrieve columns from2513 * @return bool|array Returns an array of CSV records or false2514 */2515 function get_records_csv($file, $table) {2516 global $CFG, $DB;2517 2518 if (!$metacolumns = $DB->get_columns($table)) {2519 return false;2520 }2521 2522 if(!($handle = @fopen($file, 'r'))) {2523 print_error('get_records_csv failed to open '.$file);2524 }2525 2526 $fieldnames = fgetcsv($handle, 4096);2527 if(empty($fieldnames)) {2528 fclose($handle);2529 return false;2530 }2531 2532 $columns = array();2533 2534 foreach($metacolumns as $metacolumn) {2535 $ord = array_search($metacolumn->name, $fieldnames);2536 if(is_int($ord)) {2537 $columns[$metacolumn->name] = $ord;2538 }2539 }2540 2541 $rows = array();2542 2543 while (($data = fgetcsv($handle, 4096)) !== false) {2544 $item = new stdClass;2545 foreach($columns as $name => $ord) {2546 $item->$name = $data[$ord];2547 }2548 $rows[] = $item;2549 }2550 2551 fclose($handle);2552 return $rows;2553 }2554 2555 /**2556 * Create a file with CSV contents2557 *2558 * @global stdClass $CFG2559 * @global moodle_database $DB2560 * @param string $file The file to put the CSV content into2561 * @param array $records An array of records to write to a CSV file2562 * @param string $table The table to get columns from2563 * @return bool success2564 */2565 function put_records_csv($file, $records, $table = NULL) {2566 global $CFG, $DB;2567 2568 if (empty($records)) {2569 return true;2570 }2571 2572 $metacolumns = NULL;2573 if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {2574 return false;2575 }2576 2577 echo "x";2578 2579 if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {2580 print_error('put_records_csv failed to open '.$file);2581 }2582 2583 $proto = reset($records);2584 if(is_object($proto)) {2585 $fields_records = array_keys(get_object_vars($proto));2586 }2587 else if(is_array($proto)) {2588 $fields_records = array_keys($proto);2589 }2590 else {2591 return false;2592 }2593 echo "x";2594 2595 if(!empty($metacolumns)) {2596 $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);2597 $fields = array_intersect($fields_records, $fields_table);2598 }2599 else {2600 $fields = $fields_records;2601 }2602 2603 fwrite($fp, implode(',', $fields));2604 fwrite($fp, "\r\n");2605 2606 foreach($records as $record) {2607 $array = (array)$record;2608 $values = array();2609 foreach($fields as $field) {2610 if(strpos($array[$field], ',')) {2611 $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';2612 }2613 else {2614 $values[] = $array[$field];2615 }2616 }2617 fwrite($fp, implode(',', $values)."\r\n");2618 }2619 2620 fclose($fp);2621 @chmod($CFG->tempdir.'/'.$file, $CFG->filepermissions);2622 return true;2623 }2624 2625 2626 /**2627 * Recursively delete the file or folder with path $location. That is,2628 * if it is a file delete it. If it is a folder, delete all its content2629 * then delete it. If $location does not exist to start, that is not2630 * considered an error.2631 *2632 * @param string $location the path to remove.2633 * @return bool2634 */2635 function fulldelete($location) {2636 if (empty($location)) {2637 // extra safety against wrong param2638 return false;2639 }2640 if (is_dir($location)) {2641 if (!$currdir = opendir($location)) {2642 return false;2643 }2644 while (false !== ($file = readdir($currdir))) {2645 if ($file ".." && $file ".") {2646 $fullfile = $location."/".$file;2647 if (is_dir($fullfile)) {2648 if (!fulldelete($fullfile)) {2649 return false;2650 }2651 } else {2652 if (!unlink($fullfile)) {2653 return false;2654 }2655 }2656 }2657 }2658 closedir($currdir);2659 if (! rmdir($location)) {2660 return false;2661 }2662 2663 } else if (file_exists($location)) {2664 if (!unlink($location)) {2665 return false;2666 }2667 }2668 return true;2669 }2670 2671 /**2672 * Send requested byterange of file.2673 *2674 * @param resource $handle A file handle2675 * @param string $mimetype The mimetype for the output2676 * @param array $ranges An array of ranges to send2677 * @param string $filesize The size of the content if only one range is used2678 */2679 function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {2680 // better turn off any kind of compression and buffering2681 ini_set('zlib.output_compression', 'Off');2682 2683 $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!2684 if ($handle === false) {2685 die;2686 }2687 if (count($ranges) == 1) { //only one range requested2688 $length = $ranges[0][2] - $ranges[0][1] + 1;2689 header('HTTP/1.1 206 Partial content');2690 header('Content-Length: '.$length);2691 header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);2692 header('Content-Type: '.$mimetype);2693 2694 while(@ob_get_level()) {2695 if (!@ob_end_flush()) {2696 break;2697 }2698 }2699 2700 fseek($handle, $ranges[0][1]);2701 while (!feof($handle) && $length > 0) {2702 core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk2703 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));2704 echo $buffer;2705 flush();2706 $length -= strlen($buffer);2707 }2708 fclose($handle);2709 die;2710 } else { // multiple ranges requested - not tested much2711 $totallength = 0;2712 foreach($ranges as $range) {2713 $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;2714 }2715 $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");2716 header('HTTP/1.1 206 Partial content');2717 header('Content-Length: '.$totallength);2718 header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);2719 2720 while(@ob_get_level()) {2721 if (!@ob_end_flush()) {2722 break;2723 }2724 }2725 2726 foreach($ranges as $range) {2727 $length = $range[2] - $range[1] + 1;2728 echo $range[0];2729 fseek($handle, $range[1]);2730 while (!feof($handle) && $length > 0) {2731 core_php_time_limit::raise(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk2732 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));2733 echo $buffer;2734 flush();2735 $length -= strlen($buffer);2736 }2737 }2738 echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";2739 fclose($handle);2740 die;2741 }2742 }2743 2744 /**2745 * add includes (js and css) into uploaded files2746 * before returning them, useful for themes and utf.js includes2747 *2748 * @global stdClass $CFG2749 * @param string $text text to search and replace2750 * @return string text with added head includes2751 * @todo MDL-211202752 */2753 function file_modify_html_header($text) {2754 // first look for tag2755 global $CFG;2756 2757 $stylesheetshtml = '';2758 /* foreach ($CFG->stylesheets as $stylesheet) {2759 //TODO: MDL-211202760 $stylesheetshtml .= ''."\n";2761 }*/2762 2763 $ufo = '';2764 if (filter_is_enabled('mediaplugin')) {2765 // this script is needed by most media filter plugins.2766 $attributes = array('type'=>'text/javascript', 'src'=>$CFG->httpswwwroot . '/lib/ufo.js');2767 $ufo = html_writer::tag('script', '', $attributes) . "\n";2768 }2769 2770 preg_match('/\|\/', $text, $matches);2771 if ($matches) {2772 $replacement = ''.$ufo.$stylesheetshtml;2773 $text = preg_replace('/\|\/', $replacement, $text, 1);2774 return $text;2775 }2776 2777 // if not, look for tag, and stick right after2778 preg_match('/\|\/', $text, $matches);2779 if ($matches) {2780 // replace tag with includes2781 $replacement = ''."\n".''.$ufo.$stylesheetshtml.'';2782 $text = preg_replace('/\|\/', $replacement, $text, 1);2783 return $text;2784 }2785 2786 // if not, look for tag, and stick before body2787 preg_match('/\|\/', $text, $matches);2788 if ($matches) {2789 $replacement = ''.$ufo.$stylesheetshtml.''."\n".'';2790 $text = preg_replace('/\|\/', $replacement, $text, 1);2791 return $text;2792 }2793 2794 // if not, just stick a tag at the beginning2795 $text = ''.$ufo.$stylesheetshtml.''."\n".$text;2796 return $text;2797 }2798 2799 /**2800 * RESTful cURL class2801 *2802 * This is a wrapper class for curl, it is quite easy to use:2803 * 2804 * $c = new curl;2805 * // enable cache2806 * $c = new curl(array('cache'=>true));2807 * // enable cookie2808 * $c = new curl(array('cookie'=>true));2809 * // enable proxy2810 * $c = new curl(array('proxy'=>true));2811 *2812 * // HTTP GET Method2813 * $html = $c->get('http://example.com');2814 * // HTTP POST Method2815 * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));2816 * // HTTP PUT Method2817 * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');2818 * 2819 *2820 * @package core_files2821 * @category files2822 * @copyright Dongsheng Cai 2823 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License2824 */2825 class curl {2826 /** @var bool Caches http request contents */2827 public $cache = false;2828 /** @var bool Uses proxy, null means automatic based on URL */2829 public $proxy = null;2830 /** @var string library version */2831 public $version = '0.4 dev';2832 /** @var array http's response */2833 public $response = array();2834 /** @var array Raw response headers, needed for BC in download_file_content(). */2835 public $rawresponse = array();2836 /** @var array http header */2837 public $header = array();2838 /** @var string cURL information */2839 public $info;2840 /** @var string error */2841 public $error;2842 /** @var int error code */2843 public $errno;2844 /** @var bool use workaround for open_basedir restrictions, to be changed from unit tests only! */2845 public $emulateredirects = null;2846 2847 /** @var array cURL options */2848 private $options;2849 /** @var string Proxy host */2850 private $proxy_host = '';2851 /** @var string Proxy auth */2852 private $proxy_auth = '';2853 /** @var string Proxy type */2854 private $proxy_type = '';2855 /** @var bool Debug mode on */2856 private $debug = false;2857 /** @var bool|string Path to cookie file */2858 private $cookie = false;2859 /** @var bool tracks multiple headers in response - redirect detection */2860 private $responsefinished = false;2861 2862 /**2863 * Curl constructor.2864 *2865 * Allowed settings are:2866 * proxy: (bool) use proxy server, null means autodetect non-local from url2867 * debug: (bool) use debug output2868 * cookie: (string) path to cookie file, false if none2869 * cache: (bool) use cache2870 * module_cache: (string) type of cache2871 *2872 * @param array $settings2873 */2874 public function __construct($settings = array()) {2875 global $CFG;2876 if (!function_exists('curl_init')) {2877 $this->error = 'cURL module must be enabled!';2878 trigger_error($this->error, E_USER_ERROR);2879 return false;2880 }2881 2882 // All settings of this class should be init here.2883 $this->resetopt();2884 if (!empty($settings['debug'])) {2885 $this->debug = true;2886 }2887 if (!empty($settings['cookie'])) {2888 if($settings['cookie'] === true) {2889 $this->cookie = $CFG->dataroot.'/curl_cookie.txt';2890 } else {2891 $this->cookie = $settings['cookie'];2892 }2893 }2894 if (!empty($settings['cache'])) {2895 if (class_exists('curl_cache')) {2896 if (!empty($settings['module_cache'])) {2897 $this->cache = new curl_cache($settings['module_cache']);2898 } else {2899 $this->cache = new curl_cache('misc');2900 }2901 }2902 }2903 if (!empty($CFG->proxyhost)) {2904 if (empty($CFG->proxyport)) {2905 $this->proxy_host = $CFG->proxyhost;2906 } else {2907 $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;2908 }2909 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {2910 $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;2911 $this->setopt(array(2912 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,2913 'proxyuserpwd'=>$this->proxy_auth));2914 }2915 if (!empty($CFG->proxytype)) {2916 if ($CFG->proxytype == 'SOCKS5') {2917 $this->proxy_type = CURLPROXY_SOCKS5;2918 } else {2919 $this->proxy_type = CURLPROXY_HTTP;2920 $this->setopt(array('httpproxytunnel'=>false));2921 }2922 $this->setopt(array('proxytype'=>$this->proxy_type));2923 }2924 2925 if (isset($settings['proxy'])) {2926 $this->proxy = $settings['proxy'];2927 }2928 } else {2929 $this->proxy = false;2930 }2931 2932 if (!isset($this->emulateredirects)) {2933 $this->emulateredirects = ini_get('open_basedir');2934 }2935 }2936 2937 /**2938 * Resets the CURL options that have already been set2939 */2940 public function resetopt() {2941 $this->options = array();2942 $this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0';2943 // True to include the header in the output2944 $this->options['CURLOPT_HEADER'] = 0;2945 // True to Exclude the body from the output2946 $this->options['CURLOPT_NOBODY'] = 0;2947 // Redirect ny default.2948 $this->options['CURLOPT_FOLLOWLOCATION'] = 1;2949 $this->options['CURLOPT_MAXREDIRS'] = 10;2950 $this->options['CURLOPT_ENCODING'] = '';2951 // TRUE to return the transfer as a string of the return2952 // value of curl_exec() instead of outputting it out directly.2953 $this->options['CURLOPT_RETURNTRANSFER'] = 1;2954 $this->options['CURLOPT_SSL_VERIFYPEER'] = 0;2955 $this->options['CURLOPT_SSL_VERIFYHOST'] = 2;2956 $this->options['CURLOPT_CONNECTTIMEOUT'] = 30;2957 2958 if ($cacert = self::get_cacert()) {2959 $this->options['CURLOPT_CAINFO'] = $cacert;2960 }2961 }2962 2963 /**2964 * Get the location of ca certificates.2965 * @return string absolute file path or empty if default used2966 */2967 public static function get_cacert() {2968 global $CFG;2969 2970 // Bundle in dataroot always wins.2971 if (is_readable("$CFG->dataroot/moodleorgca.crt")) {2972 return realpath("$CFG->dataroot/moodleorgca.crt");2973 }2974 2975 // Next comes the default from php.ini2976 $cacert = ini_get('curl.cainfo');2977 if (!empty($cacert) and is_readable($cacert)) {2978 return realpath($cacert);2979 }2980 2981 // Windows PHP does not have any certs, we need to use something.2982 if ($CFG->ostype === 'WINDOWS') {2983 if (is_readable("$CFG->libdir/cacert.pem")) {2984 return realpath("$CFG->libdir/cacert.pem");2985 }2986 }2987 2988 // Use default, this should work fine on all properly configured *nix systems.2989 return null;2990 }2991 2992 /**2993 * Reset Cookie2994 */2995 public function resetcookie() {2996 if (!empty($this->cookie)) {2997 if (is_file($this->cookie)) {2998 $fp = fopen($this->cookie, 'w');2999 if (!empty($fp)) {3000 fwrite($fp, '');3001 fclose($fp);3002 }3003 }3004 }3005 }3006 3007 /**3008 * Set curl options.3009 *3010 * Do not use the curl constants to define the options, pass a string3011 * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass3012 * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method.3013 *3014 * @param array $options If array is null, this function will reset the options to default value.3015 * @return void3016 * @throws coding_exception If an option uses constant value instead of option name.3017 */3018 public function setopt($options = array()) {3019 if (is_array($options)) {3020 foreach ($options as $name => $val) {3021 if (!is_string($name)) {3022 throw new coding_exception('Curl options should be defined using strings, not constant values.');3023 }3024 if (stripos($name, 'CURLOPT_') === false) {3025 $name = strtoupper('CURLOPT_'.$name);3026 } else {3027 $name = strtoupper($name);3028 }3029 $this->options[$name] = $val;3030 }3031 }3032 }3033 3034 /**3035 * Reset http method3036 */3037 public function cleanopt() {3038 unset($this->options['CURLOPT_HTTPGET']);3039 unset($this->options['CURLOPT_POST']);3040 unset($this->options['CURLOPT_POSTFIELDS']);3041 unset($this->options['CURLOPT_PUT']);3042 unset($this->options['CURLOPT_INFILE']);3043 unset($this->options['CURLOPT_INFILESIZE']);3044 unset($this->options['CURLOPT_CUSTOMREQUEST']);3045 unset($this->options['CURLOPT_FILE']);3046 }3047 3048 /**3049 * Resets the HTTP Request headers (to prepare for the new request)3050 */3051 public function resetHeader() {3052 $this->header = array();3053 }3054 3055 /**3056 * Set HTTP Request Header3057 *3058 * @param array $header3059 */3060 public function setHeader($header) {3061 if (is_array($header)) {3062 foreach ($header as $v) {3063 $this->setHeader($v);3064 }3065 } else {3066 // Remove newlines, they are not allowed in headers.3067 $this->header[] = preg_replace('/[\r\n]/', '', $header);3068 }3069 }3070 3071 /**3072 * Get HTTP Response Headers3073 * @return array of arrays3074 */3075 public function getResponse() {3076 return $this->response;3077 }3078 3079 /**3080 * Get raw HTTP Response Headers3081 * @return array of strings3082 */3083 public function get_raw_response() {3084 return $this->rawresponse;3085 }3086 3087 /**3088 * private callback function3089 * Formatting HTTP Response Header3090 *3091 * We only keep the last headers returned. For example during a redirect the3092 * redirect headers will not appear in {@link self::getResponse()}, if you need3093 * to use those headers, refer to {@link self::get_raw_response()}.3094 *3095 * @param resource $ch Apparently not used3096 * @param string $header3097 * @return int The strlen of the header3098 */3099 private function formatHeader($ch, $header) {3100 $this->rawresponse[] = $header;3101 3102 if (trim($header, "\r\n") === '') {3103 // This must be the last header.3104 $this->responsefinished = true;3105 }3106 3107 if (strlen($header) > 2) {3108 if ($this->responsefinished) {3109 // We still have headers after the supposedly last header, we must be3110 // in a redirect so let's empty the response to keep the last headers.3111 $this->responsefinished = false;3112 $this->response = array();3113 }3114 list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);3115 $key = rtrim($key, ':');3116 if (!empty($this->response[$key])) {3117 if (is_array($this->response[$key])) {3118 $this->response[$key][] = $value;3119 } else {3120 $tmp = $this->response[$key];3121 $this->response[$key] = array();3122 $this->response[$key][] = $tmp;3123 $this->response[$key][] = $value;3124 3125 }3126 } else {3127 $this->response[$key] = $value;3128 }3129 }3130 return strlen($header);3131 }3132 3133 /**3134 * Set options for individual curl instance3135 *3136 * @param resource $curl A curl handle3137 * @param array $options3138 * @return resource The curl handle3139 */3140 private function apply_opt($curl, $options) {3141 // Some more security first.3142 if (defined('CURLOPT_PROTOCOLS')) {3143 $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);3144 }3145 if (defined('CURLOPT_REDIR_PROTOCOLS')) {3146 $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);3147 }3148 3149 // Clean up3150 $this->cleanopt();3151 // set cookie3152 if (!empty($this->cookie) || !empty($options['cookie'])) {3153 $this->setopt(array('cookiejar'=>$this->cookie,3154 'cookiefile'=>$this->cookie3155 ));3156 }3157 3158 // Bypass proxy if required.3159 if ($this->proxy === null) {3160 if (!empty($this->options['CURLOPT_URL']) and is_proxybypass($this->options['CURLOPT_URL'])) {3161 $proxy = false;3162 } else {3163 $proxy = true;3164 }3165 } else {3166 $proxy = (bool)$this->proxy;3167 }3168 3169 // Set proxy.3170 if ($proxy) {3171 $options['CURLOPT_PROXY'] = $this->proxy_host;3172 } else {3173 unset($this->options['CURLOPT_PROXY']);3174 }3175 3176 $this->setopt($options);3177 // reset before set options3178 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));3179 // set headers3180 if (empty($this->header)) {3181 $this->setHeader(array(3182 'User-Agent: MoodleBot/1.0',3183 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',3184 'Connection: keep-alive'3185 ));3186 }3187 curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);3188 3189 if ($this->debug) {3190 echo 'Options';3191 var_dump($this->options);3192 echo 'Header';3193 var_dump($this->header);3194 }3195 3196 // Do not allow infinite redirects.3197 if (!isset($this->options['CURLOPT_MAXREDIRS'])) {3198 $this->options['CURLOPT_MAXREDIRS'] = 0;3199 } else if ($this->options['CURLOPT_MAXREDIRS'] > 100) {3200 $this->options['CURLOPT_MAXREDIRS'] = 100;3201 } else {3202 $this->options['CURLOPT_MAXREDIRS'] = (int)$this->options['CURLOPT_MAXREDIRS'];3203 }3204 3205 // Make sure we always know if redirects expected.3206 if (!isset($this->options['CURLOPT_FOLLOWLOCATION'])) {3207 $this->options['CURLOPT_FOLLOWLOCATION'] = 0;3208 }3209 3210 // Set options.3211 foreach($this->options as $name => $val) {3212 if ($name === 'CURLOPT_PROTOCOLS' or $name === 'CURLOPT_REDIR_PROTOCOLS') {3213 // These can not be changed, sorry.3214 continue;3215 }3216 if ($name === 'CURLOPT_FOLLOWLOCATION' and $this->emulateredirects) {3217 // The redirects are emulated elsewhere.3218 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0);3219 continue;3220 }3221 $name = constant($name);3222 curl_setopt($curl, $name, $val);3223 }3224 3225 return $curl;3226 }3227 3228 /**3229 * Download multiple files in parallel3230 *3231 * Calls {@link multi()} with specific download headers3232 *3233 * 3234 * $c = new curl();3235 * $file1 = fopen('a', 'wb');3236 * $file2 = fopen('b', 'wb');3237 * $c->download(array(3238 * array('url'=>'http://localhost/', 'file'=>$file1),3239 * array('url'=>'http://localhost/20/', 'file'=>$file2)3240 * ));3241 * fclose($file1);3242 * fclose($file2);3243 * 3244 *3245 * or3246 *3247 * 3248 * $c = new curl();3249 * $c->download(array(3250 * array('url'=>'http://localhost/', 'filepath'=>'/tmp/file1.tmp'),3251 * array('url'=>'http://localhost/20/', 'filepath'=>'/tmp/file2.tmp')3252 * ));3253 * 3254 *3255 * @param array $requests An array of files to request {3256 * url => url to download the file [required]3257 * file => file handler, or3258 * filepath => file path3259 * }3260 * If 'file' and 'filepath' parameters are both specified in one request, the3261 * open file handle in the 'file' parameter will take precedence and 'filepath'3262 * will be ignored.3263 *3264 * @param array $options An array of options to set3265 * @return array An array of results3266 */3267 public function download($requests, $options = array()) {3268 $options['RETURNTRANSFER'] = false;3269 return $this->multi($requests, $options);3270 }3271 3272 /**3273 * Multi HTTP Requests3274 * This function could run multi-requests in parallel.3275 *3276 * @param array $requests An array of files to request3277 * @param array $options An array of options to set3278 * @return array An array of results3279 */3280 protected function multi($requests, $options = array()) {3281 $count = count($requests);3282 $handles = array();3283 $results = array();3284 $main = curl_multi_init();3285 for ($i = 0; $i < $count; $i++) {3286 if (!empty($requests[$i]['filepath']) and empty($requests[$i]['file'])) {3287 // open file3288 $requests[$i]['file'] = fopen($requests[$i]['filepath'], 'w');3289 $requests[$i]['auto-handle'] = true;3290 }3291 foreach($requests[$i] as $n=>$v) {3292 $options[$n] = $v;3293 }3294 $handles[$i] = curl_init($requests[$i]['url']);3295 $this->apply_opt($handles[$i], $options);3296 curl_multi_add_handle($main, $handles[$i]);3297 }3298 $running = 0;3299 do {3300 curl_multi_exec($main, $running);3301 } while($running > 0);3302 for ($i = 0; $i < $count; $i++) {3303 if (!empty($options['CURLOPT_RETURNTRANSFER'])) {3304 $results[] = true;3305 } else {3306 $results[] = curl_multi_getcontent($handles[$i]);3307 }3308 curl_multi_remove_handle($main, $handles[$i]);3309 }3310 curl_multi_close($main);3311 3312 for ($i = 0; $i < $count; $i++) {3313 if (!empty($requests[$i]['filepath']) and !empty($requests[$i]['auto-handle'])) {3314 // close file handler if file is opened in this function3315 fclose($requests[$i]['file']);3316 }3317 }3318 return $results;3319 }3320 3321 /**3322 * Single HTTP Request3323 *3324 * @param string $url The URL to request3325 * @param array $options3326 * @return bool3327 */3328 protected function request($url, $options = array()) {3329 // Set the URL as a curl option.3330 $this->setopt(array('CURLOPT_URL' => $url));3331 3332 // Create curl instance.3333 $curl = curl_init();3334 3335 // Reset here so that the data is valid when result returned from cache.3336 $this->info = array();3337 $this->error = '';3338 $this->errno = 0;3339 $this->response = array();3340 $this->rawresponse = array();3341 $this->responsefinished = false;3342 3343 $this->apply_opt($curl, $options);3344 if ($this->cache && $ret = $this->cache->get($this->options)) {3345 return $ret;3346 }3347 3348 $ret = curl_exec($curl);3349 $this->info = curl_getinfo($curl);3350 $this->error = curl_error($curl);3351 $this->errno = curl_errno($curl);3352 // Note: $this->response and $this->rawresponse are filled by $hits->formatHeader callback.3353 3354 if ($this->emulateredirects and $this->options['CURLOPT_FOLLOWLOCATION'] and $this->info['http_code'] != 200) {3355 $redirects = 0;3356 3357 while($redirects options['CURLOPT_MAXREDIRS']) {3358 3359 if ($this->info['http_code'] == 301) {3360 // Moved Permanently - repeat the same request on new URL.3361 3362 } else if ($this->info['http_code'] == 302) {3363 // Found - the standard redirect - repeat the same request on new URL.3364 3365 } else if ($this->info['http_code'] == 303) {3366 // 303 See Other - repeat only if GET, do not bother with POSTs.3367 if (empty($this->options['CURLOPT_HTTPGET'])) {3368 break;3369 }3370 3371 } else if ($this->info['http_code'] == 307) {3372 // Temporary Redirect - must repeat using the same request type.3373 3374 } else if ($this->info['http_code'] == 308) {3375 // Permanent Redirect - must repeat using the same request type.3376 3377 } else {3378 // Some other http code means do not retry!3379 break;3380 }3381 3382 $redirects++;3383 3384 $redirecturl = null;3385 if (isset($this->info['redirect_url'])) {3386 if (preg_match('|^https?://|i', $this->info['redirect_url'])) {3387 $redirecturl = $this->info['redirect_url'];3388 }3389 }3390 if (!$redirecturl) {3391 foreach ($this->response as $k => $v) {3392 if (strtolower($k) === 'location') {3393 $redirecturl = $v;3394 break;3395 }3396 }3397 if (preg_match('|^https?://|i', $redirecturl)) {3398 // Great, this is the correct location format!3399 3400 } else if ($redirecturl) {3401 $current = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);3402 if (strpos($redirecturl, '/') === 0) {3403 // Relative to server root - just guess.3404 $pos = strpos('/', $current, 8);3405 if ($pos === false) {3406 $redirecturl = $current.$redirecturl;3407 } else {3408 $redirecturl = substr($current, 0, $pos).$redirecturl;3409 }3410 } else {3411 // Relative to current script.3412 $redirecturl = dirname($current).'/'.$redirecturl;3413 }3414 }3415 }3416 3417 curl_setopt($curl, CURLOPT_URL, $redirecturl);3418 $ret = curl_exec($curl);3419 3420 $this->info = curl_getinfo($curl);3421 $this->error = curl_error($curl);3422 $this->errno = curl_errno($curl);3423 3424 $this->info['redirect_count'] = $redirects;3425 3426 if ($this->info['http_code'] === 200) {3427 // Finally this is what we wanted.3428 break;3429 }3430 if ($this->errno != CURLE_OK) {3431 // Something wrong is going on.3432 break;3433 }3434 }3435 if ($redirects > $this->options['CURLOPT_MAXREDIRS']) {3436 $this->errno = CURLE_TOO_MANY_REDIRECTS;3437 $this->error = 'Maximum ('.$this->options['CURLOPT_MAXREDIRS'].') redirects followed';3438 }3439 }3440 3441 if ($this->cache) {3442 $this->cache->set($this->options, $ret);3443 }3444 3445 if ($this->debug) {3446 echo 'Return Data';3447 var_dump($ret);3448 echo 'Info';3449 var_dump($this->info);3450 echo 'Error';3451 var_dump($this->error);3452 }3453 3454 curl_close($curl);3455 3456 if (empty($this->error)) {3457 return $ret;3458 } else {3459 return $this->error;3460 // exception is not ajax friendly3461 //throw new moodle_exception($this->error, 'curl');3462 }3463 }3464 3465 /**3466 * HTTP HEAD method3467 *3468 * @see request()3469 *3470 * @param string $url3471 * @param array $options3472 * @return bool3473 */3474 public function head($url, $options = array()) {3475 $options['CURLOPT_HTTPGET'] = 0;3476 $options['CURLOPT_HEADER'] = 1;3477 $options['CURLOPT_NOBODY'] = 1;3478 return $this->request($url, $options);3479 }3480 3481 /**3482 * HTTP POST method3483 *3484 * @param string $url3485 * @param array|string $params3486 * @param array $options3487 * @return bool3488 */3489 public function post($url, $params = '', $options = array()) {3490 $options['CURLOPT_POST'] = 1;3491 if (is_array($params)) {3492 $this->_tmp_file_post_params = array();3493 foreach ($params as $key => $value) {3494 if ($value instanceof stored_file) {3495 $value->add_to_curl_request($this, $key);3496 } else {3497 $this->_tmp_file_post_params[$key] = $value;3498 }3499 }3500 $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;3501 unset($this->_tmp_file_post_params);3502 } else {3503 // $params is the raw post data3504 $options['CURLOPT_POSTFIELDS'] = $params;3505 }3506 return $this->request($url, $options);3507 }3508 3509 /**3510 * HTTP GET method3511 *3512 * @param string $url3513 * @param array $params3514 * @param array $options3515 * @return bool3516 */3517 public function get($url, $params = array(), $options = array()) {3518 $options['CURLOPT_HTTPGET'] = 1;3519 3520 if (!empty($params)) {3521 $url .= (stripos($url, '?') !== false) ? '&' : '?';3522 $url .= http_build_query($params, '', '&');3523 }3524 return $this->request($url, $options);3525 }3526 3527 /**3528 * Downloads one file and writes it to the specified file handler3529 *3530 * 3531 * $c = new curl();3532 * $file = fopen('savepath', 'w');3533 * $result = $c->download_one('http://localhost/', null,3534 * array('file' => $file, 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));3535 * fclose($file);3536 * $download_info = $c->get_info();3537 * if ($result === true) {3538 * // file downloaded successfully3539 * } else {3540 * $error_text = $result;3541 * $error_code = $c->get_errno();3542 * }3543 * 3544 *3545 * 3546 * $c = new curl();3547 * $result = $c->download_one('http://localhost/', null,3548 * array('filepath' => 'savepath', 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));3549 * // ... see above, no need to close handle and remove file if unsuccessful3550 * 3551 *3552 * @param string $url3553 * @param array|null $params key-value pairs to be added to $url as query string3554 * @param array $options request options. Must include either 'file' or 'filepath'3555 * @return bool|string true on success or error string on failure3556 */3557 public function download_one($url, $params, $options = array()) {3558 $options['CURLOPT_HTTPGET'] = 1;3559 if (!empty($params)) {3560 $url .= (stripos($url, '?') !== false) ? '&' : '?';3561 $url .= http_build_query($params, '', '&');3562 }3563 if (!empty($options['filepath']) && empty($options['file'])) {3564 // open file3565 if (!($options['file'] = fopen($options['filepath'], 'w'))) {3566 $this->errno = 100;3567 return get_string('cannotwritefile', 'error', $options['filepath']);3568 }3569 $filepath = $options['filepath'];3570 }3571 unset($options['filepath']);3572 $result = $this->request($url, $options);3573 if (isset($filepath)) {3574 fclose($options['file']);3575 if ($result !== true) {3576 unlink($filepath);3577 }3578 }3579 return $result;3580 }3581 3582 /**3583 * HTTP PUT method3584 *3585 * @param string $url3586 * @param array $params3587 * @param array $options3588 * @return bool3589 */3590 public function put($url, $params = array(), $options = array()) {3591 $file = $params['file'];3592 if (!is_file($file)) {3593 return null;3594 }3595 $fp = fopen($file, 'r');3596 $size = filesize($file);3597 $options['CURLOPT_PUT'] = 1;3598 $options['CURLOPT_INFILESIZE'] = $size;3599 $options['CURLOPT_INFILE'] = $fp;3600 if (!isset($this->options['CURLOPT_USERPWD'])) {3601 $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: [email protected]'));3602 }3603 $ret = $this->request($url, $options);3604 fclose($fp);3605 return $ret;3606 }3607 3608 /**3609 * HTTP DELETE method3610 *3611 * @param string $url3612 * @param array $param3613 * @param array $options3614 * @return bool3615 */3616 public function delete($url, $param = array(), $options = array()) {3617 $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';3618 if (!isset($options['CURLOPT_USERPWD'])) {3619 $options['CURLOPT_USERPWD'] = 'anonymous: [email protected]';3620 }3621 $ret = $this->request($url, $options);3622 return $ret;3623 }3624 3625 /**3626 * HTTP TRACE method3627 *3628 * @param string $url3629 * @param array $options3630 * @return bool3631 */3632 public function trace($url, $options = array()) {3633 $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';3634 $ret = $this->request($url, $options);3635 return $ret;3636 }3637 3638 /**3639 * HTTP OPTIONS method3640 *3641 * @param string $url3642 * @param array $options3643 * @return bool3644 */3645 public function options($url, $options = array()) {3646 $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';3647 $ret = $this->request($url, $options);3648 return $ret;3649 }3650 3651 /**3652 * Get curl information3653 *3654 * @return string3655 */3656 public function get_info() {3657 return $this->info;3658 }3659 3660 /**3661 * Get curl error code3662 *3663 * @return int3664 */3665 public function get_errno() {3666 return $this->errno;3667 }3668 3669 /**3670 * When using a proxy, an additional HTTP response code may appear at3671 * the start of the header. For example, when using https over a proxy3672 * there may be 'HTTP/1.0 200 Connection Established'. Other codes are3673 * also possible and some may come with their own headers.3674 *3675 * If using the return value containing all headers, this function can be3676 * called to remove unwanted doubles.3677 *3678 * Note that it is not possible to distinguish this situation from valid3679 * data unless you know the actual response part (below the headers)3680 * will not be included in this string, or else will not 'look like' HTTP3681 * headers. As a result it is not safe to call this function for general3682 * data.3683 *3684 * @param string $input Input HTTP response3685 * @return string HTTP response with additional headers stripped if any3686 */3687 public static function strip_double_headers($input) {3688 // I have tried to make this regular expression as specific as possible3689 // to avoid any case where it does weird stuff if you happen to put3690 // HTTP/1.1 200 at the start of any line in your RSS file. This should3691 // also make it faster because it can abandon regex processing as soon3692 // as it hits something that doesn't look like an http header. The3693 // header definition is taken from RFC 822, except I didn't support3694 // folding which is never used in practice.3695 $crlf = "\r\n";3696 return preg_replace(3697 // HTTP version and status code (ignore value of code).3698 '~^HTTP/1\..*' . $crlf .3699 // Header name: character between 33 and 126 decimal, except colon.3700 // Colon. Header value: any character except \r and \n. CRLF.3701 '(?:[\x21-\x39\x3b-\x7e]+:[^' . $crlf . ']+' . $crlf . ')*' .3702 // Headers are terminated by another CRLF (blank line).3703 $crlf .3704 // Second HTTP status code, this time must be 200.3705 '(HTTP/1.[01] 200 )~', '$1', $input);3706 }3707 }3708 3709 /**3710 * This class is used by cURL class, use case:3711 *3712 * 3713 * $CFG->repositorycacheexpire = 120;3714 * $CFG->curlcache = 120;3715 *3716 * $c = new curl(array('cache'=>true), 'module_cache'=>'repository');3717 * $ret = $c->get('http://www.google.com');3718 * 3719 *3720 * @package core_files3721 * @copyright Dongsheng Cai 3722 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later3723 */3724 class curl_cache {3725 /** @var string Path to cache directory */3726 public $dir = '';3727 3728 /**3729 * Constructor3730 *3731 * @global stdClass $CFG3732 * @param string $module which module is using curl_cache3733 */3734 public function __construct($module = 'repository') {3735 global $CFG;3736 if (!empty($module)) {3737 $this->dir = $CFG->cachedir.'/'.$module.'/';3738 } else {3739 $this->dir = $CFG->cachedir.'/misc/';3740 }3741 if (!file_exists($this->dir)) {3742 mkdir($this->dir, $CFG->directorypermissions, true);3743 }3744 if ($module == 'repository') {3745 if (empty($CFG->repositorycacheexpire)) {3746 $CFG->repositorycacheexpire = 120;3747 }3748 $this->ttl = $CFG->repositorycacheexpire;3749 } else {3750 if (empty($CFG->curlcache)) {3751 $CFG->curlcache = 120;3752 }3753 $this->ttl = $CFG->curlcache;3754 }3755 }3756 3757 /**3758 * Get cached value3759 *3760 * @global stdClass $CFG3761 * @global stdClass $USER3762 * @param mixed $param3763 * @return bool|string3764 */3765 public function get($param) {3766 global $CFG, $USER;3767 $this->cleanup($this->ttl);3768 $filename = 'u'.$USER->id.'_'.md5(serialize($param));3769 if(file_exists($this->dir.$filename)) {3770 $lasttime = filemtime($this->dir.$filename);3771 if (time()-$lasttime > $this->ttl) {3772 return false;3773 } else {3774 $fp = fopen($this->dir.$filename, 'r');3775 $size = filesize($this->dir.$filename);3776 $content = fread($fp, $size);3777 return unserialize($content);3778 }3779 }3780 return false;3781 }3782 3783 /**3784 * Set cache value3785 *3786 * @global object $CFG3787 * @global object $USER3788 * @param mixed $param3789 * @param mixed $val3790 */3791 public function set($param, $val) {3792 global $CFG, $USER;3793 $filename = 'u'.$USER->id.'_'.md5(serialize($param));3794 $fp = fopen($this->dir.$filename, 'w');3795 fwrite($fp, serialize($val));3796 fclose($fp);3797 @chmod($this->dir.$filename, $CFG->filepermissions);3798 }3799 3800 /**3801 * Remove cache files3802 *3803 * @param int $expire The number of seconds before expiry3804 */3805 public function cleanup($expire) {3806 if ($dir = opendir($this->dir)) {3807 while (false !== ($file = readdir($dir))) {3808 if(!is_dir($file) && $file != '.' && $file != '..') {3809 $lasttime = @filemtime($this->dir.$file);3810 if (time() - $lasttime > $expire) {3811 @unlink($this->dir.$file);3812 }3813 }3814 }3815 closedir($dir);3