Moodle 2.6 XRef and Diffs - Mukudu Devxref-diff.mukudu-dev.net/moodle26/lib/filelib.php.source.htmlMoodle 2.6 XRef and Diffs by Mukudu Ltd

  • Upload
    ledat

  • View
    221

  • Download
    3

Embed Size (px)

Citation preview

Moodle 2.6 XRef and Diffs

Quick Navigation

Moodle 2.6 XRef and Diffs

HomeQuick NavigationMoodle 2.6ClassesFunctionsVariablesConstantsStatistics

VersionsMoodle 3.2Moodle 3.1Moodle 3.0Moodle 2.9Moodle 2.8Moodle 2.7Moodle 2.6

ClassesFunctionsVariablesConstantsTables

Jump To

Search moodle.org's Developer Documentation

Search

/lib/ -> filelib.php (source)

[Summary view][Diff With 26-27][Diff With 26-28][Diff With 26-29][Diff With 26-30][Diff With 26-31][Diff With 26-32]

1 '-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');1607 1608 $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1609 if (empty($filetype)) {1610 $filetype = 'xxx'; // file without extension1611 }1612 if (preg_match('/^icon(\d*)$/', $element, $iconsizematch)) {1613 $iconsize = max(array(16, (int)$iconsizematch[1]));1614 $filenames = array($mimeinfo['xxx']['icon']);1615 if ($filetype != 'xxx' && isset($mimeinfo[$filetype]['icon'])) {1616 array_unshift($filenames, $mimeinfo[$filetype]['icon']);1617 }1618 // find the file with the closest size, first search for specific icon then for default icon1619 foreach ($filenames as $filename) {1620 foreach ($iconpostfixes as $size => $postfix) {1621 $fullname = $CFG->dirroot.'/pix/f/'.$filename.$postfix;1622 if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {1623 return $filename.$postfix;1624 }1625 }1626 }1627 } else if (isset($mimeinfo[$filetype][$element])) {1628 return $mimeinfo[$filetype][$element];1629 } else if (isset($mimeinfo['xxx'][$element])) {1630 return $mimeinfo['xxx'][$element]; // By default1631 } else {1632 return null;1633 }1634 }1635 1636 /**1637 * Obtains information about a filetype based on the MIME type rather than1638 * the other way around.1639 *1640 * @category files1641 * @param string $element Desired information ('extension', 'icon', 'icon-24', etc.)1642 * @param string $mimetype MIME type we're looking up1643 * @return string Requested piece of information from array1644 */1645 function mimeinfo_from_type($element, $mimetype) {1646 /* array of cached mimetype->extension associations */1647 static $cached = array();1648 $mimeinfo = & get_mimetypes_array();1649 1650 if (!array_key_exists($mimetype, $cached)) {1651 $cached[$mimetype] = null;1652 foreach($mimeinfo as $filetype => $values) {1653 if ($values['type'] == $mimetype) {1654 if ($cached[$mimetype] === null) {1655 $cached[$mimetype] = '.'.$filetype;1656 }1657 if (!empty($values['defaulticon'])) {1658 $cached[$mimetype] = '.'.$filetype;1659 break;1660 }1661 }1662 }1663 if (empty($cached[$mimetype])) {1664 $cached[$mimetype] = '.xxx';1665 }1666 }1667 if ($element === 'extension') {1668 return $cached[$mimetype];1669 } else {1670 return mimeinfo($element, $cached[$mimetype]);1671 }1672 }1673 1674 /**1675 * Return the relative icon path for a given file1676 *1677 * Usage:1678 * 1679 * // $file - instance of stored_file or file_info1680 * $icon = $OUTPUT->pix_url(file_file_icon($file))->out();1681 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($file)));1682 * 1683 * or1684 * 1685 * echo $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file));1686 * 1687 *1688 * @param stored_file|file_info|stdClass|array $file (in case of object attributes $file->filename1689 * and $file->mimetype are expected)1690 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561691 * @return string1692 */1693 function file_file_icon($file, $size = null) {1694 if (!is_object($file)) {1695 $file = (object)$file;1696 }1697 if (isset($file->filename)) {1698 $filename = $file->filename;1699 } else if (method_exists($file, 'get_filename')) {1700 $filename = $file->get_filename();1701 } else if (method_exists($file, 'get_visible_name')) {1702 $filename = $file->get_visible_name();1703 } else {1704 $filename = '';1705 }1706 if (isset($file->mimetype)) {1707 $mimetype = $file->mimetype;1708 } else if (method_exists($file, 'get_mimetype')) {1709 $mimetype = $file->get_mimetype();1710 } else {1711 $mimetype = '';1712 }1713 $mimetypes = &get_mimetypes_array();1714 if ($filename) {1715 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1716 if ($extension && !empty($mimetypes[$extension])) {1717 // if file name has known extension, return icon for this extension1718 return file_extension_icon($filename, $size);1719 }1720 }1721 return file_mimetype_icon($mimetype, $size);1722 }1723 1724 /**1725 * Return the relative icon path for a folder image1726 *1727 * Usage:1728 * 1729 * $icon = $OUTPUT->pix_url(file_folder_icon())->out();1730 * echo html_writer::empty_tag('img', array('src' => $icon));1731 * 1732 * or1733 * 1734 * echo $OUTPUT->pix_icon(file_folder_icon(32));1735 * 1736 *1737 * @param int $iconsize The size of the icon. Defaults to 16 can also be 24, 32, 48, 64, 72, 80, 96, 128, 2561738 * @return string1739 */1740 function file_folder_icon($iconsize = null) {1741 global $CFG;1742 static $iconpostfixes = array(256=>'-256', 128=>'-128', 96=>'-96', 80=>'-80', 72=>'-72', 64=>'-64', 48=>'-48', 32=>'-32', 24=>'-24', 16=>'');1743 static $cached = array();1744 $iconsize = max(array(16, (int)$iconsize));1745 if (!array_key_exists($iconsize, $cached)) {1746 foreach ($iconpostfixes as $size => $postfix) {1747 $fullname = $CFG->dirroot.'/pix/f/folder'.$postfix;1748 if ($iconsize >= $size && (file_exists($fullname.'.png') || file_exists($fullname.'.gif'))) {1749 $cached[$iconsize] = 'f/folder'.$postfix;1750 break;1751 }1752 }1753 }1754 return $cached[$iconsize];1755 }1756 1757 /**1758 * Returns the relative icon path for a given mime type1759 *1760 * This function should be used in conjunction with $OUTPUT->pix_url to produce1761 * a return the full path to an icon.1762 *1763 * 1764 * $mimetype = 'image/jpg';1765 * $icon = $OUTPUT->pix_url(file_mimetype_icon($mimetype))->out();1766 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => get_mimetype_description($mimetype)));1767 * 1768 *1769 * @category files1770 * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered1771 * to conform with that.1772 * @param string $mimetype The mimetype to fetch an icon for1773 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561774 * @return string The relative path to the icon1775 */1776 function file_mimetype_icon($mimetype, $size = NULL) {1777 return 'f/'.mimeinfo_from_type('icon'.$size, $mimetype);1778 }1779 1780 /**1781 * Returns the relative icon path for a given file name1782 *1783 * This function should be used in conjunction with $OUTPUT->pix_url to produce1784 * a return the full path to an icon.1785 *1786 * 1787 * $filename = '.jpg';1788 * $icon = $OUTPUT->pix_url(file_extension_icon($filename))->out();1789 * echo html_writer::empty_tag('img', array('src' => $icon, 'alt' => '...'));1790 * 1791 *1792 * @todo MDL-31074 When an $OUTPUT->icon method is available this function should be altered1793 * to conform with that.1794 * @todo MDL-31074 Implement $size1795 * @category files1796 * @param string $filename The filename to get the icon for1797 * @param int $size The size of the icon. Defaults to 16 can also be 24, 32, 64, 128, 2561798 * @return string1799 */1800 function file_extension_icon($filename, $size = NULL) {1801 return 'f/'.mimeinfo('icon'.$size, $filename);1802 }1803 1804 /**1805 * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the1806 * mimetypes.php language file.1807 *1808 * @param mixed $obj - instance of stored_file or file_info or array/stdClass with field1809 * 'filename' and 'mimetype', or just a string with mimetype (though it is recommended to1810 * have filename); In case of array/stdClass the field 'mimetype' is optional.1811 * @param bool $capitalise If true, capitalises first character of result1812 * @return string Text description1813 */1814 function get_mimetype_description($obj, $capitalise=false) {1815 $filename = $mimetype = '';1816 if (is_object($obj) && method_exists($obj, 'get_filename') && method_exists($obj, 'get_mimetype')) {1817 // this is an instance of stored_file1818 $mimetype = $obj->get_mimetype();1819 $filename = $obj->get_filename();1820 } else if (is_object($obj) && method_exists($obj, 'get_visible_name') && method_exists($obj, 'get_mimetype')) {1821 // this is an instance of file_info1822 $mimetype = $obj->get_mimetype();1823 $filename = $obj->get_visible_name();1824 } else if (is_array($obj) || is_object ($obj)) {1825 $obj = (array)$obj;1826 if (!empty($obj['filename'])) {1827 $filename = $obj['filename'];1828 }1829 if (!empty($obj['mimetype'])) {1830 $mimetype = $obj['mimetype'];1831 }1832 } else {1833 $mimetype = $obj;1834 }1835 $mimetypefromext = mimeinfo('type', $filename);1836 if (empty($mimetype) || $mimetypefromext !== 'document/unknown') {1837 // if file has a known extension, overwrite the specified mimetype1838 $mimetype = $mimetypefromext;1839 }1840 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));1841 if (empty($extension)) {1842 $mimetypestr = mimeinfo_from_type('string', $mimetype);1843 $extension = str_replace('.', '', mimeinfo_from_type('extension', $mimetype));1844 } else {1845 $mimetypestr = mimeinfo('string', $filename);1846 }1847 $chunks = explode('/', $mimetype, 2);1848 $chunks[] = '';1849 $attr = array(1850 'mimetype' => $mimetype,1851 'ext' => $extension,1852 'mimetype1' => $chunks[0],1853 'mimetype2' => $chunks[1],1854 );1855 $a = array();1856 foreach ($attr as $key => $value) {1857 $a[$key] = $value;1858 $a[strtoupper($key)] = strtoupper($value);1859 $a[ucfirst($key)] = ucfirst($value);1860 }1861 1862 // MIME types may include + symbol but this is not permitted in string ids.1863 $safemimetype = str_replace('+', '_', $mimetype);1864 $safemimetypestr = str_replace('+', '_', $mimetypestr);1865 if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) {1866 $result = get_string($safemimetype, 'mimetypes', (object)$a);1867 } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) {1868 $result = get_string($safemimetypestr, 'mimetypes', (object)$a);1869 } else if (get_string_manager()->string_exists('default', 'mimetypes')) {1870 $result = get_string('default', 'mimetypes', (object)$a);1871 } else {1872 $result = $mimetype;1873 }1874 if ($capitalise) {1875 $result=ucfirst($result);1876 }1877 return $result;1878 }1879 1880 /**1881 * Returns array of elements of type $element in type group(s)1882 *1883 * @param string $element name of the element we are interested in, usually 'type' or 'extension'1884 * @param string|array $groups one group or array of groups/extensions/mimetypes1885 * @return array1886 */1887 function file_get_typegroup($element, $groups) {1888 static $cached = array();1889 if (!is_array($groups)) {1890 $groups = array($groups);1891 }1892 if (!array_key_exists($element, $cached)) {1893 $cached[$element] = array();1894 }1895 $result = array();1896 foreach ($groups as $group) {1897 if (!array_key_exists($group, $cached[$element])) {1898 // retrieive and cache all elements of type $element for group $group1899 $mimeinfo = & get_mimetypes_array();1900 $cached[$element][$group] = array();1901 foreach ($mimeinfo as $extension => $value) {1902 $value['extension'] = '.'.$extension;1903 if (empty($value[$element])) {1904 continue;1905 }1906 if (($group === '.'.$extension || $group === $value['type'] ||1907 (!empty($value['groups']) && in_array($group, $value['groups']))) &&1908 !in_array($value[$element], $cached[$element][$group])) {1909 $cached[$element][$group][] = $value[$element];1910 }1911 }1912 }1913 $result = array_merge($result, $cached[$element][$group]);1914 }1915 return array_values(array_unique($result));1916 }1917 1918 /**1919 * Checks if file with name $filename has one of the extensions in groups $groups1920 *1921 * @see get_mimetypes_array()1922 * @param string $filename name of the file to check1923 * @param string|array $groups one group or array of groups to check1924 * @param bool $checktype if true and extension check fails, find the mimetype and check if1925 * file mimetype is in mimetypes in groups $groups1926 * @return bool1927 */1928 function file_extension_in_typegroup($filename, $groups, $checktype = false) {1929 $extension = pathinfo($filename, PATHINFO_EXTENSION);1930 if (!empty($extension) && in_array('.'.strtolower($extension), file_get_typegroup('extension', $groups))) {1931 return true;1932 }1933 return $checktype && file_mimetype_in_typegroup(mimeinfo('type', $filename), $groups);1934 }1935 1936 /**1937 * Checks if mimetype $mimetype belongs to one of the groups $groups1938 *1939 * @see get_mimetypes_array()1940 * @param string $mimetype1941 * @param string|array $groups one group or array of groups to check1942 * @return bool1943 */1944 function file_mimetype_in_typegroup($mimetype, $groups) {1945 return !empty($mimetype) && in_array($mimetype, file_get_typegroup('type', $groups));1946 }1947 1948 /**1949 * Requested file is not found or not accessible, does not return, terminates script1950 *1951 * @global stdClass $CFG1952 * @global stdClass $COURSE1953 */1954 function send_file_not_found() {1955 global $CFG, $COURSE;1956 send_header_404();1957 print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS??1958 }1959 /**1960 * Helper function to send correct 404 for server.1961 */1962 function send_header_404() {1963 if (substr(php_sapi_name(), 0, 3) == 'cgi') {1964 header("Status: 404 Not Found");1965 } else {1966 header('HTTP/1.0 404 not found');1967 }1968 }1969 1970 /**1971 * The readfile function can fail when files are larger than 2GB (even on 64-bit1972 * platforms). This wrapper uses readfile for small files and custom code for1973 * large ones.1974 *1975 * @param string $path Path to file1976 * @param int $filesize Size of file (if left out, will get it automatically)1977 * @return int|bool Size read (will always be $filesize) or false if failed1978 */1979 function readfile_allow_large($path, $filesize = -1) {1980 // Automatically get size if not specified.1981 if ($filesize === -1) {1982 $filesize = filesize($path);1983 }1984 if ($filesize 0) {1995 $size = min($left, 65536);1996 $buffer = fread($handle, $size);1997 if ($buffer === false) {1998 return false;1999 }2000 echo $buffer;2001 $left -= $size;2002 }2003 return $filesize;2004 }2005 }2006 2007 /**2008 * Enhanced readfile() with optional acceleration.2009 * @param string|stored_file $file2010 * @param string $mimetype2011 * @param bool $accelerate2012 * @return void2013 */2014 function readfile_accel($file, $mimetype, $accelerate) {2015 global $CFG;2016 2017 if ($mimetype === 'text/plain') {2018 // there is no encoding specified in text files, we need something consistent2019 header('Content-Type: text/plain; charset=utf-8');2020 } else {2021 header('Content-Type: '.$mimetype);2022 }2023 2024 $lastmodified = is_object($file) ? $file->get_timemodified() : filemtime($file);2025 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');2026 2027 if (is_object($file)) {2028 header('Etag: "' . $file->get_contenthash() . '"');2029 if (isset($_SERVER['HTTP_IF_NONE_MATCH']) and trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $file->get_contenthash()) {2030 header('HTTP/1.1 304 Not Modified');2031 return;2032 }2033 }2034 2035 // if etag present for stored file rely on it exclusively2036 if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (empty($_SERVER['HTTP_IF_NONE_MATCH']) or !is_object($file))) {2037 // get unixtime of request header; clip extra junk off first2038 $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]));2039 if ($since && $since >= $lastmodified) {2040 header('HTTP/1.1 304 Not Modified');2041 return;2042 }2043 }2044 2045 if ($accelerate and !empty($CFG->xsendfile)) {2046 if (empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {2047 header('Accept-Ranges: bytes');2048 } else {2049 header('Accept-Ranges: none');2050 }2051 2052 if (is_object($file)) {2053 $fs = get_file_storage();2054 if ($fs->xsendfile($file->get_contenthash())) {2055 return;2056 }2057 2058 } else {2059 require_once("$CFG->libdir/xsendfilelib.php");2060 if (xsendfile($file)) {2061 return;2062 }2063 }2064 }2065 2066 $filesize = is_object($file) ? $file->get_filesize() : filesize($file);2067 2068 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');2069 2070 if ($accelerate and empty($CFG->disablebyteserving) and $mimetype !== 'text/plain') {2071 header('Accept-Ranges: bytes');2072 2073 if (!empty($_SERVER['HTTP_RANGE']) and strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) {2074 // byteserving stuff - for acrobat reader and download accelerators2075 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.352076 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php2077 $ranges = false;2078 if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) {2079 foreach ($ranges as $key=>$value) {2080 if ($ranges[$key][1] == '') {2081 //suffix case2082 $ranges[$key][1] = $filesize - $ranges[$key][2];2083 $ranges[$key][2] = $filesize - 1;2084 } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) {2085 //fix range length2086 $ranges[$key][2] = $filesize - 1;2087 }2088 if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) {2089 //invalid byte-range ==> ignore header2090 $ranges = false;2091 break;2092 }2093 //prepare multipart header2094 $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n";2095 $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n";2096 }2097 } else {2098 $ranges = false;2099 }2100 if ($ranges) {2101 if (is_object($file)) {2102 $handle = $file->get_content_file_handle();2103 } else {2104 $handle = fopen($file, 'rb');2105 }2106 byteserving_send_file($handle, $mimetype, $ranges, $filesize);2107 }2108 }2109 } else {2110 // Do not byteserve2111 header('Accept-Ranges: none');2112 }2113 2114 header('Content-Length: '.$filesize);2115 2116 if ($filesize > 10000000) {2117 // for large files try to flush and close all buffers to conserve memory2118 while(@ob_get_level()) {2119 if (!@ob_end_flush()) {2120 break;2121 }2122 }2123 }2124 2125 // send the whole file content2126 if (is_object($file)) {2127 $file->readfile();2128 } else {2129 readfile_allow_large($file, $filesize);2130 }2131 }2132 2133 /**2134 * Similar to readfile_accel() but designed for strings.2135 * @param string $string2136 * @param string $mimetype2137 * @param bool $accelerate2138 * @return void2139 */2140 function readstring_accel($string, $mimetype, $accelerate) {2141 global $CFG;2142 2143 if ($mimetype === 'text/plain') {2144 // there is no encoding specified in text files, we need something consistent2145 header('Content-Type: text/plain; charset=utf-8');2146 } else {2147 header('Content-Type: '.$mimetype);2148 }2149 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');2150 header('Accept-Ranges: none');2151 2152 if ($accelerate and !empty($CFG->xsendfile)) {2153 $fs = get_file_storage();2154 if ($fs->xsendfile(sha1($string))) {2155 return;2156 }2157 }2158 2159 header('Content-Length: '.strlen($string));2160 echo $string;2161 }2162 2163 /**2164 * Handles the sending of temporary file to user, download is forced.2165 * File is deleted after abort or successful sending, does not return, script terminated2166 *2167 * @param string $path path to file, preferably from moodledata/temp/something; or content of file itself2168 * @param string $filename proposed file name when saving file2169 * @param bool $pathisstring If the path is string2170 */2171 function send_temp_file($path, $filename, $pathisstring=false) {2172 global $CFG;2173 2174 if (core_useragent::is_firefox()) {2175 // only FF is known to correctly save to disk before opening...2176 $mimetype = mimeinfo('type', $filename);2177 } else {2178 $mimetype = 'application/x-forcedownload';2179 }2180 2181 // close session - not needed anymore2182 \core\session\manager::write_close();2183 2184 if (!$pathisstring) {2185 if (!file_exists($path)) {2186 send_header_404();2187 print_error('filenotfound', 'error', $CFG->wwwroot.'/');2188 }2189 // executed after normal finish or abort2190 core_shutdown_manager::register_function('send_temp_file_finished', array($path));2191 }2192 2193 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2194 if (core_useragent::is_ie()) {2195 $filename = urlencode($filename);2196 }2197 2198 header('Content-Disposition: attachment; filename="'.$filename.'"');2199 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312200 header('Cache-Control: private, max-age=10, no-transform');2201 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2202 header('Pragma: ');2203 } else { //normal http - prevent caching at all cost2204 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2205 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2206 header('Pragma: no-cache');2207 }2208 2209 // send the contents - we can not accelerate this because the file will be deleted asap2210 if ($pathisstring) {2211 readstring_accel($path, $mimetype, false);2212 } else {2213 readfile_accel($path, $mimetype, false);2214 @unlink($path);2215 }2216 2217 die; //no more chars to output2218 }2219 2220 /**2221 * Internal callback function used by send_temp_file()2222 *2223 * @param string $path2224 */2225 function send_temp_file_finished($path) {2226 if (file_exists($path)) {2227 @unlink($path);2228 }2229 }2230 2231 /**2232 * Handles the sending of file data to the user's browser, including support for2233 * byteranges etc.2234 *2235 * @category files2236 * @param string $path Path of file on disk (including real filename), or actual content of file as string2237 * @param string $filename Filename to send2238 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)2239 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only2240 * @param bool $pathisstring If true (default false), $path is the content to send and not the pathname2241 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin2242 * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename2243 * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.2244 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,2245 * you must detect this case when control is returned using connection_aborted. Please not that session is closed2246 * and should not be reopened.2247 * @return null script execution stopped unless $dontdie is true2248 */2249 function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='', $dontdie=false) {2250 global $CFG, $COURSE;2251 2252 if ($dontdie) {2253 ignore_user_abort(true);2254 }2255 2256 if ($lifetime === 'default' or is_null($lifetime)) {2257 $lifetime = $CFG->filelifetime;2258 }2259 2260 \core\session\manager::write_close(); // Unlock session during file serving.2261 2262 // Use given MIME type if specified, otherwise guess it using mimeinfo.2263 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O2264 // only Firefox saves all files locally before opening when content-disposition: attachment stated2265 $isFF = core_useragent::is_firefox(); // only FF properly tested2266 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :2267 ($mimetype ? $mimetype : mimeinfo('type', $filename));2268 2269 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2270 if (core_useragent::is_ie()) {2271 $filename = rawurlencode($filename);2272 }2273 2274 if ($forcedownload) {2275 header('Content-Disposition: attachment; filename="'.$filename.'"');2276 } else if ($mimetype !== 'application/x-shockwave-flash') {2277 // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file2278 // as an upload and enforces security that may prevent the file from being loaded.2279 2280 header('Content-Disposition: inline; filename="'.$filename.'"');2281 }2282 2283 if ($lifetime > 0) {2284 $private = '';2285 if (isloggedin() and !isguestuser()) {2286 $private = ' private,';2287 }2288 $nobyteserving = false;2289 header('Cache-Control:'.$private.' max-age='.$lifetime.', no-transform');2290 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');2291 header('Pragma: ');2292 2293 } else { // Do not cache files in proxies and browsers2294 $nobyteserving = true;2295 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312296 header('Cache-Control: private, max-age=10, no-transform');2297 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2298 header('Pragma: ');2299 } else { //normal http - prevent caching at all cost2300 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2301 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2302 header('Pragma: no-cache');2303 }2304 }2305 2306 if (empty($filter)) {2307 // send the contents2308 if ($pathisstring) {2309 readstring_accel($path, $mimetype, !$dontdie);2310 } else {2311 readfile_accel($path, $mimetype, !$dontdie);2312 }2313 2314 } else {2315 // Try to put the file through filters2316 if ($mimetype == 'text/html') {2317 $options = new stdClass();2318 $options->noclean = true;2319 $options->nocache = true; // temporary workaround for MDL-51362320 $text = $pathisstring ? $path : implode('', file($path));2321 2322 $text = file_modify_html_header($text);2323 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);2324 2325 readstring_accel($output, $mimetype, false);2326 2327 } else if (($mimetype == 'text/plain') and ($filter == 1)) {2328 // only filter text if filter all files is selected2329 $options = new stdClass();2330 $options->newlines = false;2331 $options->noclean = true;2332 $text = htmlentities($pathisstring ? $path : implode('', file($path)), ENT_QUOTES, 'UTF-8');2333 $output = ''. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'';2334 2335 readstring_accel($output, $mimetype, false);2336 2337 } else {2338 // send the contents2339 if ($pathisstring) {2340 readstring_accel($path, $mimetype, !$dontdie);2341 } else {2342 readfile_accel($path, $mimetype, !$dontdie);2343 }2344 }2345 }2346 if ($dontdie) {2347 return;2348 }2349 die; //no more chars to output!!!2350 }2351 2352 /**2353 * Handles the sending of file data to the user's browser, including support for2354 * byteranges etc.2355 *2356 * The $options parameter supports the following keys:2357 * (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail)2358 * (string|null) filename - overrides the implicit filename2359 * (bool) dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.2360 * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel,2361 * you must detect this case when control is returned using connection_aborted. Please not that session is closed2362 * and should not be reopened.2363 *2364 * @category files2365 * @param stored_file $stored_file local file object2366 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)2367 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only2368 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin2369 * @param array $options additional options affecting the file serving2370 * @return null script execution stopped unless $options['dontdie'] is true2371 */2372 function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownload=false, array $options=array()) {2373 global $CFG, $COURSE;2374 2375 if (empty($options['filename'])) {2376 $filename = null;2377 } else {2378 $filename = $options['filename'];2379 }2380 2381 if (empty($options['dontdie'])) {2382 $dontdie = false;2383 } else {2384 $dontdie = true;2385 }2386 2387 if ($lifetime === 'default' or is_null($lifetime)) {2388 $lifetime = $CFG->filelifetime;2389 }2390 2391 if (!empty($options['preview'])) {2392 // replace the file with its preview2393 $fs = get_file_storage();2394 $preview_file = $fs->get_file_preview($stored_file, $options['preview']);2395 if (!$preview_file) {2396 // unable to create a preview of the file, send its default mime icon instead2397 if ($options['preview'] === 'tinyicon') {2398 $size = 24;2399 } else if ($options['preview'] === 'thumb') {2400 $size = 90;2401 } else {2402 $size = 256;2403 }2404 $fileicon = file_file_icon($stored_file, $size);2405 send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');2406 } else {2407 // preview images have fixed cache lifetime and they ignore forced download2408 // (they are generated by GD and therefore they are considered reasonably safe).2409 $stored_file = $preview_file;2410 $lifetime = DAYSECS;2411 $filter = 0;2412 $forcedownload = false;2413 }2414 }2415 2416 // handle external resource2417 if ($stored_file && $stored_file->is_external_file() && !isset($options['sendcachedexternalfile'])) {2418 $stored_file->send_file($lifetime, $filter, $forcedownload, $options);2419 die;2420 }2421 2422 if (!$stored_file or $stored_file->is_directory()) {2423 // nothing to serve2424 if ($dontdie) {2425 return;2426 }2427 die;2428 }2429 2430 if ($dontdie) {2431 ignore_user_abort(true);2432 }2433 2434 \core\session\manager::write_close(); // Unlock session during file serving.2435 2436 // Use given MIME type if specified, otherwise guess it using mimeinfo.2437 // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O2438 // only Firefox saves all files locally before opening when content-disposition: attachment stated2439 $filename = is_null($filename) ? $stored_file->get_filename() : $filename;2440 $isFF = core_useragent::is_firefox(); // only FF properly tested2441 $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' :2442 ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename));2443 2444 // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup2445 if (core_useragent::is_ie()) {2446 $filename = rawurlencode($filename);2447 }2448 2449 if ($forcedownload) {2450 header('Content-Disposition: attachment; filename="'.$filename.'"');2451 } else if ($mimetype !== 'application/x-shockwave-flash') {2452 // If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file2453 // as an upload and enforces security that may prevent the file from being loaded.2454 2455 header('Content-Disposition: inline; filename="'.$filename.'"');2456 }2457 2458 if ($lifetime > 0) {2459 $private = '';2460 if (isloggedin() and !isguestuser()) {2461 $private = ' private,';2462 }2463 header('Cache-Control:'.$private.' max-age='.$lifetime.', no-transform');2464 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');2465 header('Pragma: ');2466 2467 } else { // Do not cache files in proxies and browsers2468 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB3164312469 header('Cache-Control: private, max-age=10, no-transform');2470 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2471 header('Pragma: ');2472 } else { //normal http - prevent caching at all cost2473 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');2474 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');2475 header('Pragma: no-cache');2476 }2477 }2478 2479 if (empty($filter)) {2480 // send the contents2481 readfile_accel($stored_file, $mimetype, !$dontdie);2482 2483 } else { // Try to put the file through filters2484 if ($mimetype == 'text/html') {2485 $options = new stdClass();2486 $options->noclean = true;2487 $options->nocache = true; // temporary workaround for MDL-51362488 $text = $stored_file->get_content();2489 $text = file_modify_html_header($text);2490 $output = format_text($text, FORMAT_HTML, $options, $COURSE->id);2491 2492 readstring_accel($output, $mimetype, false);2493 2494 } else if (($mimetype == 'text/plain') and ($filter == 1)) {2495 // only filter text if filter all files is selected2496 $options = new stdClass();2497 $options->newlines = false;2498 $options->noclean = true;2499 $text = $stored_file->get_content();2500 $output = ''. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'';2501 2502 readstring_accel($output, $mimetype, false);2503 2504 } else { // Just send it out raw2505 readfile_accel($stored_file, $mimetype, !$dontdie);2506 }2507 }2508 if ($dontdie) {2509 return;2510 }2511 die; //no more chars to output!!!2512 }2513 2514 /**2515 * Retrieves an array of records from a CSV file and places2516 * them into a given table structure2517 *2518 * @global stdClass $CFG2519 * @global moodle_database $DB2520 * @param string $file The path to a CSV file2521 * @param string $table The table to retrieve columns from2522 * @return bool|array Returns an array of CSV records or false2523 */2524 function get_records_csv($file, $table) {2525 global $CFG, $DB;2526 2527 if (!$metacolumns = $DB->get_columns($table)) {2528 return false;2529 }2530 2531 if(!($handle = @fopen($file, 'r'))) {2532 print_error('get_records_csv failed to open '.$file);2533 }2534 2535 $fieldnames = fgetcsv($handle, 4096);2536 if(empty($fieldnames)) {2537 fclose($handle);2538 return false;2539 }2540 2541 $columns = array();2542 2543 foreach($metacolumns as $metacolumn) {2544 $ord = array_search($metacolumn->name, $fieldnames);2545 if(is_int($ord)) {2546 $columns[$metacolumn->name] = $ord;2547 }2548 }2549 2550 $rows = array();2551 2552 while (($data = fgetcsv($handle, 4096)) !== false) {2553 $item = new stdClass;2554 foreach($columns as $name => $ord) {2555 $item->$name = $data[$ord];2556 }2557 $rows[] = $item;2558 }2559 2560 fclose($handle);2561 return $rows;2562 }2563 2564 /**2565 * Create a file with CSV contents2566 *2567 * @global stdClass $CFG2568 * @global moodle_database $DB2569 * @param string $file The file to put the CSV content into2570 * @param array $records An array of records to write to a CSV file2571 * @param string $table The table to get columns from2572 * @return bool success2573 */2574 function put_records_csv($file, $records, $table = NULL) {2575 global $CFG, $DB;2576 2577 if (empty($records)) {2578 return true;2579 }2580 2581 $metacolumns = NULL;2582 if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {2583 return false;2584 }2585 2586 echo "x";2587 2588 if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {2589 print_error('put_records_csv failed to open '.$file);2590 }2591 2592 $proto = reset($records);2593 if(is_object($proto)) {2594 $fields_records = array_keys(get_object_vars($proto));2595 }2596 else if(is_array($proto)) {2597 $fields_records = array_keys($proto);2598 }2599 else {2600 return false;2601 }2602 echo "x";2603 2604 if(!empty($metacolumns)) {2605 $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);2606 $fields = array_intersect($fields_records, $fields_table);2607 }2608 else {2609 $fields = $fields_records;2610 }2611 2612 fwrite($fp, implode(',', $fields));2613 fwrite($fp, "\r\n");2614 2615 foreach($records as $record) {2616 $array = (array)$record;2617 $values = array();2618 foreach($fields as $field) {2619 if(strpos($array[$field], ',')) {2620 $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';2621 }2622 else {2623 $values[] = $array[$field];2624 }2625 }2626 fwrite($fp, implode(',', $values)."\r\n");2627 }2628 2629 fclose($fp);2630 @chmod($CFG->tempdir.'/'.$file, $CFG->filepermissions);2631 return true;2632 }2633 2634 2635 /**2636 * Recursively delete the file or folder with path $location. That is,2637 * if it is a file delete it. If it is a folder, delete all its content2638 * then delete it. If $location does not exist to start, that is not2639 * considered an error.2640 *2641 * @param string $location the path to remove.2642 * @return bool2643 */2644 function fulldelete($location) {2645 if (empty($location)) {2646 // extra safety against wrong param2647 return false;2648 }2649 if (is_dir($location)) {2650 if (!$currdir = opendir($location)) {2651 return false;2652 }2653 while (false !== ($file = readdir($currdir))) {2654 if ($file ".." && $file ".") {2655 $fullfile = $location."/".$file;2656 if (is_dir($fullfile)) {2657 if (!fulldelete($fullfile)) {2658 return false;2659 }2660 } else {2661 if (!unlink($fullfile)) {2662 return false;2663 }2664 }2665 }2666 }2667 closedir($currdir);2668 if (! rmdir($location)) {2669 return false;2670 }2671 2672 } else if (file_exists($location)) {2673 if (!unlink($location)) {2674 return false;2675 }2676 }2677 return true;2678 }2679 2680 /**2681 * Send requested byterange of file.2682 *2683 * @param resource $handle A file handle2684 * @param string $mimetype The mimetype for the output2685 * @param array $ranges An array of ranges to send2686 * @param string $filesize The size of the content if only one range is used2687 */2688 function byteserving_send_file($handle, $mimetype, $ranges, $filesize) {2689 // better turn off any kind of compression and buffering2690 @ini_set('zlib.output_compression', 'Off');2691 2692 $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB!2693 if ($handle === false) {2694 die;2695 }2696 if (count($ranges) == 1) { //only one range requested2697 $length = $ranges[0][2] - $ranges[0][1] + 1;2698 header('HTTP/1.1 206 Partial content');2699 header('Content-Length: '.$length);2700 header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize);2701 header('Content-Type: '.$mimetype);2702 2703 while(@ob_get_level()) {2704 if (!@ob_end_flush()) {2705 break;2706 }2707 }2708 2709 fseek($handle, $ranges[0][1]);2710 while (!feof($handle) && $length > 0) {2711 @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk2712 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));2713 echo $buffer;2714 flush();2715 $length -= strlen($buffer);2716 }2717 fclose($handle);2718 die;2719 } else { // multiple ranges requested - not tested much2720 $totallength = 0;2721 foreach($ranges as $range) {2722 $totallength += strlen($range[0]) + $range[2] - $range[1] + 1;2723 }2724 $totallength += strlen("\r\n--".BYTESERVING_BOUNDARY."--\r\n");2725 header('HTTP/1.1 206 Partial content');2726 header('Content-Length: '.$totallength);2727 header('Content-Type: multipart/byteranges; boundary='.BYTESERVING_BOUNDARY);2728 2729 while(@ob_get_level()) {2730 if (!@ob_end_flush()) {2731 break;2732 }2733 }2734 2735 foreach($ranges as $range) {2736 $length = $range[2] - $range[1] + 1;2737 echo $range[0];2738 fseek($handle, $range[1]);2739 while (!feof($handle) && $length > 0) {2740 @set_time_limit(60*60); //reset time limit to 60 min - should be enough for 1 MB chunk2741 $buffer = fread($handle, ($chunksize < $length ? $chunksize : $length));2742 echo $buffer;2743 flush();2744 $length -= strlen($buffer);2745 }2746 }2747 echo "\r\n--".BYTESERVING_BOUNDARY."--\r\n";2748 fclose($handle);2749 die;2750 }2751 }2752 2753 /**2754 * add includes (js and css) into uploaded files2755 * before returning them, useful for themes and utf.js includes2756 *2757 * @global stdClass $CFG2758 * @param string $text text to search and replace2759 * @return string text with added head includes2760 * @todo MDL-211202761 */2762 function file_modify_html_header($text) {2763 // first look for tag2764 global $CFG;2765 2766 $stylesheetshtml = '';2767 /*2768 foreach ($CFG->stylesheets as $stylesheet) {2769 //TODO: MDL-211202770 $stylesheetshtml .= ''."\n";2771 }2772 */2773 // TODO The code below is actually a waste of CPU. When MDL-29738 will be implemented it should be re-evaluated too.2774 2775 preg_match('/\|\/', $text, $matches);2776 if ($matches) {2777 $replacement = ''.$stylesheetshtml;2778 $text = preg_replace('/\|\/', $replacement, $text, 1);2779 return $text;2780 }2781 2782 // if not, look for tag, and stick right after2783 preg_match('/\|\/', $text, $matches);2784 if ($matches) {2785 // replace tag with includes2786 $replacement = ''."\n".''.$stylesheetshtml.'';2787 $text = preg_replace('/\|\/', $replacement, $text, 1);2788 return $text;2789 }2790 2791 // if not, look for tag, and stick before body2792 preg_match('/\|\/', $text, $matches);2793 if ($matches) {2794 $replacement = ''.$stylesheetshtml.''."\n".'';2795 $text = preg_replace('/\|\/', $replacement, $text, 1);2796 return $text;2797 }2798 2799 // if not, just stick a tag at the beginning2800 $text = ''.$stylesheetshtml.''."\n".$text;2801 return $text;2802 }2803 2804 /**2805 * RESTful cURL class2806 *2807 * This is a wrapper class for curl, it is quite easy to use:2808 * 2809 * $c = new curl;2810 * // enable cache2811 * $c = new curl(array('cache'=>true));2812 * // enable cookie2813 * $c = new curl(array('cookie'=>true));2814 * // enable proxy2815 * $c = new curl(array('proxy'=>true));2816 *2817 * // HTTP GET Method2818 * $html = $c->get('http://example.com');2819 * // HTTP POST Method2820 * $html = $c->post('http://example.com/', array('q'=>'words', 'name'=>'moodle'));2821 * // HTTP PUT Method2822 * $html = $c->put('http://example.com/', array('file'=>'/var/www/test.txt');2823 * 2824 *2825 * @package core_files2826 * @category files2827 * @copyright Dongsheng Cai 2828 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License2829 */2830 class curl {2831 /** @var bool Caches http request contents */2832 public $cache = false;2833 /** @var bool Uses proxy, null means automatic based on URL */2834 public $proxy = null;2835 /** @var string library version */2836 public $version = '0.4 dev';2837 /** @var array http's response */2838 public $response = array();2839 /** @var array Raw response headers, needed for BC in download_file_content(). */2840 public $rawresponse = array();2841 /** @var array http header */2842 public $header = array();2843 /** @var string cURL information */2844 public $info;2845 /** @var string error */2846 public $error;2847 /** @var int error code */2848 public $errno;2849 /** @var bool use workaround for open_basedir restrictions, to be changed from unit tests only! */2850 public $emulateredirects = null;2851 2852 /** @var array cURL options */2853 private $options;2854 /** @var string Proxy host */2855 private $proxy_host = '';2856 /** @var string Proxy auth */2857 private $proxy_auth = '';2858 /** @var string Proxy type */2859 private $proxy_type = '';2860 /** @var bool Debug mode on */2861 private $debug = false;2862 /** @var bool|string Path to cookie file */2863 private $cookie = false;2864 /** @var bool tracks multiple headers in response - redirect detection */2865 private $responsefinished = false;2866 2867 /**2868 * Curl constructor.2869 *2870 * Allowed settings are:2871 * proxy: (bool) use proxy server, null means autodetect non-local from url2872 * debug: (bool) use debug output2873 * cookie: (string) path to cookie file, false if none2874 * cache: (bool) use cache2875 * module_cache: (string) type of cache2876 *2877 * @param array $settings2878 */2879 public function __construct($settings = array()) {2880 global $CFG;2881 if (!function_exists('curl_init')) {2882 $this->error = 'cURL module must be enabled!';2883 trigger_error($this->error, E_USER_ERROR);2884 return false;2885 }2886 2887 // All settings of this class should be init here.2888 $this->resetopt();2889 if (!empty($settings['debug'])) {2890 $this->debug = true;2891 }2892 if (!empty($settings['cookie'])) {2893 if($settings['cookie'] === true) {2894 $this->cookie = $CFG->dataroot.'/curl_cookie.txt';2895 } else {2896 $this->cookie = $settings['cookie'];2897 }2898 }2899 if (!empty($settings['cache'])) {2900 if (class_exists('curl_cache')) {2901 if (!empty($settings['module_cache'])) {2902 $this->cache = new curl_cache($settings['module_cache']);2903 } else {2904 $this->cache = new curl_cache('misc');2905 }2906 }2907 }2908 if (!empty($CFG->proxyhost)) {2909 if (empty($CFG->proxyport)) {2910 $this->proxy_host = $CFG->proxyhost;2911 } else {2912 $this->proxy_host = $CFG->proxyhost.':'.$CFG->proxyport;2913 }2914 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {2915 $this->proxy_auth = $CFG->proxyuser.':'.$CFG->proxypassword;2916 $this->setopt(array(2917 'proxyauth'=> CURLAUTH_BASIC | CURLAUTH_NTLM,2918 'proxyuserpwd'=>$this->proxy_auth));2919 }2920 if (!empty($CFG->proxytype)) {2921 if ($CFG->proxytype == 'SOCKS5') {2922 $this->proxy_type = CURLPROXY_SOCKS5;2923 } else {2924 $this->proxy_type = CURLPROXY_HTTP;2925 $this->setopt(array('httpproxytunnel'=>false));2926 }2927 $this->setopt(array('proxytype'=>$this->proxy_type));2928 }2929 2930 if (isset($settings['proxy'])) {2931 $this->proxy = $settings['proxy'];2932 }2933 } else {2934 $this->proxy = false;2935 }2936 2937 if (!isset($this->emulateredirects)) {2938 $this->emulateredirects = (ini_get('open_basedir') or ini_get('safe_mode'));2939 }2940 }2941 2942 /**2943 * Resets the CURL options that have already been set2944 */2945 public function resetopt() {2946 $this->options = array();2947 $this->options['CURLOPT_USERAGENT'] = 'MoodleBot/1.0';2948 // True to include the header in the output2949 $this->options['CURLOPT_HEADER'] = 0;2950 // True to Exclude the body from the output2951 $this->options['CURLOPT_NOBODY'] = 0;2952 // Redirect ny default.2953 $this->options['CURLOPT_FOLLOWLOCATION'] = 1;2954 $this->options['CURLOPT_MAXREDIRS'] = 10;2955 $this->options['CURLOPT_ENCODING'] = '';2956 // TRUE to return the transfer as a string of the return2957 // value of curl_exec() instead of outputting it out directly.2958 $this->options['CURLOPT_RETURNTRANSFER'] = 1;2959 $this->options['CURLOPT_SSL_VERIFYPEER'] = 0;2960 $this->options['CURLOPT_SSL_VERIFYHOST'] = 2;2961 $this->options['CURLOPT_CONNECTTIMEOUT'] = 30;2962 2963 if ($cacert = self::get_cacert()) {2964 $this->options['CURLOPT_CAINFO'] = $cacert;2965 }2966 }2967 2968 /**2969 * Get the location of ca certificates.2970 * @return string absolute file path or empty if default used2971 */2972 public static function get_cacert() {2973 global $CFG;2974 2975 // Bundle in dataroot always wins.2976 if (is_readable("$CFG->dataroot/moodleorgca.crt")) {2977 return realpath("$CFG->dataroot/moodleorgca.crt");2978 }2979 2980 // Next comes the default from php.ini2981 $cacert = ini_get('curl.cainfo');2982 if (!empty($cacert) and is_readable($cacert)) {2983 return realpath($cacert);2984 }2985 2986 // Windows PHP does not have any certs, we need to use something.2987 if ($CFG->ostype === 'WINDOWS') {2988 if (is_readable("$CFG->libdir/cacert.pem")) {2989 return realpath("$CFG->libdir/cacert.pem");2990 }2991 }2992 2993 // Use default, this should work fine on all properly configured *nix systems.2994 return null;2995 }2996 2997 /**2998 * Reset Cookie2999 */3000 public function resetcookie() {3001 if (!empty($this->cookie)) {3002 if (is_file($this->cookie)) {3003 $fp = fopen($this->cookie, 'w');3004 if (!empty($fp)) {3005 fwrite($fp, '');3006 fclose($fp);3007 }3008 }3009 }3010 }3011 3012 /**3013 * Set curl options.3014 *3015 * Do not use the curl constants to define the options, pass a string3016 * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass3017 * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method.3018 *3019 * @param array $options If array is null, this function will reset the options to default value.3020 * @return void3021 * @throws coding_exception If an option uses constant value instead of option name.3022 */3023 public function setopt($options = array()) {3024 if (is_array($options)) {3025 foreach ($options as $name => $val) {3026 if (!is_string($name)) {3027 throw new coding_exception('Curl options should be defined using strings, not constant values.');3028 }3029 if (stripos($name, 'CURLOPT_') === false) {3030 $name = strtoupper('CURLOPT_'.$name);3031 } else {3032 $name = strtoupper($name);3033 }3034 $this->options[$name] = $val;3035 }3036 }3037 }3038 3039 /**3040 * Reset http method3041 */3042 public function cleanopt() {3043 unset($this->options['CURLOPT_HTTPGET']);3044 unset($this->options['CURLOPT_POST']);3045 unset($this->options['CURLOPT_POSTFIELDS']);3046 unset($this->options['CURLOPT_PUT']);3047 unset($this->options['CURLOPT_INFILE']);3048 unset($this->options['CURLOPT_INFILESIZE']);3049 unset($this->options['CURLOPT_CUSTOMREQUEST']);3050 unset($this->options['CURLOPT_FILE']);3051 }3052 3053 /**3054 * Resets the HTTP Request headers (to prepare for the new request)3055 */3056 public function resetHeader() {3057 $this->header = array();3058 }3059 3060 /**3061 * Set HTTP Request Header3062 *3063 * @param array $header3064 */3065 public function setHeader($header) {3066 if (is_array($header)) {3067 foreach ($header as $v) {3068 $this->setHeader($v);3069 }3070 } else {3071 // Remove newlines, they are not allowed in headers.3072 $this->header[] = preg_replace('/[\r\n]/', '', $header);3073 }3074 }3075 3076 /**3077 * Get HTTP Response Headers3078 * @return array of arrays3079 */3080 public function getResponse() {3081 return $this->response;3082 }3083 3084 /**3085 * Get raw HTTP Response Headers3086 * @return array of strings3087 */3088 public function get_raw_response() {3089 return $this->rawresponse;3090 }3091 3092 /**3093 * private callback function3094 * Formatting HTTP Response Header3095 *3096 * We only keep the last headers returned. For example during a redirect the3097 * redirect headers will not appear in {@link self::getResponse()}, if you need3098 * to use those headers, refer to {@link self::get_raw_response()}.3099 *3100 * @param resource $ch Apparently not used3101 * @param string $header3102 * @return int The strlen of the header3103 */3104 private function formatHeader($ch, $header) {3105 $this->rawresponse[] = $header;3106 3107 if (trim($header, "\r\n") === '') {3108 // This must be the last header.3109 $this->responsefinished = true;3110 }3111 3112 if (strlen($header) > 2) {3113 if ($this->responsefinished) {3114 // We still have headers after the supposedly last header, we must be3115 // in a redirect so let's empty the response to keep the last headers.3116 $this->responsefinished = false;3117 $this->response = array();3118 }3119 list($key, $value) = explode(" ", rtrim($header, "\r\n"), 2);3120 $key = rtrim($key, ':');3121 if (!empty($this->response[$key])) {3122 if (is_array($this->response[$key])) {3123 $this->response[$key][] = $value;3124 } else {3125 $tmp = $this->response[$key];3126 $this->response[$key] = array();3127 $this->response[$key][] = $tmp;3128 $this->response[$key][] = $value;3129 3130 }3131 } else {3132 $this->response[$key] = $value;3133 }3134 }3135 return strlen($header);3136 }3137 3138 /**3139 * Set options for individual curl instance3140 *3141 * @param resource $curl A curl handle3142 * @param array $options3143 * @return resource The curl handle3144 */3145 private function apply_opt($curl, $options) {3146 // Some more security first.3147 if (defined('CURLOPT_PROTOCOLS')) {3148 $this->options['CURLOPT_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);3149 }3150 if (defined('CURLOPT_REDIR_PROTOCOLS')) {3151 $this->options['CURLOPT_REDIR_PROTOCOLS'] = (CURLPROTO_HTTP | CURLPROTO_HTTPS);3152 }3153 3154 // Clean up3155 $this->cleanopt();3156 // set cookie3157 if (!empty($this->cookie) || !empty($options['cookie'])) {3158 $this->setopt(array('cookiejar'=>$this->cookie,3159 'cookiefile'=>$this->cookie3160 ));3161 }3162 3163 // Bypass proxy if required.3164 if ($this->proxy === null) {3165 if (!empty($this->options['CURLOPT_URL']) and is_proxybypass($this->options['CURLOPT_URL'])) {3166 $proxy = false;3167 } else {3168 $proxy = true;3169 }3170 } else {3171 $proxy = (bool)$this->proxy;3172 }3173 3174 // Set proxy.3175 if ($proxy) {3176 $options['CURLOPT_PROXY'] = $this->proxy_host;3177 } else {3178 unset($this->options['CURLOPT_PROXY']);3179 }3180 3181 $this->setopt($options);3182 // reset before set options3183 curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this,'formatHeader'));3184 // set headers3185 if (empty($this->header)) {3186 $this->setHeader(array(3187 'User-Agent: MoodleBot/1.0',3188 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',3189 'Connection: keep-alive'3190 ));3191 }3192 curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header);3193 3194 if ($this->debug) {3195 echo 'Options';3196 var_dump($this->options);3197 echo 'Header';3198 var_dump($this->header);3199 }3200 3201 // Do not allow infinite redirects.3202 if (!isset($this->options['CURLOPT_MAXREDIRS'])) {3203 $this->options['CURLOPT_MAXREDIRS'] = 0;3204 } else if ($this->options['CURLOPT_MAXREDIRS'] > 100) {3205 $this->options['CURLOPT_MAXREDIRS'] = 100;3206 } else {3207 $this->options['CURLOPT_MAXREDIRS'] = (int)$this->options['CURLOPT_MAXREDIRS'];3208 }3209 3210 // Make sure we always know if redirects expected.3211 if (!isset($this->options['CURLOPT_FOLLOWLOCATION'])) {3212 $this->options['CURLOPT_FOLLOWLOCATION'] = 0;3213 }3214 3215 // Set options.3216 foreach($this->options as $name => $val) {3217 if ($name === 'CURLOPT_PROTOCOLS' or $name === 'CURLOPT_REDIR_PROTOCOLS') {3218 // These can not be changed, sorry.3219 continue;3220 }3221 if ($name === 'CURLOPT_FOLLOWLOCATION' and $this->emulateredirects) {3222 // The redirects are emulated elsewhere.3223 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0);3224 continue;3225 }3226 $name = constant($name);3227 curl_setopt($curl, $name, $val);3228 }3229 3230 return $curl;3231 }3232 3233 /**3234 * Download multiple files in parallel3235 *3236 * Calls {@link multi()} with specific download headers3237 *3238 * 3239 * $c = new curl();3240 * $file1 = fopen('a', 'wb');3241 * $file2 = fopen('b', 'wb');3242 * $c->download(array(3243 * array('url'=>'http://localhost/', 'file'=>$file1),3244 * array('url'=>'http://localhost/20/', 'file'=>$file2)3245 * ));3246 * fclose($file1);3247 * fclose($file2);3248 * 3249 *3250 * or3251 *3252 * 3253 * $c = new curl();3254 * $c->download(array(3255 * array('url'=>'http://localhost/', 'filepath'=>'/tmp/file1.tmp'),3256 * array('url'=>'http://localhost/20/', 'filepath'=>'/tmp/file2.tmp')3257 * ));3258 * 3259 *3260 * @param array $requests An array of files to request {3261 * url => url to download the file [required]3262 * file => file handler, or3263 * filepath => file path3264 * }3265 * If 'file' and 'filepath' parameters are both specified in one request, the3266 * open file handle in the 'file' parameter will take precedence and 'filepath'3267 * will be ignored.3268 *3269 * @param array $options An array of options to set3270 * @return array An array of results3271 */3272 public function download($requests, $options = array()) {3273 $options['RETURNTRANSFER'] = false;3274 return $this->multi($requests, $options);3275 }3276 3277 /**3278 * Multi HTTP Requests3279 * This function could run multi-requests in parallel.3280 *3281 * @param array $requests An array of files to request3282 * @param array $options An array of options to set3283 * @return array An array of results3284 */3285 protected function multi($requests, $options = array()) {3286 $count = count($requests);3287 $handles = array();3288 $results = array();3289 $main = curl_multi_init();3290 for ($i = 0; $i < $count; $i++) {3291 if (!empty($requests[$i]['filepath']) and empty($requests[$i]['file'])) {3292 // open file3293 $requests[$i]['file'] = fopen($requests[$i]['filepath'], 'w');3294 $requests[$i]['auto-handle'] = true;3295 }3296 foreach($requests[$i] as $n=>$v) {3297 $options[$n] = $v;3298 }3299 $handles[$i] = curl_init($requests[$i]['url']);3300 $this->apply_opt($handles[$i], $options);3301 curl_multi_add_handle($main, $handles[$i]);3302 }3303 $running = 0;3304 do {3305 curl_multi_exec($main, $running);3306 } while($running > 0);3307 for ($i = 0; $i < $count; $i++) {3308 if (!empty($options['CURLOPT_RETURNTRANSFER'])) {3309 $results[] = true;3310 } else {3311 $results[] = curl_multi_getcontent($handles[$i]);3312 }3313 curl_multi_remove_handle($main, $handles[$i]);3314 }3315 curl_multi_close($main);3316 3317 for ($i = 0; $i < $count; $i++) {3318 if (!empty($requests[$i]['filepath']) and !empty($requests[$i]['auto-handle'])) {3319 // close file handler if file is opened in this function3320 fclose($requests[$i]['file']);3321 }3322 }3323 return $results;3324 }3325 3326 /**3327 * Single HTTP Request3328 *3329 * @param string $url The URL to request3330 * @param array $options3331 * @return bool3332 */3333 protected function request($url, $options = array()) {3334 // Set the URL as a curl option.3335 $this->setopt(array('CURLOPT_URL' => $url));3336 3337 // Create curl instance.3338 $curl = curl_init();3339 3340 // Reset here so that the data is valid when result returned from cache.3341 $this->info = array();3342 $this->error = '';3343 $this->errno = 0;3344 $this->response = array();3345 $this->rawresponse = array();3346 $this->responsefinished = false;3347 3348 $this->apply_opt($curl, $options);3349 if ($this->cache && $ret = $this->cache->get($this->options)) {3350 return $ret;3351 }3352 3353 $ret = curl_exec($curl);3354 $this->info = curl_getinfo($curl);3355 $this->error = curl_error($curl);3356 $this->errno = curl_errno($curl);3357 // Note: $this->response and $this->rawresponse are filled by $hits->formatHeader callback.3358 3359 if ($this->emulateredirects and $this->options['CURLOPT_FOLLOWLOCATION'] and $this->info['http_code'] != 200) {3360 $redirects = 0;3361 3362 while($redirects options['CURLOPT_MAXREDIRS']) {3363 3364 if ($this->info['http_code'] == 301) {3365 // Moved Permanently - repeat the same request on new URL.3366 3367 } else if ($this->info['http_code'] == 302) {3368 // Found - the standard redirect - repeat the same request on new URL.3369 3370 } else if ($this->info['http_code'] == 303) {3371 // 303 See Other - repeat only if GET, do not bother with POSTs.3372 if (empty($this->options['CURLOPT_HTTPGET'])) {3373 break;3374 }3375 3376 } else if ($this->info['http_code'] == 307) {3377 // Temporary Redirect - must repeat using the same request type.3378 3379 } else if ($this->info['http_code'] == 308) {3380 // Permanent Redirect - must repeat using the same request type.3381 3382 } else {3383 // Some other http code means do not retry!3384 break;3385 }3386 3387 $redirects++;3388 3389 $redirecturl = null;3390 if (isset($this->info['redirect_url'])) {3391 if (preg_match('|^https?://|i', $this->info['redirect_url'])) {3392 $redirecturl = $this->info['redirect_url'];3393 }3394 }3395 if (!$redirecturl) {3396 foreach ($this->response as $k => $v) {3397 if (strtolower($k) === 'location') {3398 $redirecturl = $v;3399 break;3400 }3401 }3402 if (preg_match('|^https?://|i', $redirecturl)) {3403 // Great, this is the correct location format!3404 3405 } else if ($redirecturl) {3406 $current = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);3407 if (strpos($redirecturl, '/') === 0) {3408 // Relative to server root - just guess.3409 $pos = strpos('/', $current, 8);3410 if ($pos === false) {3411 $redirecturl = $current.$redirecturl;3412 } else {3413 $redirecturl = substr($current, 0, $pos).$redirecturl;3414 }3415 } else {3416 // Relative to current script.3417 $redirecturl = dirname($current).'/'.$redirecturl;3418 }3419 }3420 }3421 3422 curl_setopt($curl, CURLOPT_URL, $redirecturl);3423 $ret = curl_exec($curl);3424 3425 $this->info = curl_getinfo($curl);3426 $this->error = curl_error($curl);3427 $this->errno = curl_errno($curl);3428 3429 $this->info['redirect_count'] = $redirects;3430 3431 if ($this->info['http_code'] === 200) {3432 // Finally this is what we wanted.3433 break;3434 }3435 if ($this->errno != CURLE_OK) {3436 // Something wrong is going on.3437 break;3438 }3439 }3440 if ($redirects > $this->options['CURLOPT_MAXREDIRS']) {3441 $this->errno = CURLE_TOO_MANY_REDIRECTS;3442 $this->error = 'Maximum ('.$this->options['CURLOPT_MAXREDIRS'].') redirects followed';3443 }3444 }3445 3446 if ($this->cache) {3447 $this->cache->set($this->options, $ret);3448 }3449 3450 if ($this->debug) {3451 echo 'Return Data';3452 var_dump($ret);3453 echo 'Info';3454 var_dump($this->info);3455 echo 'Error';3456 var_dump($this->error);3457 }3458 3459 curl_close($curl);3460 3461 if (empty($this->error)) {3462 return $ret;3463 } else {3464 return $this->error;3465 // exception is not ajax friendly3466 //throw new moodle_exception($this->error, 'curl');3467 }3468 }3469 3470 /**3471 * HTTP HEAD method3472 *3473 * @see request()3474 *3475 * @param string $url3476 * @param array $options3477 * @return bool3478 */3479 public function head($url, $options = array()) {3480 $options['CURLOPT_HTTPGET'] = 0;3481 $options['CURLOPT_HEADER'] = 1;3482 $options['CURLOPT_NOBODY'] = 1;3483 return $this->request($url, $options);3484 }3485 3486 /**3487 * HTTP POST method3488 *3489 * @param string $url3490 * @param array|string $params3491 * @param array $options3492 * @return bool3493 */3494 public function post($url, $params = '', $options = array()) {3495 $options['CURLOPT_POST'] = 1;3496 if (is_array($params)) {3497 $this->_tmp_file_post_params = array();3498 foreach ($params as $key => $value) {3499 if ($value instanceof stored_file) {3500 $value->add_to_curl_request($this, $key);3501 } else {3502 $this->_tmp_file_post_params[$key] = $value;3503 }3504 }3505 $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params;3506 unset($this->_tmp_file_post_params);3507 } else {3508 // $params is the raw post data3509 $options['CURLOPT_POSTFIELDS'] = $params;3510 }3511 return $this->request($url, $options);3512 }3513 3514 /**3515 * HTTP GET method3516 *3517 * @param string $url3518 * @param array $params3519 * @param array $options3520 * @return bool3521 */3522 public function get($url, $params = array(), $options = array()) {3523 $options['CURLOPT_HTTPGET'] = 1;3524 3525 if (!empty($params)) {3526 $url .= (stripos($url, '?') !== false) ? '&' : '?';3527 $url .= http_build_query($params, '', '&');3528 }3529 return $this->request($url, $options);3530 }3531 3532 /**3533 * Downloads one file and writes it to the specified file handler3534 *3535 * 3536 * $c = new curl();3537 * $file = fopen('savepath', 'w');3538 * $result = $c->download_one('http://localhost/', null,3539 * array('file' => $file, 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));3540 * fclose($file);3541 * $download_info = $c->get_info();3542 * if ($result === true) {3543 * // file downloaded successfully3544 * } else {3545 * $error_text = $result;3546 * $error_code = $c->get_errno();3547 * }3548 * 3549 *3550 * 3551 * $c = new curl();3552 * $result = $c->download_one('http://localhost/', null,3553 * array('filepath' => 'savepath', 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));3554 * // ... see above, no need to close handle and remove file if unsuccessful3555 * 3556 *3557 * @param string $url3558 * @param array|null $params key-value pairs to be added to $url as query string3559 * @param array $options request options. Must include either 'file' or 'filepath'3560 * @return bool|string true on success or error string on failure3561 */3562 public function download_one($url, $params, $options = array()) {3563 $options['CURLOPT_HTTPGET'] = 1;3564 if (!empty($params)) {3565 $url .= (stripos($url, '?') !== false) ? '&' : '?';3566 $url .= http_build_query($params, '', '&');3567 }3568 if (!empty($options['filepath']) && empty($options['file'])) {3569 // open file3570 if (!($options['file'] = fopen($options['filepath'], 'w'))) {3571 $this->errno = 100;3572 return get_string('cannotwritefile', 'error', $options['filepath']);3573 }3574 $filepath = $options['filepath'];3575 }3576 unset($options['filepath']);3577 $result = $this->request($url, $options);3578 if (isset($filepath)) {3579 fclose($options['file']);3580 if ($result !== true) {3581 unlink($filepath);3582 }3583 }3584 return $result;3585 }3586 3587 /**3588 * HTTP PUT method3589 *3590 * @param string $url3591 * @param array $params3592 * @param array $options3593 * @return bool3594 */3595 public function put($url, $params = array(), $options = array()) {3596 $file = $params['file'];3597 if (!is_file($file)) {3598 return null;3599 }3600 $fp = fopen($file, 'r');3601 $size = filesize($file);3602 $options['CURLOPT_PUT'] = 1;3603 $options['CURLOPT_INFILESIZE'] = $size;3604 $options['CURLOPT_INFILE'] = $fp;3605 if (!isset($this->options['CURLOPT_USERPWD'])) {3606 $this->setopt(array('CURLOPT_USERPWD'=>'anonymous: [email protected]'));3607 }3608 $ret = $this->request($url, $options);3609 fclose($fp);3610 return $ret;3611 }3612 3613 /**3614 * HTTP DELETE method3615 *3616 * @param string $url3617 * @param array $param3618 * @param array $options3619 * @return bool3620 */3621 public function delete($url, $param = array(), $options = array()) {3622 $options['CURLOPT_CUSTOMREQUEST'] = 'DELETE';3623 if (!isset($options['CURLOPT_USERPWD'])) {3624 $options['CURLOPT_USERPWD'] = 'anonymous: [email protected]';3625 }3626 $ret = $this->request($url, $options);3627 return $ret;3628 }3629 3630 /**3631 * HTTP TRACE method3632 *3633 * @param string $url3634 * @param array $options3635 * @return bool3636 */3637 public function trace($url, $options = array()) {3638 $options['CURLOPT_CUSTOMREQUEST'] = 'TRACE';3639 $ret = $this->request($url, $options);3640 return $ret;3641 }3642 3643 /**3644 * HTTP OPTIONS method3645 *3646 * @param string $url3647 * @param array $options3648 * @return bool3649 */3650 public function options($url, $options = array()) {3651 $options['CURLOPT_CUSTOMREQUEST'] = 'OPTIONS';3652 $ret = $this->request($url, $options);3653 return $ret;3654 }3655 3656 /**3657 * Get curl information3658 *3659 * @return string3660 */3661 public function get_info() {3662 return $this->info;3663 }3664 3665 /**3666 * Get curl error code3667 *3668 * @return int3669 */3670 public function get_errno() {3671 return $this->errno;3672 }3673 3674 /**3675 * When using a proxy, an additional HTTP response code may appear at3676 * the start of the header. For example, when using https over a proxy3677 * there may be 'HTTP/1.0 200 Connection Established'. Other codes are3678 * also possible and some may come with their own headers.3679 *3680 * If using the return value containing all headers, this function can be3681 * called to remove unwanted doubles.3682 *3683 * Note that it is not possible to distinguish this situation from valid3684 * data unless you know the actual response part (below the headers)3685 * will not be included in this string, or else will not 'look like' HTTP3686 * headers. As a result it is not safe to call this function for general3687 * data.3688 *3689 * @param string $input Input HTTP response3690 * @return string HTTP response with additional headers stripped if any3691 */3692 public static function strip_double_headers($input) {3693 // I have tried to make this regular expression as specific as possible3694 // to avoid any case where it does weird stuff if you happen to put3695 // HTTP/1.1 200 at the start of any line in your RSS file. This should3696 // also make it faster because it can abandon regex processing as soon3697 // as it hits something that doesn't look like an http header. The3698 // header definition is taken from RFC 822, except I didn't support3699 // folding which is never used in practice.3700 $crlf = "\r\n";3701 return preg_replace(3702 // HTTP version and status code (ignore value of code).3703 '~^HTTP/1\..*' . $crlf .3704 // Header name: character between 33 and 126 decimal, except colon.3705 // Colon. Header value: any character except \r and \n. CRLF.3706 '(?:[\x21-\x39\x3b-\x7e]+:[^' . $crlf . ']+' . $crlf . ')*' .3707 // Headers are terminated by another CRLF (blank line).3708 $crlf .3709 // Second HTTP status code, this time must be 200.3710 '(HTTP/1.[01] 200 )~', '$1', $input);3711 }3712 }3713 3714 /**3715 * This class is used by cURL class, use case:3716 *3717 * 3718 * $CFG->repositorycacheexpire = 120;3719 * $CFG->curlcache = 120;3720 *3721 * $c = new curl(array('cache'=>true), 'module_cache'=>'repository');3722 * $ret = $c->get('http://www.google.com');3723 * 3724 *3725 * @package core_files3726 * @copyright Dongsheng Cai 3727 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later3728 */3729 class curl_cache {3730 /** @var string Path to cache directory */3731 public $dir = '';3732 3733 /**3734 * Constructor3735 *3736 * @global stdClass $CFG3737 * @param string $module which module is using curl_cache3738 */3739 public function __construct($module = 'repository') {3740 global $CFG;3741 if (!empty($module)) {3742 $this->dir = $CFG->cachedir.'/'.$module.'/';3743 } else {3744 $this->dir = $CFG->cachedir.'/misc/';3745 }3746 if (!file_exists($this->dir)) {3747 mkdir($this->dir, $CFG->directorypermissions, true);3748 }3749 if ($module == 'repository') {3750 if (empty($CFG->repositorycacheexpire)) {3751 $CFG->repositorycacheexpire = 120;3752 }3753 $this->ttl = $CFG->repositorycacheexpire;3754 } else {3755 if (empty($CFG->curlcache)) {3756 $CFG->curlcache = 120;3757 }3758 $this->ttl = $CFG->curlcache;3759 }3760 }3761 3762 /**3763 * Get cached value3764 *3765 * @global stdClass $CFG3766 * @global stdClass $USER3767 * @param mixed $param3768 * @return bool|string3769 */3770 public function get($param) {3771 global $CFG, $USER;3772 $this->cleanup($this->ttl);3773 $filename = 'u'.$USER->id.'_'.md5(serialize($param));3774 if(file_exists($this->dir.$filename)) {3775 $lasttime = filemtime($this->dir.$filename);3776 if (time()-$lasttime > $this->ttl) {3777 return false;3778 } else {3779 $fp = fopen($this->dir.$filename, 'r');3780 $size = filesize($this->dir.$filename);3781 $content = fread($fp, $size);3782 return unserialize($content);3783 }3784 }3785 return false;3786 }3787 3788 /**3789 * Set cache value3790 *3791 * @global object $CFG3792 * @global object $USER3793 * @param mixed $param3794 * @param mixed $val3795 */3796 public function set($param, $val) {3797 global $CFG, $USER;3798 $filename = 'u'.$USER->id.'_'.md5(serialize($param));3799 $fp = fopen($this->dir.$filename, 'w');3800 fwrite($fp, serialize($val));3801 fclose($fp);3802 @chmod($this->dir.$filename, $CFG->filepermissions);3803 }3804 3805 /**3806 * Remove cache files3807 *3808 * @param int $expire The number of seconds before ex