diff --git a/admin/survey/Branching.php b/admin/survey/Branching.php index 5cb29e147..c59765674 100644 --- a/admin/survey/Branching.php +++ b/admin/survey/Branching.php @@ -339,10 +339,8 @@ class Branching { $row = SurveyInfo::getInstance()->getSurveyRow(); - //if ($row['toolbox'] == 1 || $row['toolbox'] == 2) if($this->survey_type != 0) $this->toolbox_basic2(); - //else if ($row['toolbox'] >= 3) $this->toolbox_library(); @@ -353,6 +351,8 @@ class Branching { }); anketa); } /** @@ -679,7 +679,7 @@ class Branching { # find & replace if ($row['locked'] == 0) { echo '

'; - echo ''.$lang['srv_find_replace'].''; + echo ''.$lang['srv_find_replace_words'].''; echo '

'; } @@ -1069,6 +1069,8 @@ class Branching { printf ($lang['srv_new_survey_success3'], 'index.php?anketa='.$this->anketa.'&a=branching&change_mode=1&what=toolbox&value=3'); echo ' '; + MobileSurveyAdmin::displayNoQuestions($this->anketa); + echo ''; } @@ -1102,9 +1104,6 @@ class Branching { } - //$sqlQ = sisplet_query("SELECT * FROM srv_branching WHERE ank_id='$this->anketa' AND parent='0' ORDER BY vrstni_red ASC"); - //if (!$sqlQ) echo mysqli_error($GLOBALS['connect_db']); - //while ($rowQ = mysqli_fetch_array($sqlQ)) { foreach (Cache::srv_branching_parent($this->anketa, $parent) AS $k => $rowQ) { $this->display_element($rowQ['element_spr'], $rowQ['element_if']); } @@ -1631,6 +1630,8 @@ class Branching { if ( in_array($row['tip'], array(1,2,6,16,19,20)) ) { if ($row['enota'] != 10 && $row['orientation'] != 10){ echo '
'.$lang['srv_novavrednost'].'
'; + + MobileSurveyAdmin::displayAddQuestionCategory($this->anketa, $spremenljivka, $row['tip']); } } @@ -6836,7 +6837,8 @@ class Branching { } if ( ! ( mysqli_num_rows($sql1)==1 && $spr_id==0 ) ) { - echo '
'.$lang['srv_add_cond'].': + echo '
'.$lang['srv_add_cond'].' '.Help::display('srv_if_operator').': +  AND ,  AND NOT ,  OR , @@ -7227,7 +7229,7 @@ class Branching { // right_bracket buttons if ($row_count['count'] != 1 || $row['right_bracket']>0 || $row['left_bracket']>0) { - echo ''; + echo ''; if ($row['right_bracket'] > 0) echo ''; @@ -7235,24 +7237,23 @@ class Branching { echo ''; echo ''; - } else { - echo ''; + } + else { + echo ''; } echo ''; // move - echo ''; + echo ''; if ($row_count['count'] != 1 ) echo ''; echo ''; // remove - echo ''; + echo ''; if ($row_count['count'] != 1 ) echo ''."\n"; - //else - //echo ''."\n"; echo ''; diff --git a/admin/survey/SurveyAdmin.php b/admin/survey/SurveyAdmin.php index c4febcab3..bf9a1e4c5 100644 --- a/admin/survey/SurveyAdmin.php +++ b/admin/survey/SurveyAdmin.php @@ -37,6 +37,7 @@ * slideshow (prezentacija) * social_network (socialna omrežja - generator imen) * quiz (kviz s pravilnimi/napacnimi odgovori) + * voting (volitve z anonimnimi vabili) * uporabnost (evalvacija strani - split screen) * panel (povezovanje ankete s panelom - npr. Valicon, GFK...) * 360_stopinj (adecco) @@ -266,158 +267,93 @@ class SurveyAdmin global $site_domain; global $aai_instalacija; - $sql = sisplet_query("SELECT email FROM users WHERE id='$global_user_id'"); - $row = mysqli_fetch_assoc($sql); - if ($row['email'] == "test@1ka.si") { - echo '
'; - echo '' . $lang['notify_testUser'] . ''; - echo '
'; - } - - echo '
'; - - - // user navigacija - echo '
'; - - // Search po zunanji lupini - preusmeri na drupalov search - echo '
'; - - if($lang['id'] != "1") - $drupal_search_url = 'https://www.1ka.si/d/en/iskanje/'; - else - $drupal_search_url = 'https://www.1ka.si/d/sl/iskanje/'; - - echo '
'; - - echo ''; - - echo ' '; - echo ''; - echo ''; - - echo '
'; - - echo '
'; - - - // Hitra pomoč - povezave na linke s pomočjo na www.1ka.si - $subdomain = ($lang['id'] == "1") ? 'www' : 'english'; - $help_url = Common::getHelpUrl($subdomain, $this->first_action); - echo '
'; - echo ' '; - echo ''; - echo ' '; - echo '
'; - - // povezava na fieldwork sync - if ($this->anketa > 0) { - - // poglej če je tale ID ankete v srv_fieldwork - $sql = sisplet_query("SELECT id FROM srv_fieldwork where sid_server='" . $this->anketa . "'"); - if (mysqli_num_rows($sql) > 0) { - // nariši link. - echo '
'; - echo ''; - echo ''; - echo ' '; - echo '
'; - } - } - - $sql = $this->db_select_user($global_user_id); - $row = mysqli_fetch_array($sql); - - $text = $row['name'] . ' ' . $row['surname']; - $text = (strlen($text) > 25) ? substr($text, 0, 25) . '...' : $text; - - - echo '
' . $text . ' '; - echo '
'; - - echo '' . $lang['edit_data'] . ''; - - // Odjava na nov nacin preko frontend/api - echo '
'; - echo '' . $lang['logout'] . ''; - echo '
'; - - echo '
'; - echo '
'; - - echo '
'; - - - // logotip - echo ''; - echo '
'; - echo '
'; - /***** SEZNAM ANKET - Ce ni nastavljene ankete, potem prikazujemo seznam na prvi strani *****/ - if (!($this->anketa > 0)) { + /********************* GLAVA *********************/ + echo '
'; - $this->displaySeznamAnket(); - } - /***** GLAVNA VSEBINA - Znotraj posamezne ankete *****/ - else{ + // DESKTOP HEADER + echo '
'; - echo '
anketa == 0 ? ' class="prva"' : '') . '>'; + // Nastavitve zgoraj desno v headerju (search, help, profil...) + $this->displayHeaderRight(); + + // logotip + $this->displayHeaderLogo(); + + // Znotraj posamezne ankete + if($this->anketa > 0){ + + // Utripajoc napis "Demo anketa" + $this->displayHeaderDemoSurvey(); // Prikaze podatke o anketi in navigacijo - na vrhu (top bar) - $this->displayAnketaTop(); + $this->displayHeaderAnketa(); + } + // Seznam anket + else{ + $this->displayHeaderSeznamAnket(); + } + + echo '
'; + + // MOBILE HEADER + echo '
'; + + $mobile_admin = new MobileSurveyAdmin($this); + $mobile_admin->displayHeaderMobile(); + + echo '
'; + + echo '
'; + /********************* GLAVA - END *********************/ + + + /********************* MAIN *********************/ + echo '
'; + + // SEZNAM ANKET - Ce ni nastavljene ankete, potem prikazujemo seznam na prvi strani *****/ + if (!($this->anketa > 0)) { + $this->displaySeznamAnket(); + } + // ZNOTRAJ ANKETE + else{ + echo '
'; echo '
'; $this->displayAnketa(); echo '
'; - echo '
'; - - - // Predpregled tipa vprašanj - prikazujemo samo kadar smo v urejanju ankete - if (($_GET['a'] == '' && isset($_GET['anketa'])) || $_GET['a'] == 'branching') { - - echo '
'; - - echo '
'; - $this->getTipPreviewHtml(); - echo '
'; - - echo '
'; // tip_preview - } - - // Utripajoc napis "Demo anketa" - $row = SurveyInfo::getInstance()->getSurveyRow(); - if ($row['invisible'] == 1 && !Dostop::isMetaAdmin()) { - echo '
'; - echo '
' . $lang['srv_close_invisible'] . '
'; - - ?> '; } /***** SKRITI DIVI ZA POPUPE *****/ $this->displayHiddenPopups(); - - echo '
'; + echo '
'; + /********************* MAIN - END *********************/ + - /***** FOOTER *****/ + /********************* FOOTER *********************/ $this->displayFooter(); + /********************* FOOTER - END *********************/ + + + echo '
'; } + // Prikazemo skrite dive za popupe private function displayHiddenPopups(){ global $lang; + + // Predpregled tipa vprašanj - prikazujemo samo kadar smo v urejanju ankete + $this->getTipPreviewHtml(); + + // Loading ikona echo '
'; echo ' '.$lang['srv_saving']; @@ -545,9 +481,148 @@ class SurveyAdmin echo '
'; echo '
'; } + + + // Prikazemo podatke zgoraj desno v glavi (search, user, help) + private function displayHeaderRight(){ + global $site_url; + global $global_user_id; + global $lang; + + + // user navigacija + echo '
'; + + + // Gumb za nadgraditev paketa v mojih anketah (ce imamo vklopljene pakete in nimamo 3ka paketa) + if($this->anketa == 0){ + + global $app_settings; + if($app_settings['commercial_packages'] == true){ + + // Preverimo trenuten paket uporabnika + $userAccess = UserAccess::getInstance($global_user_id); + $current_package = $userAccess->getPackage(); + if($current_package != '3' && !$userAccess->userNotAuthor()){ + + $drupal_url = ($lang['id'] == '2') ? $site_url.'d/en/' : $site_url.'d/'; + $upgrade_url = $drupal_url.'izvedi-nakup/3/podatki'; + + $button_text = ($current_package == '2') ? $lang['srv_access_upgrade2'] : $lang['srv_access_upgrade']; + + echo '
'; + echo ''; + echo '
'; + } + } + } + + + // Search po zunanji lupini - preusmeri na drupalov search + echo '
'; + + if($lang['id'] != "1") + $drupal_search_url = 'https://www.1ka.si/d/en/iskanje/'; + else + $drupal_search_url = 'https://www.1ka.si/d/sl/iskanje/'; + + echo '
'; + + echo ''; + + echo ' '; + echo ''; + echo ''; + + echo '
'; + + echo '
'; + + + // Hitra pomoč - povezave na linke s pomočjo na www.1ka.si + $subdomain = ($lang['id'] == "1") ? 'www' : 'english'; + $help_url = Common::getHelpUrl($subdomain, $this->first_action); + echo '
'; + echo ' '; + echo ''; + echo ' '; + echo '
'; + + + // povezava na fieldwork sync + if ($this->anketa > 0) { + + // poglej če je tale ID ankete v srv_fieldwork + $sql = sisplet_query("SELECT id FROM srv_fieldwork where sid_server='" . $this->anketa . "'"); + if (mysqli_num_rows($sql) > 0) { + // nariši link. + echo '
'; + + echo ''; + echo ''; + echo ' '; + + echo '
'; + } + } + + + // User profil + $sql = $this->db_select_user($global_user_id); + $row = mysqli_fetch_array($sql); + + $text = $row['name'] . ' ' . $row['surname']; + $text = (strlen($text) > 25) ? substr($text, 0, 25) . '...' : $text; + + echo '
'.$text.' '; + echo '
'; + + echo '' . $lang['edit_data'] . ''; + + // Odjava na nov nacin preko frontend/api + echo '
'; + echo '' . $lang['logout'] . ''; + echo '
'; + + echo '
'; + echo '
'; + + + echo '
'; + } + + // Prikazemo logo zgoraj levo + public function displayHeaderLogo(){ + global $lang; + global $site_url; + + echo ''; + } + + // Utripajoc napis "Demo anketa" + private function displayHeaderDemoSurvey(){ + global $lang; + + $row = SurveyInfo::getInstance()->getSurveyRow(); + + if ($row['invisible'] == 1 && !Dostop::isMetaAdmin()) { + + echo '
'; + echo '
' . $lang['srv_close_invisible'] . '
'; + + ?> thirdNavigation(); } + /** + * prikaze glavo v seznamu anket + * + */ + private function displayHeaderSeznamAnket(){ + global $lang, $site_url, $global_user_id, $admin_type, $site_domain; + + // Pobrisemo vse preview vnose + Common::deletePreviewData($this->anketa); + + # naložimo razred z seznamom anket + $SL = new SurveyList(); + $SLCount = $SL->countSurveys(); + $SLCountPhone = $SL->countPhoneSurveys(); + + // Obvestilo da ima uporabnik neprebrano sporocilo + $NO = new Notifications(); + $countMessages = $NO->countMessages(); + if ($countMessages > 0) { + echo '
'; + echo $lang['srv_notifications_alert']; + echo '
'; + + // Ce imamo vklopljen avtomatski prikaz sporcila (za pomembne zadeve), ga prikazemo po loadu + if($NO->checkForceShow()) + echo ''; + } + + // GDPR popup za prejemanje obvestil - force ce ga se ni izpolnil - SAMO NA www.1ka.si, test.1ka.si in virtualkah + if (($site_url == 'https://www.1ka.si/' || $site_url == 'http://test.1ka.si/' || $site_url == 'https://1ka.arnes.si/' || ($cookie_domain == '.1ka.si' && $virtual_domain == true)) + && User::getInstance()->getSetting($setting='gdpr_agree') == '-1') { + + // Avtomatsko prikazemo po loadu + echo ''; + } + + + echo '
'; + + echo '
 
'; + + echo '
'; + $SL->display_tabs(); + echo '
'; + + echo '
'; + + + # smo v knjižnici + $SL->display_sub_tabs(); + } + + // Priakz footerja private function displayFooter(){ global $lang; @@ -574,7 +702,8 @@ class SurveyAdmin global $aai_instalacija; global $mysql_database_name; - echo ''; - echo '
'; // END #main - - echo ''; // END #main_holder + echo ''; } + // Prikaze ime ankete, zvezdico in tiste linke spodi (ker se vse refresha z ajaxom) private function anketa_active() { global $lang; @@ -1168,6 +1296,18 @@ class SurveyAdmin echo ''; echo ''; echo ''; + } + # volitve + if (isset($modules['voting'])) { + $css = ($this->first_action == A_VOTING) ? 'on' : 'off'; + + echo '
  •  
  • '; + + echo '
  • '; + echo ''; + echo ''; + echo ''; + echo '
  • '; } # napredni parapodatki if (isset($modules['advanced_paradata'])) { @@ -1776,6 +1916,14 @@ class SurveyAdmin || isset($modules['slideshow'])) ? '' : ' side-right'; echo '
  • '; echo '
  • ' . $lang['srv_kviz'] . '
  • '; + } + #volitve + if (isset($modules['voting'])) { + $_active = ($_GET['a'] == A_VOTING) ? ' active' : ''; + $_right = (isset($modules['social_network']) + || isset($modules['slideshow'])) ? '' : ' side-right'; + echo '
  • '; + echo '
  • ' . $lang['srv_voting'] . '
  • '; } #napredni parapodatki if (isset($modules['advanced_paradata'])) { @@ -1822,13 +1970,15 @@ class SurveyAdmin echo ''; echo '
  • '; - # parapodatki (browser, os, js...) - echo '
  • '; - echo ''; - echo $lang['srv_metapodatki'] . ''; - echo '
  • '; - echo '
  • '; + # parapodatki (browser, os, js...) - volitve imajo to ugasnjeno + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')) { + echo '
  • '; + echo ''; + echo $lang['srv_metapodatki'] . ''; + echo '
  • '; + echo '
  • '; + } # neodgovori in uporabnost enot //if ($admin_type === '0') { @@ -1849,6 +1999,16 @@ class SurveyAdmin echo ''; echo '
  • '; + # kakovost resp - V DELU - ZAENKRAT SAMO ADMINI + if ($admin_type === '0') { + echo '
  • '; + echo ''; + echo $lang['srv_kakovost'] . ''; + echo '
  • '; + echo '
  • '; + } + # speeder index - V DELU - ZAENKRAT SAMO ADMINI if ($admin_type === '0') { echo '
  • '; @@ -1869,8 +2029,8 @@ class SurveyAdmin echo '
  • '; } - # IP analiza lokacij - gorenje ima to ugasnjeno - if (!Common::checkModule('gorenje')) { + # IP analiza lokacij - gorenje ima to ugasnjeno, volitve imajo tudi ugasnjeno + if (!Common::checkModule('gorenje') && !SurveyInfo::getInstance()->checkSurveyModule('voting')) { echo '
  • '; echo ''; @@ -1923,17 +2083,21 @@ class SurveyAdmin } # langStatistic - #langStatistic naj bo viden samo če imamo različne jezike - $qry_string = "SELECT language FROM srv_user WHERE ank_id = '" . $this->anketa . "' AND preview = '0' AND deleted='0' group by language"; - $qry = (sisplet_query($qry_string)); - $cntLang = mysqli_num_rows($qry); - if ($cntLang > 1) { - echo '
  • '; - echo ''; - echo $lang['srv_languages_statistics'] . ''; - echo '
  • '; - echo '
  • '; + #langStatistic naj bo viden samo če imamo različne jezike in nimamo volitev + if (!Common::checkModule('gorenje') && !SurveyInfo::getInstance()->checkSurveyModule('voting')) { + + $qry_string = "SELECT language FROM srv_user WHERE ank_id = '" . $this->anketa . "' AND preview = '0' AND deleted='0' group by language"; + $qry = (sisplet_query($qry_string)); + $cntLang = mysqli_num_rows($qry); + + if ($cntLang > 1) { + echo '
  • '; + echo ''; + echo $lang['srv_languages_statistics'] . ''; + echo '
  • '; + echo '
  • '; + } } echo ''; @@ -2002,7 +2166,7 @@ class SurveyAdmin // Zavihki PODATKI elseif($_GET['a'] == 'data'){ - if(!isset($_GET['m'])){ + if(!isset($_GET['m']) || $_GET['m'] == 'view'){ $podstran = 'data'; } elseif($_GET['m'] == 'quick_edit'){ @@ -2183,6 +2347,7 @@ class SurveyAdmin || $_GET['a'] == 'uporabnost' || ($_GET['a'] == 'hierarhija_superadmin' && $hierarhija_type < 5) || $_GET['a'] == 'kviz' + || $_GET['a'] == 'voting' || $_GET['a'] == 'slideshow' || $_GET['a'] == 'vnos' || $_GET['a'] == A_TELEPHONE @@ -2308,6 +2473,13 @@ class SurveyAdmin $SUR->displayUporabnost(); echo ' '; } + // prikaze modul kakovost + elseif ($_GET['a'] == A_KAKOVOST_RESP) { + echo '
    '; + $SUR = new SurveyKakovost($this->anketa); + $SUR->displayKakovost(); + echo '
    '; + } // Prikaze analizo hitrosti respondenta elseif ($_GET['a'] == A_SPEEDER_INDEX) { echo '
    '; @@ -2433,6 +2605,7 @@ class SurveyAdmin elseif ($_GET['a'] == 'uporabnost' || ($_GET['a'] == 'hierarhija_superadmin' && $hierarhija_type < 5) || $_GET['a'] == 'kviz' + || $_GET['a'] == 'voting' || $_GET['a'] == 'slideshow' || $_GET['a'] == 'vnos' || $_GET['a'] == A_TELEPHONE @@ -3094,7 +3267,7 @@ class SurveyAdmin } // Linki za napredne module - function showAdvancedModulesLinks() + private function showAdvancedModulesLinks() { global $lang; global $site_url; @@ -3143,6 +3316,12 @@ class SurveyAdmin echo '
  • '; echo 'checkUserAccess($what='kviz') ? 'class="user_access_locked"' : '').'>' . $lang['srv_vrsta_survey_type_6'] . '
  • '; + # Volitve + if ($admin_type == 0) { + echo '
  • '; + echo 'checkUserAccess($what='voting') ? 'class="user_access_locked"' : '').'>' . $lang['srv_vrsta_survey_type_18'] . '
  • '; + } + # Socialna omrezja echo '
  • '; echo 'checkUserAccess($what='social_network') ? 'class="user_access_locked"' : '').'>' . $lang['srv_vrsta_survey_type_8'] . '
  • '; @@ -3279,54 +3458,15 @@ class SurveyAdmin * prikaze seznam anket in polje za dodajanje na prvi strani * */ - function displaySeznamAnket(){ + private function displaySeznamAnket(){ global $lang, $site_url, $global_user_id, $admin_type, $site_domain; - // Pobrisemo vse preview vnose - Common::deletePreviewData($this->anketa); # naložimo razred z seznamom anket $SL = new SurveyList(); $SLCount = $SL->countSurveys(); $SLCountPhone = $SL->countPhoneSurveys(); - // Obvestilo da ima uporabnik neprebrano sporocilo - $NO = new Notifications(); - $countMessages = $NO->countMessages(); - if ($countMessages > 0) { - echo '
    '; - echo $lang['srv_notifications_alert']; - echo '
    '; - - // Ce imamo vklopljen avtomatski prikaz sporcila (za pomembne zadeve), ga prikazemo po loadu - if($NO->checkForceShow()) - echo ''; - } - - // GDPR popup za prejemanje obvestil - force ce ga se ni izpolnil - SAMO NA www.1ka.si, test.1ka.si in virtualkah - if (($site_url == 'https://www.1ka.si/' || $site_url == 'http://test.1ka.si/' || $site_url == 'https://1ka.arnes.si/' || ($cookie_domain == '.1ka.si' && $virtual_domain == true)) - && User::getInstance()->getSetting($setting='gdpr_agree') == '-1') { - - // Avtomatsko prikazemo po loadu - echo ''; - } - - - echo '
    '; - - echo '
     
    '; - - echo '
    '; - $SL->display_tabs(); - echo '
    '; - - echo '
    '; - - - # smo v knjižnici - $SL->display_sub_tabs(); - - // VSEBINA POSAMEZNEGA TABA PRI MOJIH ANKETAH echo '
    '; @@ -3616,6 +3756,8 @@ class SurveyAdmin echo '
    '; } + + /** * vrne kodo ankete, ki se jo uporabi za embed * @@ -3800,7 +3942,7 @@ class SurveyAdmin } } - if (($_GET['a'] == A_COLLECT_DATA || $_GET['a'] == A_USABLE_RESP || $_GET['a'] == A_SPEEDER_INDEX || $_GET['a'] == A_REMINDER_TRACKING || $_GET['a'] == A_TEXT_ANALYSIS || $_GET['a'] == A_EDITS_ANALYSIS || $_GET['a'] == A_ANALYSIS) && $_GET['m'] != 'analysis_links' && $_GET['m'] != 'anal_arch') + if (($_GET['a'] == A_COLLECT_DATA || $_GET['a'] == A_USABLE_RESP || $_GET['a'] == A_KAKOVOST_RESP || $_GET['a'] == A_SPEEDER_INDEX || $_GET['a'] == A_REMINDER_TRACKING || $_GET['a'] == A_TEXT_ANALYSIS || $_GET['a'] == A_EDITS_ANALYSIS || $_GET['a'] == A_ANALYSIS) && $_GET['m'] != 'analysis_links' && $_GET['m'] != 'anal_arch') $this->displayExportHover($navigation); } else if ($navigation == 1) { @@ -4933,10 +5075,24 @@ class SurveyAdmin * preview * */ - function getTipPreviewHtml(){ + private function getTipPreviewHtml(){ global $lang; global $global_user_id; + + // Predpregled tipa vprašanj - prikazujemo samo kadar smo v urejanju ankete + if (!$this->anketa > 0) + return; + + if ( ($_GET['a'] != '' || !isset($_GET['anketa'])) && $_GET['a'] != 'branching' ) + return; + + + echo '
    '; + + echo '
    '; + + // Preverimo, ce je funkcionalnost v paketu, ki ga ima uporabnik $userAccess = UserAccess::getInstance($global_user_id); @@ -5842,6 +5998,11 @@ class SurveyAdmin echo '
    '; } } + + + echo '
    '; + + echo '
    '; // tip_preview } /** diff --git a/admin/survey/SurveyAdminAjax.php b/admin/survey/SurveyAdminAjax.php index 531cfa638..b5c6b7804 100644 --- a/admin/survey/SurveyAdminAjax.php +++ b/admin/survey/SurveyAdminAjax.php @@ -849,12 +849,33 @@ class SurveyAdminAjax { $this->anketa = $_POST['anketa']; } - $MA = new MailAdapter($this->anketa, $type='alert'); + // Squalo vklopljen + if(isset($_POST['squalo_mode'])){ + + // Squalo vklopljen + if($_POST['squalo_mode'] == '1'){echo $_POST['squalo_mode']; + sisplet_query("UPDATE srv_anketa_module SET vrednost='2' WHERE ank_id='".$this->anketa."' AND modul='email'"); + } + // Squalo izklopljen + else{ + sisplet_query("UPDATE srv_anketa_module SET vrednost='1' WHERE ank_id='".$this->anketa."' AND modul='email'"); + + $MA = new MailAdapter($this->anketa, $type='alert'); - $settings = $MA->getSettingsFromRequest($_REQUEST); - $mode = $_REQUEST['SMTPMailMode']; + $settings = $MA->getSettingsFromRequest($_REQUEST); + $mode = $_REQUEST['SMTPMailMode']; + + $MA->setSettings($mode, $settings); + } + } + else{ + $MA = new MailAdapter($this->anketa, $type='alert'); - $MA->setSettings($mode, $settings); + $settings = $MA->getSettingsFromRequest($_REQUEST); + $mode = $_REQUEST['SMTPMailMode']; + + $MA->setSettings($mode, $settings); + } } } elseif ($_GET['m'] == 'predvidenicasi') { // predvideni casi foreach($_POST AS $key => $val) { @@ -1260,7 +1281,7 @@ class SurveyAdminAjax { $avtorSql = sisplet_query("SELECT insert_uid FROM srv_anketa WHERE id='" . $this->anketa . "'"); $avtorRow = mysqli_fetch_assoc($avtorSql); - // da ne more zbrisat avtorja (razen če je test@1ka.si) + // da ne more zbrisat avtorja $avtorPogoj = (isset ($avtorRow['insert_uid']) && $avtorRow['insert_uid'] > 0) ? " AND (uid != ".$avtorRow['insert_uid']." )" : ""; // da ne more zbrisat sam sebe $avtorID = " AND uid != '" . $global_user_id . "'"; @@ -6119,6 +6140,17 @@ class SurveyAdminAjax { $sq = new SurveyQuiz($this->anketa); $sq->displaySettings(); } + elseif ($what == 'voting' && $value == '1'){ + + // Volitve + $sv = new SurveyVoting($this->anketa); + + // Izvedemo vse potrebno pri vklopu (vklopimo obvescanje, ugasnemo belezenje parapodatkov...) + $sv->turnOnVoting(); + + // Prikazemo morebitne nastavitve + $sv->displaySettings(); + } elseif ($what == 'advanced_paradata' && $value == '1'){ // kviz $sap = new SurveyAdvancedParadata($this->anketa); diff --git a/admin/survey/SurveyAdminSettings.php b/admin/survey/SurveyAdminSettings.php index 04edbc78d..3cb8a0c09 100644 --- a/admin/survey/SurveyAdminSettings.php +++ b/admin/survey/SurveyAdminSettings.php @@ -441,86 +441,106 @@ class SurveyAdminSettings { /* PISKOTEK */ if ($_GET['a'] == A_PRIKAZ) { + echo '
    '; echo '' . $lang['srv_data_valid_units_settings'] . ''; + echo '

    '; echo ''.$lang['srv_prikaz_default_valid'].''; echo ''; echo ''; echo '

    '; - echo '

    '; - echo ''.$lang['srv_prikaz_showItime'].''; - echo ''; - echo ''; - echo '

    '; + + // Pri volitvah ne moremo prikazati datuma respondenta + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo '

    '; + echo ''.$lang['srv_prikaz_showItime'].''; + echo ''; + echo ''; + echo '

    '; + } + echo '

    '; echo ''.$lang['srv_prikaz_showLineNumber'].''; echo ''; echo ''; echo '

    '; + echo '
    '; - } /*Piskotek*/ if ($_GET['a'] == 'piskot') { - - echo '
    '; - - echo ''; - - echo '' . $lang['srv_cookie'] . ''; - - // Shrani piskotek za X casa - echo ''; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo '
    '; - - // Ko se uporabnik vrne (zacne od zacetka/nadaljuje kjer je ostal) - echo ''; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo '
    '; - - // Ce je zakljucil lahko naknadno ureja svoje odgovore - echo '' . "\n\r"; + + // Pri volitvah ne moremo popravljati nastavitev piskotka + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + + echo '
    '; + echo '' . $lang['srv_cookie'] . ''; + echo ''.$lang['srv_voting_no_cookie'].''; + echo '
    '; + + echo ''; + + return; + } + + echo '
    '; + + echo ''; + + echo '' . $lang['srv_cookie'] . ''; + + // Shrani piskotek za X casa + echo ''; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo '
    '; + + // Ko se uporabnik vrne (zacne od zacetka/nadaljuje kjer je ostal) + echo ''; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo '
    '; + + // Ce je zakljucil lahko naknadno ureja svoje odgovore + echo '' . "\n\r"; echo '
    '; // Nikoli ne more popravljati svojih odgovorov (tudi ce se npr. vrne na prejsnjo stran) - echo '
    '; - echo ' ' . "\n\r"; - echo '
    ' . "\n\r"; - echo '
    '; - - // Ce ni sprejel piskotka lahko/ne more nadaljevati - echo '' . "\n\r"; - echo '
    '; - - echo '
    '; - - // Prepoznaj respondenta - echo ''; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo ' ' . "\n\r"; - echo '
    '; - - // Ob izpolnjevanju prikazi email - echo '
    '; - echo '  '; - echo ' ' . "\n\r"; + echo '
    '; + echo ' ' . "\n\r"; + echo '
    ' . "\n\r"; + echo '
    '; + + // Ce ni sprejel piskotka lahko/ne more nadaljevati + echo '' . "\n\r"; + echo '
    '; + + echo '
    '; + + // Prepoznaj respondenta + echo ''; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo ' ' . "\n\r"; + echo '
    '; + + // Ob izpolnjevanju prikazi email + echo '
    '; + echo '  '; + echo ' ' . "\n\r"; echo '
    '; echo '
    '; @@ -538,54 +558,54 @@ class SurveyAdminSettings { // For modul maza, show all cookie settings $isMaza = (SurveyInfo::checkSurveyModule('maza')) ? 1 : 0; - - ?> anketa . "' AND (uid='" . $global_user_id . "' OR uid IN (SELECT user FROM srv_dostop_manage WHERE manager='$global_user_id' ))"; - $sqlDostopAvtor = sisplet_query($stringDostopAvtor); - $rowDostopAvtor = mysqli_fetch_assoc($sqlDostopAvtor); - $avtorRow = SurveyInfo::getInstance()->getSurveyRow(); - + $stringDostopAvtor = "SELECT count(*) as isAvtor FROM srv_dostop WHERE ank_id = '" . $this->anketa . "' AND (uid='" . $global_user_id . "' OR uid IN (SELECT user FROM srv_dostop_manage WHERE manager='$global_user_id' ))"; + $sqlDostopAvtor = sisplet_query($stringDostopAvtor); + $rowDostopAvtor = mysqli_fetch_assoc($sqlDostopAvtor); + $avtorRow = SurveyInfo::getInstance()->getSurveyRow(); + echo '
    '; @@ -626,15 +646,15 @@ class SurveyAdminSettings { echo '
    '; - echo '
    '; + echo '
    '; // Preverimo, ce je funkcionalnost v paketu, ki ga ima uporabnik global $global_user_id; $userAccess = UserAccess::getInstance($global_user_id); - // dodajanje gesel za anketo - echo '
    '.$lang['srv_password'].' '.Help::display('srv_dostop_password').''; + // dodajanje gesel za anketo + echo '
    '.$lang['srv_password'].' '.Help::display('srv_dostop_password').''; if(!$userAccess->checkUserAccess($what='password')){ $userAccess->displayNoAccess($what='password'); @@ -810,13 +830,16 @@ class SurveyAdminSettings { echo ''; } - - #'.$lang['srv_detail_settings'].'' echo '

    '; - echo '

    '.$lang['srv_detail_settings'].'

    '; + - echo '

    '.$lang['comments'].'
    '; - echo ''.$lang['srv_vabila'].'

    '; + echo '

    '.$lang['comments'].'

    '; + echo '

    '.$lang['srv_vabila'].'

    '; + + echo '

    '; + echo ' '.$lang['srv_detail_settings'].' '; + echo ' '; + echo '

    '; echo '
    '; @@ -1392,16 +1415,34 @@ class SurveyAdminSettings { $browser = SurveySetting::getInstance()->getSurveyMiscSetting('survey_browser'); $referal = SurveySetting::getInstance()->getSurveyMiscSetting('survey_referal'); $date = SurveySetting::getInstance()->getSurveyMiscSetting('survey_date'); - + echo '
    '; echo ''.$lang['srv_sledenje'].''; + // Preverimo ce je vklopljen modul za volitve - potem ne pustimo nobenih preklopov + $voting_disabled = ''; + $voting_disabled_class = ''; + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + $voting_disabled = ' disabled'; + $voting_disabled_class = ' class="gray"'; + + echo '

    '.$lang['srv_voting_warning_paradata'].'

    '; + } + echo '

    '.$lang['srv_metadata_desc'].'

    '; - echo '
    '; - echo '
    '; - echo '
    '; + echo ''; + echo ' '; + echo '
    '; + + echo ''; + echo ' '; + echo '
    '; + + echo ''; + echo ' '; + echo '
    '; echo '
    '; @@ -1410,17 +1451,24 @@ class SurveyAdminSettings { echo '
    '; + echo ''.$lang['srv_sledenje_ip_title'].''; - echo ' '; - if($ip == 0 && $ip_show != 1) + echo ''; + echo ' '; + echo ' '; + + if($ip == 0 && $ip_show != 1) echo '
    '.$lang['srv_sledenje_ip_alert'].'
    '; echo '
    '; if($ip == 0 && ($admin_type == 0 || $admin_type == 1)){ - echo ' '; - if($ip_show == 1) + echo ''; + echo ' '; + echo ' '; + + if($ip_show == 1) echo '
    '.$lang['srv_show_ip_alert'].'
    '; } @@ -1433,9 +1481,12 @@ class SurveyAdminSettings { echo '
    '; echo ''.$lang['srv_sledenje_identifikatorji_title'].' '.Help::display('srv_email_with_data').''; - echo ' '; - if($row['show_email'] == 1) - echo '
    '.$lang['srv_show_mail_with_data3'].'
    '; + echo ''; + echo ' '; + echo ' '; + + if($row['show_email'] == 1) + echo '
    '.$lang['srv_show_mail_with_data3'].'
    '; echo '
    '; } @@ -2731,95 +2782,46 @@ class SurveyAdminSettings { echo '
    '. "\n"; echo ' ' . $lang['srv_alert_prejemnik'] . ''. "\n"; - - // Vklop/izklop obvescanja lahko naredi samo admin ali manager - if(true /*$admin_type == 0 || $admin_type == 1 || (isset($app_settings['commercial_packages']) && $app_settings['commercial_packages'] == true)*/){ - // respondent - ne prikazemo ce gre za glasovanje - if($rowS['survey_type'] != 0){ - echo '

    '; - echo ''; - echo ''; - $this->display_alert_label('finish_respondent',($rowAlert['finish_respondent'] == 1)); - echo ''. "\n"; - - // Ce imamo vec prevodov omogocimo za vsak prevod svoj email - $this->display_alert_label('finish_respondent_language',($rowAlert['finish_respondent'] == 1)); - echo '

    '; - } - //respondent iz cms - echo '

    '; - echo ''; - $this->display_alert_label('finish_respondent_cms',($rowAlert['finish_respondent_cms'] == 1)); - echo '

    '. "\n"; - - // avtor ankete oz osebe z dostopom - //echo '

    '; - echo '

    '; - echo ''; - $this->display_alert_label('finish_author',($rowAlert['finish_author'] == 1)); - echo '

    '; - - // posebej navedeni maili - echo '

    '; - echo ' '; - echo ' '; - if ($rowAlert['finish_other_if']>0) { if ($b==null) $b = new Branching($this->anketa); $b->conditions_display($rowAlert['finish_other_if']); } - echo '

    '; - - echo '

    '; - echo '' . - '' . - '

    '; - } - // Ostali samo vidijo kaj je vklopljeno - else{ - // Text z obvestilom in linkom na cenik/obrazec za vklop nastavitev - echo '

    '.$lang['srv_alert_turnOn_text'].'

    '; - // respondent - ne prikazemo ce gre za glasovanje - if($rowS['survey_type'] != 0){ - echo '

    '; - echo ''; - $value = $rowAlert['finish_respondent'] == 1 ? 1 : 0; - echo ''; - echo ''; - $this->display_alert_label('finish_respondent',($rowAlert['finish_respondent'] == 1)); - echo ''. "\n"; - - // Ce imamo vec prevodov omogocimo za vsak prevod svoj email - $this->display_alert_label('finish_respondent_language',($rowAlert['finish_respondent'] == 1)); - echo '

    '; - } - //respondent iz cms - echo '

    '; - $value = $rowAlert['finish_respondent_cms'] == 1 ? 1 : 0; - echo ''; - echo ''; - $this->display_alert_label('finish_respondent_cms',($rowAlert['finish_respondent_cms'] == 1)); - echo '

    '. "\n"; + // respondent - ne prikazemo ce gre za glasovanje oz. volitve + if($rowS['survey_type'] != 0 && !SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo '

    '; + echo ''; + echo ''; + $this->display_alert_label('finish_respondent',($rowAlert['finish_respondent'] == 1)); + echo ''. "\n"; + + // Ce imamo vec prevodov omogocimo za vsak prevod svoj email + $this->display_alert_label('finish_respondent_language',($rowAlert['finish_respondent'] == 1)); + echo '

    '; + } - // avtor ankete oz osebe z dostopom - echo '

    '; - $value = $rowAlert['finish_author'] == 1 ? 1 : 0; - echo ''; - echo ''; - $this->display_alert_label('finish_author',($rowAlert['finish_author'] == 1)); - echo '

    '; + // respondent iz cms ne prikazemo ce gre za volitve + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo '

    '; + echo ''; + $this->display_alert_label('finish_respondent_cms',($rowAlert['finish_respondent_cms'] == 1)); + echo '

    '. "\n"; + } - // posebej navedeni maili - echo '

    '. $lang['email_one_per_line']; - $value = $rowAlert['finish_other'] == 1 || ($rowAlert['finish_other_emails'] && $rowAlert['finish_other'] != 0) ? 1 : 0; - echo ''; - echo ' '; - echo ' '; - if ($rowAlert['finish_other_if']>0) { if ($b==null) $b = new Branching($this->anketa); $b->conditions_display($rowAlert['finish_other_if']); } - echo '

    '; - - echo '

    '; - echo '' . - '' . - '

    '; - } + // avtor ankete oz osebe z dostopom + //echo '

    '; + echo '

    '; + echo ''; + $this->display_alert_label('finish_author',($rowAlert['finish_author'] == 1)); + echo '

    '; + + // posebej navedeni maili + echo '

    '; + echo ' '; + echo ' '; + if ($rowAlert['finish_other_if']>0) { if ($b==null) $b = new Branching($this->anketa); $b->conditions_display($rowAlert['finish_other_if']); } + echo '

    '; + + echo '

    '; + echo '' . + '' . + '

    '; echo '
    '; @@ -8697,6 +8699,36 @@ class SurveyAdminSettings { } echo ''; } + elseif ($_GET['a'] == 'voting'){ + + // Ce so vabila ze vklopljena ne pustimo vklopa + if(isset($modules['voting']) || (!isset($modules['voting']) && SurveyInfo::getInstance()->checkSurveyModule('email'))){ + $disabled = ' disabled="disabled"'; + $css_disabled = ' gray'; + } + + echo '
    '.$lang['srv_voting'].''; + + echo ''; + echo '
    '.$lang['srv_voting_info'].''; + + // Opozorilo, da so vabila ze vklopljena in zato modula ni mogoce vklopiti + if(!isset($modules['voting']) && SurveyInfo::getInstance()->checkSurveyModule('email')){ + echo '

    '.$lang['srv_voting_info_error'].'
    '; + } + + echo '
    '; + + echo '
    '; + + echo '
    '; + if(isset($modules['voting'])){ + $sv = new SurveyVoting($this->anketa); + $sv->displaySettings(); + } + echo '
    '; + } elseif ($_GET['a'] == 'advanced_paradata'){ echo '
    '.$lang['srv_advanced_paradata'].''; @@ -9267,6 +9299,8 @@ class SurveyAdminSettings { echo ''.$lang['srv_api_auth'].': '; echo ''.$lang['srv_api_auth2'].''; echo '

    '; + + echo $lang['additional_info_api']; echo '
    '; @@ -9475,10 +9509,9 @@ class SurveyAdminSettings { // Save gumb - ce ni AAI if(!$aai_instalacija){ + echo ' '; echo ' '; - echo ' '; - echo ''; // div za prikaz uspešnosti shranjevanja diff --git a/admin/survey/classes/class.Common.php b/admin/survey/classes/class.Common.php index 5f22d84e6..f2cde6be2 100644 --- a/admin/survey/classes/class.Common.php +++ b/admin/survey/classes/class.Common.php @@ -1610,6 +1610,11 @@ class Common { $help_url = 'https://www.1ka.si/d/en/help/user-guide/edit/settings'; } + // Moje ankete - narocila + else if ($_GET['a'] == 'narocila') { + $help_url = 'https://www.1ka.si/d/en/services'; + } + // Ostale default podstrani else { switch ($podstran) { @@ -2026,6 +2031,12 @@ class Common { else if ($_GET['a'] == 'branching') { $help_url = 'https://www.1ka.si/d/sl/pomoc/vodic-za-uporabnike/urejanje/vprasalnik'; } + + // Moje ankete - narocila + else if ($_GET['a'] == 'narocila') { + $help_url = 'https://www.1ka.si/d/en/services'; + $help_url = 'https://www.1ka.si/d/sl/cenik'; + } // Ostale default podstrani else { diff --git a/admin/survey/classes/class.GDPR.php b/admin/survey/classes/class.GDPR.php index 16a1fdaba..ab4908e7c 100644 --- a/admin/survey/classes/class.GDPR.php +++ b/admin/survey/classes/class.GDPR.php @@ -1918,6 +1918,7 @@ class GDPR{ // PODATKI O POOBLAŠČENI OSEBI ZA VARSTVO OSEBNIH PODATKOV $result[4]['heading'] = $lang['srv_gdpr_survey_gdpr_about_text5_1']; + // DPO if($gdpr_survey_settings['authorized'] == ''){ // Zasebnik brez DPO @@ -1941,18 +1942,42 @@ class GDPR{ } // Ce ga ni vnesel, je DPO mail enak splosnemu mailu oz. mailu avtorja ankete else{ - if($gdpr_settings['email'] != '') + if($gdpr_settings['email'] != ''){ $gdpr_authorized = $gdpr_settings['email']; - else + } + else{ $gdpr_authorized = User::getInstance()->primaryEmail(); + } } } } else{ $gdpr_authorized = $gdpr_survey_settings['authorized']; } - $result[4]['text'][0] = $lang['srv_gdpr_survey_gdpr_about_text5_2'].' '.$gdpr_authorized.''; + // Kontaktni email + if($gdpr_survey_settings['contact_email'] == ''){ + + $user_settings = self::getUserSettings(); + + // Kontaktni mail je enak mailu, ki ga je vnesel v splosnih nastavitvah + if($user_settings['email'] != ''){ + $gdpr_contact_email = $user_settings['email']; + } + // Ce ga ni vnesel, je kontaktni mail enak mailu avtorja ankete + else{ + $gdpr_contact_email = User::getInstance()->primaryEmail(); + } + } + else{ + $gdpr_contact_email = $gdpr_survey_settings['contact_email']; + } + + $result[4]['text'][0] = $lang['srv_gdpr_survey_gdpr_about_text5_2'].' '.$gdpr_authorized.''; + + // Ce mail ni isti izpisemo se avtorja + if($gdpr_authorized != $gdpr_contact_email) + $result[4]['text'][1] = $lang['srv_gdpr_survey_gdpr_about_text5_2_2'].' '.$gdpr_contact_email.''; // ZAVAROVANJE PODATKOV $result[5]['heading'] = $lang['srv_gdpr_survey_gdpr_about_text6_1']; diff --git a/admin/survey/classes/class.Library.php b/admin/survey/classes/class.Library.php index d774cd63c..3aa26964e 100644 --- a/admin/survey/classes/class.Library.php +++ b/admin/survey/classes/class.Library.php @@ -288,32 +288,32 @@ class Library { // Anketa ze obstaja v javni knjiznici - jo pobrisemo if (mysqli_num_rows($sqlPublic) > 0) { echo ' '; - echo ' '.$lang['srv_ank_lib_off']; + echo ' '.$lang['srv_ank_lib_off'].''; echo ' '; } // Anketo dodamo v javno knjiznico else{ echo ' '; - echo ' '.$lang['srv_ank_lib_on']; + echo ' '.$lang['srv_ank_lib_on'].''; echo ' '; } } // nova anketa kot template iz knjiznice - echo ' '.$lang['srv_anketacopy'].' '; - echo ' '.$lang['srv_poglejanketo2'].' '; + echo ' '.$lang['srv_anketacopy'].' '; + echo ' '.$lang['srv_poglejanketo2'].' '; // brisi iz knjiznice if ($admin_type == 0) { - echo ' '.$lang['edit3'].''; + echo ' '.$lang['edit3'].''; } if ($admin_type==0 && $this->tab==2) {// sistemska - echo ' '.$lang['hour_remove'].''; + echo ' '.$lang['hour_remove'].''; } if($this->tab==3){// moja knjiznica - echo ' '.$lang['hour_remove'].''; + echo ' '.$lang['hour_remove'].''; } } else { diff --git a/admin/survey/classes/class.SurveyCopy.php b/admin/survey/classes/class.SurveyCopy.php index 54dfd087c..cbb98b5ef 100644 --- a/admin/survey/classes/class.SurveyCopy.php +++ b/admin/survey/classes/class.SurveyCopy.php @@ -485,8 +485,7 @@ class SurveyCopy { 'edit_time' => "NOW()", 'folder' => "'1'", 'forum' => "'0'", - 'thread' => "'0'", - 'old_email_style' => "'0'"); + 'thread' => "'0'"); # user_id ni enak če je anketa na drugem strežniku zato v tem primeru damo -1 da na drugem strežniku vemo da je to kopija od drugod if (self::$destSite != 0) { diff --git a/admin/survey/classes/class.SurveyInfo.php b/admin/survey/classes/class.SurveyInfo.php index 46b8fcc01..590a4c59a 100644 --- a/admin/survey/classes/class.SurveyInfo.php +++ b/admin/survey/classes/class.SurveyInfo.php @@ -725,6 +725,11 @@ class SurveyInfo $module_availible = false; break; + case 'voting': + if(!$userAccess->checkUserAccess($what='voting')) + $module_availible = false; + break; + case 'social_network': if(!$userAccess->checkUserAccess($what='social_network')) $module_availible = false; @@ -792,6 +797,11 @@ class SurveyInfo return false; break; + case 'voting': + if(!$userAccess->checkUserAccess($what='voting')) + return false; + break; + case 'social_network': if(!$userAccess->checkUserAccess($what='social_network')) return false; diff --git a/admin/survey/classes/class.SurveyList.php b/admin/survey/classes/class.SurveyList.php index a7e362a58..98e3438c9 100644 --- a/admin/survey/classes/class.SurveyList.php +++ b/admin/survey/classes/class.SurveyList.php @@ -168,7 +168,8 @@ class SurveyList { // Ali prikazujemo folderje ali ne $userAccess = UserAccess::getInstance($global_user_id); - if($userAccess->checkUserAccess('my_survey_folders')) + $detect = New Mobile_Detect(); + if($userAccess->checkUserAccess('my_survey_folders') && !$detect->isMobile() && !$detect->isTablet()) $this->show_folders = UserSetting::getInstance()->getUserSetting('survey_list_folders'); # koliko zapisov prikazujemo na stran @@ -1505,7 +1506,6 @@ class SurveyList { echo ' '.$folder['naslov'].''; // Ikona za dodajanje folderja - //echo ' '; echo ''; @@ -1524,7 +1524,7 @@ class SurveyList { echo ' '.$folder['naslov'].''; // Ikona za dodajanje folderja - echo ' '; + echo ' '; // Ikona za brisanje folderja echo ' '; @@ -1541,7 +1541,7 @@ class SurveyList { $this->displayNewSurveyList($folder['id']); // Izpisemo se vse folderje znotraj trenutnega folderja - $sql = sisplet_query("SELECT * FROM srv_mysurvey_folder WHERE usr_id='$global_user_id' AND parent='".$folder['id']."' ORDER BY naslov ASC"); + $sql = sisplet_query("SELECT * FROM srv_mysurvey_folder WHERE usr_id='$global_user_id' AND parent='".$folder['id']."' ORDER BY id DESC"); if(mysqli_num_rows($sql) > 0) { while($row = mysqli_fetch_array($sql)){ echo '
    '; @@ -2934,6 +2934,13 @@ class SurveyList { $sql = sisplet_query("UPDATE srv_mysurvey_folder SET open='1' WHERE id='".$parent."' AND usr_id='$global_user_id'"); $sql = sisplet_query("INSERT INTO srv_mysurvey_folder (usr_id, parent, naslov) VALUES ('".$global_user_id."','".$parent."', '".$lang['srv_mySurvey_new_folder']."')"); + + $new_folder_id = mysqli_insert_id($GLOBALS['connect_db']); + + $SL = new SurveyList(); + $SL->getSurveys(); + + echo ''; } // Pobrisali smo obstojec folder diff --git a/admin/survey/classes/class.SurveyPostProcess.php b/admin/survey/classes/class.SurveyPostProcess.php index b1f858090..bdbd5ec47 100644 --- a/admin/survey/classes/class.SurveyPostProcess.php +++ b/admin/survey/classes/class.SurveyPostProcess.php @@ -227,6 +227,9 @@ class SurveyPostProcess { } elseif ($_GET['a'] == 'edit_data_question_save') { $this->ajax_edit_data_question_save(0); + } elseif ($_GET['a'] == 'edit_data_question_upload_delete') { + $this->ajax_edit_data_question_upload_delete(); + } elseif ($_GET['a'] == 'get_inline_edit') { $this->ajax_get_inline_edit(); @@ -255,11 +258,8 @@ class SurveyPostProcess { $this->ajax_coding_merge(); } elseif ($_GET['a'] == 'coding_filter') { - $this->ajax_coding_filter(); - - } - - + $this->ajax_coding_filter(); + } } /** @@ -547,6 +547,25 @@ class SurveyPostProcess { /*if ($refresh == 1) header("Location: index.php?anketa=".$this->anketa."&a=data&m=edit");*/ } + + // Pobrisemo upload datoteke v urejanju podatkov + function ajax_edit_data_question_upload_delete () { + Common::updateEditStamp(); + + $spr_id = $_POST['spr_id']; + $usr_id = $_POST['usr_id']; + $code = $_POST['code']; + + $s = sisplet_query("DELETE FROM srv_data_upload WHERE ank_id='".$this->anketa."' AND usr_id='".$usr_id."' AND code='".$code."'"); + if (!$s) echo mysqli_error($GLOBALS['connect_db']); + + $s2 = sisplet_query("DELETE FROM srv_data_text".$this->db_table." WHERE spr_id='".$spr_id."' AND usr_id='".$usr_id."'"); + if (!$s2) echo mysqli_error($GLOBALS['connect_db']); + + $this->ajax_edit_data_question(); + + self::forceRefreshData($this->anketa); + } function ajax_get_inline_edit () { diff --git a/admin/survey/classes/class.SurveyStatistic.php b/admin/survey/classes/class.SurveyStatistic.php index ca2b4bf28..092775db5 100644 --- a/admin/survey/classes/class.SurveyStatistic.php +++ b/admin/survey/classes/class.SurveyStatistic.php @@ -929,6 +929,10 @@ class SurveyStatistic { $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_6']; $prefix = ', '; } + if (isset($modules['voting'])) { + $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_18']; + $prefix = ', '; + } if (isset($modules['phone'])) { $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_7']; $prefix = ', '; diff --git a/admin/survey/classes/class.SurveyStatusProfiles.php b/admin/survey/classes/class.SurveyStatusProfiles.php index 947ce80ea..a0eb91ae0 100644 --- a/admin/survey/classes/class.SurveyStatusProfiles.php +++ b/admin/survey/classes/class.SurveyStatusProfiles.php @@ -1,4 +1,5 @@ 0 && $collect_all_status == 0) { - if ( $pid == 1 ) { + } + else if (self::$allValidCount > 0 && $collect_all_status == 0) { + + /*if ( $pid == 1 ) { #$pid = 2; $pid = self::$survayDefaultUstrezni; - } - $disabledAll = ' disabled="disabled"'; - $disabledAllGray = ' gray'; + }*/ + + if((int)self::$allUserCount > 1000){ + $disabledAll = ' disabled="disabled"'; + $disabledAllGray = ' gray'; + } } + echo ''; echo ' '; @@ -1136,11 +1144,11 @@ class SurveyStatusProfiles echo ''; echo $lang['srv_data_valid_units'].' ('.(int)self::$allValidCount.')'; echo ''; - } else { + } + else { echo '
    '; @@ -1885,10 +1876,7 @@ class Vprasanje { $row = Cache::srv_spremenljivka($this->spremenljivka); #'email','ime','priimek','telefon','naziv','drugo' - echo '

    '.$lang['srv_label'].':

    '; - - //echo '

    '.$lang['srv_datapiping'].': '.Help::display('DataPiping').' '.$lang['srv_datapiping_txt'].'

    '; - + echo '

    '.$lang['srv_label'].':

    '; } /** @@ -4326,8 +4314,13 @@ class Vprasanje { echo ''; diff --git a/admin/survey/classes/mobile/class.MobileSurveyAdmin.php b/admin/survey/classes/mobile/class.MobileSurveyAdmin.php new file mode 100644 index 000000000..358c02024 --- /dev/null +++ b/admin/survey/classes/mobile/class.MobileSurveyAdmin.php @@ -0,0 +1,746 @@ +surveyAdminClass = $surveyAdminClass; + } + + + // Izris glave z menijem - znotraj ankete + public function displayHeaderMobile(){ + + echo '
    '; + + // Ikona za meni + $this->displayMenuIcon(); + + // Meni + $this->displayMenu(); + + // Naslov ankete + if($this->surveyAdminClass->anketa > 0){ + $this->displaySurveyTitle(); + } + // Logo - enak kot na desktopu + else{ + $this->displayLogo(); + } + + // Se inicializiramo zeynep jquery mobile menu + echo ''; + + echo '
    '; + } + + + // Prikazemo mobile logo + private function displayLogo(){ + global $lang; + global $site_url; + + echo ''; + } + + private function displaySurveyTitle(){ + + SurveyInfo::getInstance()->SurveyInit($this->surveyAdminClass->anketa); + $row = SurveyInfo::getInstance()->getSurveyRow(); + + echo '
    '.$row['naslov'].'
    '; + } + + private function displayMenuIcon(){ + + echo '
    '; + echo ' '; + echo '
    '; + + echo '
    '; + echo ' '; + echo '
    '; + } + + + // Izris menija + private function displayMenu(){ + + echo '
    '; + + // Izris uporabniških podatkov v dropdownu + $this->displayMenuUser(); + + // Meni znotraj ankete + if($this->surveyAdminClass->anketa > 0){ + + // Izris glavne navigacije v dropdownu + $this->displayMenuSurveyNavigation(); + + // Izris akcij za anketo (kopiraj, brisi...) v dropdownu + $this->displayMenuSurveyActions(); + } + // Meni v mojih anketah + else{ + $this->displayMenuMySurveysNavigation(); + } + + echo '
    '; + + } + + // Izris uporabniških podatkov v dropdownu + private function displayMenuUser(){ + global $lang, $global_user_id, $site_url; + + + $sql = $this->surveyAdminClass->db_select_user($global_user_id); + $row = mysqli_fetch_array($sql); + + $user_name = $row['name'] . ' ' . $row['surname']; + $user_name = (strlen($user_name) > 25) ? substr($user_name, 0, 25) . '...' : $user_name; + + $user_email = '
    '.$row['email'].''; + + + echo '
    '; + + echo ''; + + echo '
    '; + } + + // Izris glavne navigacije v mojih anketah + private function displayMenuMySurveysNavigation(){ + global $lang, $admin_type, $app_settings; + + + # naložimo razred z seznamom anket + $SL = new SurveyList(); + $SLCount = $SL->countSurveys(); + $SLCountPhone = $SL->countPhoneSurveys(); + + + echo '
    '; + + echo ''; + + echo '
    '; + } + + // Izris glavne navigacije v anketi + private function displayMenuSurveyNavigation(){ + global $lang; + + + $hierarhija_type = (!empty($_SESSION['hierarhija'][$this->surveyAdminClass->anketa]['type']) ? $_SESSION['hierarhija'][$this->surveyAdminClass->anketa]['type'] : null); + + $row = SurveyInfo::getInstance()->getSurveyRow(); + SurveyInfo:: getInstance()->SurveyInit($this->surveyAdminClass->anketa); + + $modules = SurveyInfo::getSurveyModules(); + $d = new Dostop(); + + + echo '
    '; + + echo ''; + + echo '
    '; + } + + // Izris akcij za anketo (kopiraj, brisi...) v dropdownu + private function displayMenuSurveyActions(){ + global $lang; + + echo '
    '; + + # kopiranje + echo ' '; + echo ' '.$lang['srv_anketacopy']; + echo ' '; + + # brisanje + echo ' '; + echo ' '.$lang['srv_anketadelete']; + echo ' '; + + echo '
    '; + } + + + + private function displayMenuItemWithSubmenu($name, $title, $submenu){ + global $lang; + + echo '
  • '; + echo ' '.$title.''; + echo '
  • '; + + // Podmeni + echo ''; + } + + private function displayMenuItem($title, $url){ + + echo '
  • '; + echo ''.$title.''; + echo '
  • '; + } + + + // Gumb za dodajanje vprasanja + public static function displayAddQuestion($ank_id){ + global $lang; + + echo '
    '; + echo ' '; + echo ' + '; + echo ' '.$lang['srv_mobile_add_question'].''; + echo ' '; + echo '
    '; + + + // Popup za dodajanje vprašanja + echo '
    '; + + echo '
    '.$lang['srv_vprasanje_tip_1'].'
    '; + echo '
    '.$lang['srv_vprasanje_tip_2'].'
    '; + echo '
    '.$lang['srv_vprasanje_tip_21'].'
    '; + echo '
    '.$lang['srv_vprasanje_tip_7'].'
    '; + echo '
    '.$lang['srv_vprasanje_tip_5'].'
    '; + echo '
    '.$lang['srv_vprasanje_tip_6'].'
    '; + + echo ' '; + echo ' Zapri'; + echo ' '; + + echo '
    '; + } + + // Div ko se nimamo nobenega vprasanja v anketi + public static function displayNoQuestions($ank_id){ + global $lang; + + // Skrijemo spodnji gumb + echo ''; + + echo '
    '; + echo ' '; + echo ' + '; + echo ' '.$lang['srv_mobile_add_question'].''; + echo ' '; + echo '
    '; + } + + // Div za dodajanje kategorije v vprasanje + public static function displayAddQuestionCategory($ank_id, $spr_id, $tip){ + global $lang; + + echo '
    '; + echo ' '.$lang['srv_novavrednost'].''; + echo '
    '; + } +} diff --git a/admin/survey/classes/surveyAnalysis/class.SurveyChart.php b/admin/survey/classes/surveyAnalysis/class.SurveyChart.php index e79ebe512..2de81e818 100644 --- a/admin/survey/classes/surveyAnalysis/class.SurveyChart.php +++ b/admin/survey/classes/surveyAnalysis/class.SurveyChart.php @@ -2319,12 +2319,12 @@ class SurveyChart { 'radar_scale' => 0, // skala pri radarju (na osi / diagonalno) '3d_pie' => 0, // tip kroznega grafa (navaden / 3d) 'labelWidth' => 50, // sirina label (50% / 20%) - 'barLabel' => 0, // prikaz label v stolpicnih grafih + 'barLabel' => 1, // prikaz label v stolpicnih grafih 'barLabelSmall' => 1, // prikaz label pod 5% v stolpicnih grafih (zraven stolpca) 'rotate' => 0, // obrnjeni gridi in variable (pri vseh MG - multiradio, multinumber...) 'colors' => $colors, // custom barve grafa 'show_avg' => -1, // prikaz povprecja na grafu (samo pri ordinalnih radio) - 'show_numerus' => -1, // prikaz numerusa na grafu + 'show_numerus' => 1, // prikaz numerusa na grafu 'otherType' => 0, // poravnava other tabel 'otherFreq' => 0, // izpis frekvenc v other tabeli 'hideEmptyVar' => 0, // ali izpuscamo prazne opcije brez odgovora (ce je nad 20 variabel -> default 1) diff --git a/admin/survey/classes/surveyAnalysis/class.SurveyTableChart.php b/admin/survey/classes/surveyAnalysis/class.SurveyTableChart.php index 29803c78c..e39e8b118 100644 --- a/admin/survey/classes/surveyAnalysis/class.SurveyTableChart.php +++ b/admin/survey/classes/surveyAnalysis/class.SurveyTableChart.php @@ -1396,7 +1396,7 @@ class SurveyTableChart { 'radar_type' => 0, // tip radarj (crte / liki) 'radar_scale' => 0, // skala pri radarju (na osi / diagonalno) 'labelWidth' => 50, // sirina label (50% / 20%) - 'barLabel' => 0, // prikaz label v stolpicnih grafih + 'barLabel' => 1, // prikaz label v stolpicnih grafih 'rotate' => 0, // obrnjeni gridi in variable (pri multinumber...) 'hq' => 1, // visoka locljivost grafa 'show_numerus' => 1, // prikaz numerusa diff --git a/admin/survey/classes/surveyData/class.SurveyDataCollect.php b/admin/survey/classes/surveyData/class.SurveyDataCollect.php index 9698db74e..f53a5f022 100644 --- a/admin/survey/classes/surveyData/class.SurveyDataCollect.php +++ b/admin/survey/classes/surveyData/class.SurveyDataCollect.php @@ -1427,7 +1427,7 @@ class SurveyDataCollect{ $_dataLine .= STR_DLMT. (($rowUser['status'] == 5 || $rowUser['status'] == 6) && $rowUser['lurker'] == 0 ? '1' : '0'); # dodamo email (invitation)k podatkom - če je bilo poslano z emailom ali je uporabnik ročno vnesel email - $_dataLine .= STR_DLMT. ((int)$rowUser['inv_res_id'] > 0 + $_dataLine .= STR_DLMT. ((int)$rowUser['inv_res_id'] > 0 || (int)$rowUser['inv_res_id'] == -1 # uporabnik je bil dodan z email vabilom ? ((int)$rowUser['status'] == 1 || (int)$rowUser['status'] >= 3 # email je bil odposlan diff --git a/admin/survey/classes/surveyData/class.SurveyDataDisplay.php b/admin/survey/classes/surveyData/class.SurveyDataDisplay.php index c56e7458a..5141e2315 100644 --- a/admin/survey/classes/surveyData/class.SurveyDataDisplay.php +++ b/admin/survey/classes/surveyData/class.SurveyDataDisplay.php @@ -1104,30 +1104,33 @@ class SurveyDataDisplay{ echo '
  • '; echo ' '; echo '
  • '; - - // meta - echo '
  • '; - echo ' '; - echo '
  • '; + + // Preverimo ce je vklopljen modul za volitve - potem nimamo identifikatorjev + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ - // če imamo sistemske podatke katere moramo prikazovati ločeno - IDENTIFIKATORJI - if (!isset(self::$_HEADERS['_settings']['count_system_data_variables']) || (isset(self::$_HEADERS['_settings']['count_system_data_variables']) && (int)self::$_HEADERS['_settings']['count_system_data_variables'] > 0)) { - + // Parapodatki echo '
  • '; - echo ' '; - echo '
  • '; - } - // Po novem vedno prikazemo checkbox identifikatorji - samo je odkljukan in disablan - else{ - echo '
  • '; - echo ' '; - echo '
  • '; - } - - // datum - echo '
  • '; - echo ''; - echo '
  • '; + echo ' '; + echo ''; + + // če imamo sistemske podatke katere moramo prikazovati ločeno - IDENTIFIKATORJI + if(!isset(self::$_HEADERS['_settings']['count_system_data_variables']) || (isset(self::$_HEADERS['_settings']['count_system_data_variables']) && (int)self::$_HEADERS['_settings']['count_system_data_variables'] > 0)) { + echo '
  • '; + echo ' '; + echo '
  • '; + } + // Po novem vedno prikazemo checkbox identifikatorji - samo je odkljukan in disablan + else{ + echo '
  • '; + echo ' '; + echo '
  • '; + } + + // datum + echo '
  • '; + echo ''; + echo '
  • '; + } // zaporedna številka echo '
  • '; @@ -1422,8 +1425,6 @@ class SurveyDataDisplay{ echo ''; # colspan za ikonce if ($stolpci > 0) { - //for ($i=0; $i<$stolpci; $i++) - // echo ''; echo ' 1 ? (' span="'.$stolpci.'"') : '').'>'; } @@ -1533,8 +1534,6 @@ class SurveyDataDisplay{ # colspan za ikonce if ($stolpci > 0) { - //for ($i=0; $i<$stolpci; $i++) - // echo ' '; echo ' 1 ? (' colspan="'.$stolpci.'"') : '').'> '; } @@ -1547,7 +1546,7 @@ class SurveyDataDisplay{ if (isset(self::$_SVP_PV[$spid]) && count($spremenljivka['grids']) > 0) { if(self::$showLineNumber && $spr_cont+1 == self::$lineoffset) { echo ''; - echo '
    '.$lang['srv_line_number'].'
    '; + echo '
    '.$lang['srv_line_number'].'
    '; echo ''; } @@ -2749,7 +2748,7 @@ class SurveyDataDisplay{ echo ''; - //div na desni z metapodatki + // Div na desni z metapodatki echo '
    '; self::displayQuickEditMeta(); echo '
    '; @@ -2990,21 +2989,25 @@ class SurveyDataDisplay{ echo ''.$lang['srv_recnum'].':'; echo ''.($rowu['recnum'] ? $rowu['recnum'] : ' ').''; - // browser - echo ''.$lang['browser'].':'; - echo ''.($rowu['useragent'] ? $rowu['useragent'] : ' ').''; - - // javascript - echo ''.$lang['javascript'].':'; - echo ''.(($rowu['javascript'] == 1) ? $lang['yes'] : $lang['no1']).''; - - // jezik - // Dobimo vse jezike za katere obstaja jezikovna datoteka - include_once($site_path.'lang/jeziki.php'); - $jeziki = $lang_all_global['ime']; - $jeziki['0'] = $lang['language']; - echo ''.$lang['lang'].':'; - echo ''.$jeziki[$rowu['language']].''; + // Pri volitvah nimamo parapodatkov + if (!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + + // browser + echo ''.$lang['browser'].':'; + echo ''.($rowu['useragent'] ? $rowu['useragent'] : ' ').''; + + // javascript + echo ''.$lang['javascript'].':'; + echo ''.(($rowu['javascript'] == 1) ? $lang['yes'] : $lang['no1']).''; + + // jezik + // Dobimo vse jezike za katere obstaja jezikovna datoteka + include_once($site_path.'lang/jeziki.php'); + $jeziki = $lang_all_global['ime']; + $jeziki['0'] = $lang['language']; + echo ''.$lang['lang'].':'; + echo ''.$jeziki[$rowu['language']].''; + } // status echo ''.$lang['status'].':'; @@ -3014,77 +3017,78 @@ class SurveyDataDisplay{ echo ''.$lang['srv_data_lurker'].':'; echo ''.(($rowu['lurker'] == 1) ? $lang['yes'] : $lang['no1']).''; - //referer - echo ''.$lang['referer'].':'; - echo ''.($rowu['referer'] ? $rowu['referer'] : ' ').''; + // Pri volitvah nimamo parapodatkov + if (!SurveyInfo::getInstance()->checkSurveyModule('voting')){ - //email - samo forma - if($rowa['survey_type'] == 1){ - echo ''.$lang['email'].':'; - echo ''.($rowu['email'] ? $rowu['email'] : ' ').''; - } + // referer + echo ''.$lang['referer'].':'; + echo ''.($rowu['referer'] ? $rowu['referer'] : ' ').''; - // spreminjal - $datetime = strtotime($rowu['time_insert']); - $text = date("d.m.Y, H:i:s", $datetime); - echo ''.$lang['timeinsert'].':'; - echo ''.$text.''; + // email - samo forma + if($rowa['survey_type'] == 1){ + echo ''.$lang['email'].':'; + echo ''.($rowu['email'] ? $rowu['email'] : ' ').''; + } - $datetime = strtotime($rowu['time_edit']); - $text = date("d.m.Y, H:i:s", $datetime); - echo ''.$lang['timeedit'].':'; - echo ''.$text.''; + // spreminjal + $datetime = strtotime($rowu['time_insert']); + $text = date("d.m.Y, H:i:s", $datetime); + echo ''.$lang['timeinsert'].':'; + echo ''.$text.''; - // preberemo popravljanje po straneh - $sqlG = sisplet_query("SELECT ug.time_edit, g.naslov FROM srv_user_grupa".self::$db_table." ug, srv_grupa g WHERE g.ank_id = '".self::$sid."' AND ug.usr_id = '".self::$usr_id."' AND g.id = ug.gru_id ORDER BY g.vrstni_red ASC"); - while($rowG = mysqli_fetch_array($sqlG)){ + $datetime = strtotime($rowu['time_edit']); + $text = date("d.m.Y, H:i:s", $datetime); + echo ''.$lang['timeedit'].':'; + echo ''.$text.''; - $datetime = strtotime($rowG['time_edit']); - $text = date("d.m.Y, H:i:s", $datetime); + // preberemo popravljanje po straneh + $sqlG = sisplet_query("SELECT ug.time_edit, g.naslov FROM srv_user_grupa".self::$db_table." ug, srv_grupa g WHERE g.ank_id = '".self::$sid."' AND ug.usr_id = '".self::$usr_id."' AND g.id = ug.gru_id ORDER BY g.vrstni_red ASC"); + while($rowG = mysqli_fetch_array($sqlG)){ - echo ''.$rowG['naslov'].':'; - echo ''.$text.''; - } + $datetime = strtotime($rowG['time_edit']); + $text = date("d.m.Y, H:i:s", $datetime); - if ( $admin_type <= 1 /* && what more??? */ ) { - - echo ''.$lang['srv_sc_txt1'].':'; - echo ''.$lang['srv_sc_txt2'].''; - - } + echo ''.$rowG['naslov'].':'; + echo ''.$text.''; + } + + if ( $admin_type <= 1) { + + echo ''.$lang['srv_sc_txt1'].':'; + echo ''.$lang['srv_sc_txt2'].''; + + } + } - # preberemo vklopljene module - //potrebuje se za modul MAZA, da aplikacija izpolni te hidden inpute - //rabi pa se to za povezavo respondenta med tebelama maza_app_users in srv_user - if(SurveyInfo::checkSurveyModule('maza')){ - $maza_query = "SELECT mau.identifier, mau.deviceInfo, mau.tracking_log FROM maza_app_users as mau - JOIN maza_srv_users AS msu ON mau.id = msu.maza_user_id - JOIN srv_user AS su ON msu.srv_user_id = su.id - WHERE su.id = '".self::$usr_id."';"; - - $sql = sisplet_query($maza_query, 'array'); - - //it is already there - if(count($sql) > 0){ - //NextPin link - echo ''.$lang['srv_maza_nextpin_link'].':'; - echo '' - . 'http://traffic.ijs.si/NextPin/?user=1KAPanel_'.$sql[0]['identifier'].''; - //Device info - echo ''.$lang['srv_maza_device_info'].':'; - echo ''.$sql[0]['deviceInfo'].''; - //Tracking logs - echo ''.$lang['srv_maza_user_app_logs'].':'; - echo ''.$sql[0]['tracking_log'].''; - } - } + # preberemo vklopljene module + //potrebuje se za modul MAZA, da aplikacija izpolni te hidden inpute + //rabi pa se to za povezavo respondenta med tebelama maza_app_users in srv_user + if(SurveyInfo::checkSurveyModule('maza')){ + $maza_query = "SELECT mau.identifier, mau.deviceInfo, mau.tracking_log FROM maza_app_users as mau + JOIN maza_srv_users AS msu ON mau.id = msu.maza_user_id + JOIN srv_user AS su ON msu.srv_user_id = su.id + WHERE su.id = '".self::$usr_id."';"; + + $sql = sisplet_query($maza_query, 'array'); + + //it is already there + if(count($sql) > 0){ + //NextPin link + echo ''.$lang['srv_maza_nextpin_link'].':'; + echo '' + . 'http://traffic.ijs.si/NextPin/?user=1KAPanel_'.$sql[0]['identifier'].''; + //Device info + echo ''.$lang['srv_maza_device_info'].':'; + echo ''.$sql[0]['deviceInfo'].''; + //Tracking logs + echo ''.$lang['srv_maza_user_app_logs'].':'; + echo ''.$sql[0]['tracking_log'].''; + } + } echo ''; - echo ''; - //echo ''; - } diff --git a/admin/survey/classes/surveyData/class.SurveyDataFile.php b/admin/survey/classes/surveyData/class.SurveyDataFile.php index 561053c97..a5dbee254 100644 --- a/admin/survey/classes/surveyData/class.SurveyDataFile.php +++ b/admin/survey/classes/surveyData/class.SurveyDataFile.php @@ -57,9 +57,8 @@ class SurveyDataFile { $this->data_file_name = $folder . 'export_data_'.$this->anketa.'.dat'; // Vedno ob inicializaciji preverimo status datoteke - if($this->checked == false) { + if($this->checked == false) $this->checkFile(); - } } @@ -213,6 +212,19 @@ class SurveyDataFile { WHERE u.ank_id = '".$this->anketa."' AND preview='0' AND (u.testdata = '1' OR u.testdata = '2') AND u.deleted = '0'"); list($this->has_test_data) = mysqli_fetch_row($_qry_cnt_testdata); + + // Ce ne belezimo parapodatka o datumu responsa, preverimo zadnji timestamp resevanja ankete + if(SurveySetting::getInstance()->getSurveyMiscSetting('survey_date') == 1) { + + $sql_last_response_time = sisplet_query("SELECT UNIX_TIMESTAMP(last_response_time) AS last_response_time FROM srv_anketa WHERE id='".$this->anketa."'"); + list($last_response_time) = mysqli_fetch_row($sql_last_response_time); + + if($this->data_file_time < $last_response_time){ + $this->clearFiles(); + $this->file_status = FILE_STATUS_NO_FILE; + } + } + // Preverimo ce imamo usability stolpec v header datoteki ali ce imamo na novo testne podatke - potem pobrisemo vse datoteke, ker moramo vse generirati na novo if($this->checkUsability() || $this->checkTestData()){ $this->clearFiles(); diff --git a/admin/survey/classes/surveyEmails/class.SurveyInvitationsNew.php b/admin/survey/classes/surveyEmails/class.SurveyInvitationsNew.php index 68f645aec..86be026c2 100644 --- a/admin/survey/classes/surveyEmails/class.SurveyInvitationsNew.php +++ b/admin/survey/classes/surveyEmails/class.SurveyInvitationsNew.php @@ -13,6 +13,7 @@ define('GROUP_PAGINATE', 4); # po kolko strani grupira pri paginaciji define('REC_ON_PAGE', 10); # kolko zapisov na stran pri urejanju respondentov define('REC_ON_SEND_PAGE', 20); # kolko zapisov na stran pri pošiljanju define('NOTIFY_INFO1KA', 5); # Nad koliko emaili obveščamo info@1ka.si + set_time_limit(2400); # 30 minut class SurveyInvitationsNew { @@ -48,15 +49,13 @@ class SurveyInvitationsNew { $this->surveySettings = SurveyInfo::getInstance()->getSurveyRow(); - #koliko respondentov je že v bazi - $sql_string_all = "SELECT count(*) FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted = '0'"; - $sql_query_all = sisplet_query($sql_string_all); + # koliko respondentov je že v bazi + $sql_query_all = sisplet_query("SELECT count(*) FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted = '0'"); $sql_row_all = mysqli_fetch_row($sql_query_all); $this->count_all = (int)$sql_row_all[0]; - #preverimo ali prikazujemo nov ali star način odvisno od nastavitve v misc - $strSelect = "SELECT count(*) FROM srv_anketa AS a WHERE id ='".$this->sid."' AND insert_time > (SELECT value FROM misc WHERE what = 'invitationTrackingStarted' LIMIT 1)"; - $sql_query = sisplet_query($strSelect); + # preverimo ali prikazujemo nov ali star način odvisno od nastavitve v misc + $sql_query = sisplet_query("SELECT count(*) FROM srv_anketa AS a WHERE id ='".$this->sid."' AND insert_time > (SELECT value FROM misc WHERE what = 'invitationTrackingStarted' LIMIT 1)"); list($newTracking) = mysqli_fetch_row($sql_query); $this->newTracking = (int)$newTracking > 0 ? true : false; @@ -149,175 +148,251 @@ class SurveyInvitationsNew { } else if ($action == 'add_recipients_view') { $this->useRecipientsList(); - #$this->addRecipientsView(); - } else if ($action == 'add_recipients') { + } + else if ($action == 'add_recipients') { $this->addRecipients(); - } else if ($action == 'view_recipients') { + } + else if ($action == 'view_recipients') { $this->viewRecipients(); - } else if ($action == 'view_message') { + } + else if ($action == 'view_message') { $this->viewMessage($_POST['mid']); - } else if ($action == 'make_default') { + } + else if ($action == 'make_default') { $this->makeDefaultMessage($_POST['mid']); - } else if ($action == 'make_default_from_preview') { + } + else if ($action == 'make_default_from_preview') { $this->makeDefaultFromPreview($_POST['mid']); - } else if ($action == 'save_message_simple') { + } + else if ($action == 'save_message_simple') { $this->save_message_simple(); - # } else if ($action == 'save_message') { - # $this->saveMessage($_POST['mid']); - } else if ($action == 'save_message_simple_noEmail') { + } + else if ($action == 'save_message_simple_noEmail') { $this->save_message_simple_noEmail(); - } else if ($action == 'send_message') { + } + else if ($action == 'send_message') { $this->sendMessage($_POST['mid']); - } else if ($action == 'view_archive') { + } + else if ($action == 'view_archive') { $this->viewAarchive($_POST['mid']); - } else if ($action == 'view_send_recipients') { + } + else if ($action == 'view_send_recipients') { if(isset($_POST['noMailing']) && $_POST['noMailing'] == '1') $this->selectSendToNoEmailing(); else $this->selectSendTo(); - } else if ($action == 'mailToSourceChange') { + } + else if ($action == 'mailToSourceChange') { $this->mailToSourceChange(); - } else if ($action == 'send_mail') { - if(isset($_GET['noemailing']) && $_GET['noemailing'] == '1') + } + else if ($action == 'send_mail') { + + // Posiljanje brez emailov (samo aktivacija prejemnikov) + if(isset($_GET['noemailing']) && $_GET['noemailing'] == '1'){ $this->sendMailNoEmailing(); - else + } + // Klasicno posiljanje preko smtp-ja + else{ $this->sendMail(); - } else if ($action == 'upload_recipients') { + } + } + else if ($action == 'upload_recipients') { $this->uploadRecipients(); - } else if ($action == 'view_archive_recipients') { + } + else if ($action == 'view_archive_recipients') { $this->viewArchiveRecipients(); - } else if ($action == 'edit_archive_comment') { + } + else if ($action == 'edit_archive_comment') { $this->editArchiveComment(); - } else if ($action == 'delete_recipient_confirm') { + } + else if ($action == 'delete_recipient_confirm') { $this->deleteRecipientConfirm(); - } else if ($action == 'delete_recipient') { + } + else if ($action == 'delete_recipient') { $this->deleteRecipient(); - } else if ($action == 'delete_recipient_single') { + } + else if ($action == 'delete_recipient_single') { $this->deleteRecipientSingle(); - } else if ($action == 'delete_recipient_all') { + } + else if ($action == 'delete_recipient_all') { $this->deleteRecipientAll(); - } else if ($action == 'export_recipients') { + } + else if ($action == 'export_recipients') { $this->exportRecipients(); - } else if ($action == 'export_recipients_all') { + } + else if ($action == 'export_recipients_all') { $this->exportRecipients_all(); - } else if ($action == 'use_recipients_list') { + } + else if ($action == 'use_recipients_list') { $this->useRecipientsList(); - } else if ($action == 'change_import_type') { + } + else if ($action == 'change_import_type') { $this->changeImportType(); - } else if ($action == 'save_recipient_list') { + } + else if ($action == 'save_recipient_list') { $this->saveRecipientList(); - } else if ($action == 'get_profile_name') { + } + else if ($action == 'get_profile_name') { echo "DEPRECATED!"; die(); $this->getProfileName(); - } else if ($action == 'list_get_name') { + } + else if ($action == 'list_get_name') { $this->listGetName(); - } else if ($action == 'invListEdit') { + } + else if ($action == 'invListEdit') { $this->invListEdit(); - } else if ($action == 'save_rec_profile') { + } + else if ($action == 'save_rec_profile') { $this->saveRecProfile(); - } else if ($action == 'delete_rec_profile') { + } + else if ($action == 'delete_rec_profile') { $this->deleteRecProfile(); - } else if ($action == 'edit_rec_profile') { + } + else if ($action == 'edit_rec_profile') { $this->editRecProfile(); - } else if ($action == 'update_rec_profile') { + } + else if ($action == 'update_rec_profile') { $this->updateRecProfile(); - } else if ($action == 'delete_msg_profile') { + } + else if ($action == 'delete_msg_profile') { $this->deleteMsgProfile(); - } else if ($action == 'message_rename') { + } + else if ($action == 'message_rename') { $this->messageRename(); - } else if ($action == 'show_message_rename') { + } + else if ($action == 'show_message_rename') { $this->showMessageRename(); - } else if ($action == 'edit_recipient') { + } + else if ($action == 'edit_recipient') { $this->editRecipient(); - } else if ($action == 'save_recipient') { + } + else if ($action == 'save_recipient') { $this->saveRecipient(); - } else if ($action == 'set_recipient_filter') { + } + else if ($action == 'set_recipient_filter') { $this->setRecipientFilter(); - } else if ($action == 'only_this_survey') { + } + else if ($action == 'only_this_survey') { $this->onlyThisSurvey(); - # } else if ($action == 'check_send_mail') { - # $this->checkSendMail(); - } else if ($action == 'add_users_to_database') { + } + else if ($action == 'add_users_to_database') { $this->add_users_to_database(); - } else if ($action == 'add_checked_users_to_database') { + } + else if ($action == 'add_checked_users_to_database') { $this->add_checked_users_to_database(); - } else if ($action == 'arch_save_comment') { + } + else if ($action == 'arch_save_comment') { $this->saveArchiveComment(); - } else if ($action == 'edit_message_details') { + } + else if ($action == 'edit_message_details') { $this->editMessageDetails(); - } else if ($action == 'message_save_details') { + } + else if ($action == 'message_save_details') { $this->messageSaveDetails(); - } else if ($action == 'message_save_forward') { + } + else if ($action == 'message_save_forward') { $this->messageSaveforward(); - } else if ($action == 'message_save_forward_noEmail') { + } + else if ($action == 'message_save_forward_noEmail') { $this->messageSaveforwardNoEmail(); - } else if ($action == 'prepare_save_message') { - $this->prepareSaveMessage(); - - } else if ($action == 'showRecipientTracking') { + } + else if ($action == 'prepare_save_message') { + $this->prepareSaveMessage(); + } + else if ($action == 'showRecipientTracking') { $this->showRecipientTracking(); - } else if ($action == 'arch_show_recipients') { + } + else if ($action == 'arch_show_recipients') { $this->showArchiveRecipients(); - } else if ($action == 'arch_show_details') { + } + else if ($action == 'arch_show_details') { $this->showArchiveDetails(); - } else if ($action == 'arch_edit_details') { + } + else if ($action == 'arch_edit_details') { $this->editArchiveDetails(); - } else if ($action == 'inv_status') { + } + else if ($action == 'inv_status') { $this->showInvitationStatus(); - } else if ($action == 'inv_lists') { + } + else if ($action == 'inv_lists') { $this->showInvitationLists(); - } else if ($action == 'inv_list_save') { + } + else if ($action == 'inv_list_save') { $this->listSave(); - } else if ($action == 'invListSaveOld') { + } + else if ($action == 'invListSaveOld') { $this->invListSaveOld(); - } else if ($action == 'invListEditSave') { + } + else if ($action == 'invListEditSave') { $this->invListEditSave(); - } else if ($action == 'editRecList') { -# $this->editRecList(); + } + else if ($action == 'editRecList') { + $doEdit = $_SESSION['inv_edit_rec_profile'][$this->sid] == 'true' ? true : false; - if ($doEdit) { + + if ($doEdit) { $this->showEditRecList(); - } else { + } + else { $this->showNoEditRecList(); } - } else if ($action == 'deleteRecipientsList') { + } + else if ($action == 'deleteRecipientsList') { $this->deleteRecipientsList(); - } else if($action == 'deleteRecipientsListMulti'){ + } + else if($action == 'deleteRecipientsListMulti'){ $this->deleteRecipientsListMulti(); - } else if ($action == 'showInvitationListsNames') { + } + else if ($action == 'showInvitationListsNames') { $this->showInvitationListsNames(); - } else if ($action == 'changeInvRecListEdit') { + } + else if ($action == 'changeInvRecListEdit') { $this->changeInvRecListEdit(); - } else if ($action == 'upload_list') { + } + else if ($action == 'upload_list') { $this->upload_list(); - } else if ($action == 'send_upload_list') { + } + else if ($action == 'send_upload_list') { $this->send_upload_list(); - } else if ($action == 'changePaginationLimit') { + } + else if ($action == 'changePaginationLimit') { $this->changePaginationLimit(); - } else if ($action == 'setSortField') { + } + else if ($action == 'setSortField') { $this->setSortField(); - } else if ($action == 'validateSysVarsMapping') { + } + else if ($action == 'validateSysVarsMapping') { $this->validateSysVarsMapping(); - } else if ($action == 'addSysVarsMapping') { + } + else if ($action == 'addSysVarsMapping') { $this->addSysVarsMapping(); - } else if ($action == 'recipientsAddForward') { + } + else if ($action == 'recipientsAddForward') { $this->recipientsAddForward(); - } else if ($action == 'showAdvancedConditions') { + } + else if ($action == 'showAdvancedConditions') { $this->showAdvancedConditions(); - } else if ($action == 'setAdvancedCondition') { + } + else if ($action == 'setAdvancedCondition') { $this->setAdvancedCondition(); - } else if ($action == 'inv_server') { + } + else if ($action == 'inv_server') { $this->viewServerSettings(); - } else if ($action == 'inv_settings') { + } + else if ($action == 'inv_settings') { $this->showInvitationSettings(); - } else if ($action == 'set_noEmailing') { + } + else if ($action == 'set_noEmailing') { $this->setNoEmailing(); - } else if ($action == 'set_noEmailing_type') { + } + else if ($action == 'set_noEmailing_type') { $this->setNoEmailingType(); - } else if ($action == 'showAAISmtpPopup') { + } + else if ($action == 'showAAISmtpPopup') { $this->showAAISmtpPopup(); - } else { + } + else { $sql = sisplet_query("SELECT EXISTS (SELECT 1 FROM srv_invitations_archive WHERE ank_id='".$this->sid."')"); $row = mysqli_fetch_array($sql); @@ -1302,8 +1377,7 @@ class SurveyInvitationsNew { function viewRecipients($errors = array(), $msgs = array()) { global $lang, $site_url, $admin_type; - //echo '

    '.$lang['srv_inv_edit_recipients_heading'].'

    '; - $noEmailing = SurveySession::get('inv_noEmailing'); + $noEmailing = SurveySession::get('inv_noEmailing'); $row = $this->surveySettings; @@ -1402,8 +1476,7 @@ class SurveyInvitationsNew { } # preštejemo koliko imamo vseh respondentov in koliko jih je brez e-maila - $sql_string_all = "SELECT id FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted = '0'"; - $sql_query_all = sisplet_query($sql_string_all); + $sql_query_all = sisplet_query("SELECT id FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted = '0'"); $count_all = mysqli_num_rows($sql_query_all); $sql_string_withot_email = "SELECT count(*) FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted = '0' AND email IS NULL AND sent='0'"; @@ -1421,16 +1494,20 @@ class SurveyInvitationsNew { # Katera polja prikazujemo v seznamu prejemnikov $default_fields = array( 'sent' => 1, - 'responded' => 1, - 'unsubscribed' => 1, 'email' => 1, - 'password' => 1, 'firstname' => 0, 'lastname' => 0, 'salutation' => 0, 'phone' => 0, 'custom' => 0, ); + + // Volitve nimajo nekaterih polj + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + $default_fields['responded'] = 1; + $default_fields['unsubscribed'] = 1; + $default_fields['password'] = 1; + } // Ce imamo modul 360 imamo tudi odnos if(SurveyInfo::getInstance()->checkSurveyModule('360_stopinj')){ @@ -1448,11 +1525,17 @@ class SurveyInvitationsNew { } } } - # dodamo še ostala polja - $fields['last_status'] = 1; + + // Dodamo še ostala polja + // Volitve nimajo nekaterih polj + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + $fields['last_status'] = 1; + $fields['date(date_expired)'] = 1; + } + $sql_select_fields[] = 'i.last_status'; $fields['date_inserted'] = 1; - $fields['date(date_expired)'] = 1; + $fields['inserted_uid'] = 1; $sql_select_fields[] = 'i.inserted_uid'; $sql_select_fields[] = 'i.date_inserted'; @@ -1460,9 +1543,6 @@ class SurveyInvitationsNew { $fields['list_id'] = 1; $sql_select_fields[] = 'i.list_id'; - #štetje vabil - #$fields['count_inv'] = 1; - #$sql_select_fields[] = 'count(siar.arch_id) AS count_inv'; #dodamo paginacijo in poiščemo zapise $page = isset($_GET['page']) ? $_GET['page'] : '1'; @@ -1537,6 +1617,7 @@ class SurveyInvitationsNew { $lists = array(); $lists['-1'] = array('name'=>$lang['srv_invitation_new_templist']); $lists['0'] = array('name'=>$lang['srv_invitation_new_templist_author']); + if (count($lids) > 0 ) { $sql_string_lists = "SELECT * from srv_invitations_recipients_profiles WHERE pid IN(".implode(',',$lids).") "; $sql_query_lists = sisplet_query($sql_string_lists); @@ -1544,6 +1625,7 @@ class SurveyInvitationsNew { $lists[$row_lists['pid']] = array('name'=>$row_lists['name']); } } + if (count($msgs) > 0) { echo ''; foreach($msgs as $msg) { @@ -1551,6 +1633,7 @@ class SurveyInvitationsNew { } echo ''; } + if (count($errors) > 0) { echo ''; foreach($errors as $error) { @@ -1558,16 +1641,18 @@ class SurveyInvitationsNew { } echo ''; } + if ($count_all > 0 ) { # dodamo filtriranje echo '
    '; echo ' '; - #echo ''; - echo '   
    '; - - /* // Prestavljeno na dno - if ($count_without_email > 0) { - # add to database without sending e-mail - - echo '
    '; - echo ''.$lang['srv_invitation_recipients_activate'].' '; - echo ''.$lang['srv_invitation_recipients_activate_here'].''; - echo '
    '; - }*/ - + echo '
    '; echo ''; echo ''; @@ -1667,16 +1742,10 @@ class SurveyInvitationsNew { echo ''; + echo ''; # checkbox echo ''; - /* - * # uredi - echo ''; - # izbrisi - echo ''; - - */ foreach ($fields AS $fkey =>$field) { if ($field == 1) { @@ -1713,7 +1782,7 @@ class SurveyInvitationsNew { switch ($fkey) { case 'sent': echo ''; break; case 'responded': @@ -2217,36 +2286,39 @@ class SurveyInvitationsNew { function checkDefaultMessage() { global $lang, $global_user_id; - $sql_string = "SELECT id FROM srv_invitations_messages WHERE ank_id = '$this->sid' AND isdefault='1'"; - $sql_query = sisplet_query($sql_string); + $sql_query = sisplet_query("SELECT id FROM srv_invitations_messages WHERE ank_id = '$this->sid' AND isdefault='1'"); $row = $this->surveySettings; # če privzeto sporočilo ne obstaja ga skreiramo if (mysqli_num_rows($sql_query) == 0 ) { + Common::getInstance()->Init($this->sid); + $reply_to = Common::getInstance()->getReplyToEmail(); - #poiščemo ime seznama za sporočila + + # poiščemo ime seznama za sporočila $naslov = $this->generateMessageName(); $body_text = ($row['usercode_required'] == 1) ? $lang['srv_inv_message_body_text'].$lang['srv_inv_message_body_text_pass'] : $lang['srv_inv_message_body_text']; - # skreiramo osnovno sporočilo - $sqlString = "INSERT INTO srv_invitations_messages (ank_id, naslov, subject_text, body_text, reply_to, isdefault, uid, insert_time, comment, edit_uid, edit_time, url ) VALUES ('$this->sid', '".$naslov."', '".$lang['srv_inv_message_subject_text']."', '".$body_text."', '".$reply_to."', '1', '".$global_user_id."', NOW(), '', '".$global_user_id."', NOW(), '')"; - - $sqlQuery = sisplet_query($sqlString); + # skreiramo osnovno sporočilo + $sqlQuery = sisplet_query("INSERT INTO srv_invitations_messages (ank_id, naslov, subject_text, body_text, reply_to, isdefault, uid, insert_time, comment, edit_uid, edit_time, url ) VALUES ('$this->sid', '".$naslov."', '".$lang['srv_inv_message_subject_text']."', '".$body_text."', '".$reply_to."', '1', '".$global_user_id."', NOW(), '', '".$global_user_id."', NOW(), '')"); if (!$sqlQuery) { $error = mysqli_error($GLOBALS['connect_db']); } + $new_msg_id = mysqli_insert_id($GLOBALS['connect_db']); if ((int)$new_msg_id > 0) { return true; - } else { + } + else { # insert ni uspel, in privzetega sporočila nimamo return false; } - } else { + } + else { # če smo tu, imamo privzeto sporočilo return true; } @@ -2729,14 +2801,7 @@ class SurveyInvitationsNew { else { return false; } - - # če je testni uporabnik mu ne prikažemo linkov - $sql = sisplet_query("SELECT email FROM users WHERE id='$global_user_id'"); - list($email) = mysqli_fetch_row($sql); - if ( $email == 'test@1ka.si' ) { - return false; - } - + if (!isset($_POST['noNavi']) || (isset($_POST['noNavi']) && $_POST['noNavi'] != 'true')) { $_sub_action = $_GET['m']; @@ -2927,15 +2992,14 @@ class SurveyInvitationsNew { global $lang, $site_url; // Ali posiljamo maile ali ne - //$noEmailing = (isset($_GET['noemailing']) ? $_GET['noemailing'] : 0); $noEmailing = SurveySession::get('inv_noEmailing'); $row = $this->surveySettings; # Pripravimo izbor komu lahko pošiljamo - //echo '

    '.$lang['srv_inv_send_heading'].'

    '; echo '

    '; - // Text s podatki o nastavitvah posiljanja + + // Text s podatki o nastavitvah posiljanja $settings_text = ''.$lang['srv_inv_message_type'].':'; $individual = (int)$this->surveySettings['individual_invitation']; @@ -2988,40 +3052,51 @@ class SurveyInvitationsNew { echo '
    '; # damo v tabelo zaradi prilagajanja oblike levo/desno - echo '

       '; - echo ''; + echo ''; echo '
    '; + + echo '
    '; - echo '
    '; + echo ''; + + // Pri volitvah vedno posiljamo samo tistim, katerim se nismo poslali + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ - echo $lang['srv_inv_send_who_database'].'
    '; - echo ''; - echo ''; - echo ''; - - $this->advancedCondition(); - echo '
    '; - - echo ''; - echo '
    '; - echo '
    '; - echo '
    '; - $this->displayMailToSourceLists((int)$_POST['source_type']); - echo '
    '; #id="inv_select_mail_to_source_lists" - echo ''; + echo ''; + } + + echo '
    '; + + echo '
    '; - # polovimo sporočilo in prejemnike - $sql_string_m = "SELECT id, naslov, subject_text, body_text, reply_to, isdefault, comment, url FROM srv_invitations_messages WHERE ank_id = '$this->sid' AND isdefault='1'"; + echo $lang['srv_inv_send_who_database'].'
    '; + echo ''; + echo ''; + echo ''; + + $this->advancedCondition(); + echo '
    '; + + echo ''; + echo '
    '; + echo '
    '; - $sql_query_m = sisplet_query($sql_string_m); - if (mysqli_num_rows($sql_query_m) > 0 ) { - $preview_message = mysqli_fetch_assoc($sql_query_m); - } else { - #nimamo še vsebine sporočila skreiramo privzeto. - echo ''; - echo $lang['srv_invitation_note6']; - echo ''; - exit(); - } - - echo '
    '; + echo '
    '; + $this->displayMailToSourceLists((int)$_POST['source_type']); + echo '
    '; #id="inv_select_mail_to_source_lists" + + echo ''; + + # polovimo sporočilo in prejemnike + $sql_query_m = sisplet_query("SELECT id, naslov, subject_text, body_text, reply_to, isdefault, comment, url FROM srv_invitations_messages WHERE ank_id = '$this->sid' AND isdefault='1'"); + if (mysqli_num_rows($sql_query_m) > 0 ) { + $preview_message = mysqli_fetch_assoc($sql_query_m); + } + else { + #nimamo še vsebine sporočila skreiramo privzeto. + echo ''; + echo $lang['srv_invitation_note6']; + echo ''; + + exit(); + } + + echo '
    '; // Ce posiljamo preko navadne poste ali smsov, nimamo sporocila if($noEmailing == 0){ @@ -3046,7 +3121,9 @@ class SurveyInvitationsNew { echo ''; // inv_select_mail_to_respondents } - echo '
    '; + echo '
    '; echo ''; //inv_send_mail } @@ -3204,8 +3281,14 @@ class SurveyInvitationsNew { echo ' '.$lang['srv_inv_activate_survey_here'].''; } echo ''; - } else { - # anketa je aktivna lahko pošiljamo + } + # anketa je aktivna lahko pošiljamo + else { + + // Preverimo ce je vklopljen modul za volitve - obvestilo, da ni naknadnega posiljanja + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo '

    '.$lang['srv_voting_no_duplicates'].'

    '; + } $sql_string = "SELECT comment FROM srv_invitations_messages WHERE ank_id = '$this->sid' AND isdefault='1'"; $sql_query = sisplet_query($sql_string); @@ -3223,7 +3306,7 @@ class SurveyInvitationsNew { $source_type = (int)$_POST['source_type']; $source_lists = trim($_POST['source_lists']); - $respondents = $this->getRespondents2Send($send_type,$checkboxes, $source_type, $source_lists); + $respondents = $this->getRespondents2Send($send_type, $checkboxes, $source_type, $source_lists); #koliko strani imamp $numRespondents = count($respondents); $pages = ceil($numRespondents / $this->rec_send_page_limit); @@ -3652,6 +3735,8 @@ class SurveyInvitationsNew { } + + // Glavno posiljanje mail vabil function sendMail() { global $lang, $site_path, $site_url, $global_user_id, $lastna_instalacija; @@ -3714,11 +3799,8 @@ class SurveyInvitationsNew { } $subject_text = $sql_row_m['subject_text']; - $body_text = $sql_row_m['body_text']; $msg_url = $sql_row_m['url']; - - $message_naslov = $sql_row_m['naslov']; // naslov za odgovor je avtor ankete if ($this->validEmail($sql_row_m['reply_to'])) { @@ -3727,60 +3809,16 @@ class SurveyInvitationsNew { else { $reply_to = Common::getInstance()->getReplyToEmail(); } - - // prejeminki besedila + + + // prejeminki besedila $sql_query = sisplet_query("SELECT id, firstname, lastname, email, password, password, cookie, phone, salutation, custom, relation FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted='0' AND id IN (".implode(',',$rids).") ORDER BY id "); - - // polovimo sistemske spremenljivke z vrednostmi - $qrySistemske = sisplet_query("SELECT s.id, s.naslov, s.variable - FROM srv_spremenljivka s, srv_grupa g - WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->sid."' AND variable IN ("."'" . implode("','",$this->inv_variables)."') - ORDER BY g.vrstni_red, s.vrstni_red - "); - $sys_vars = array(); - $sys_vars_ids = array(); - - while ($row = mysqli_fetch_assoc($qrySistemske)) { - $sys_vars[$row['id']] = array('id'=>$row['id'], 'variable'=>$row['variable'],'naslov'=>$row['naslov']); - $sys_vars_ids[] = $row['id']; - } - - $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable FROM srv_vrednost WHERE spr_id IN(".implode(',',$sys_vars_ids).") ORDER BY vrstni_red ASC "); - while ($row = mysqli_fetch_assoc($sqlVrednost)) { - - // Ce gre za odnos imamo radio - if($sys_vars[$row['spr_id']]['variable'] == 'odnos'){ - if(!isset($sys_vars[$row['spr_id']]['vre_id'][$row['vrstni_red']])) - $sys_vars[$row['spr_id']]['vre_id'][$row['variable']] = $row['vre_id']; - } - elseif (!isset($sys_vars[$row['spr_id']]['vre_id'])) { - $sys_vars[$row['spr_id']]['vre_id'] = $row['vre_id']; - } - } - - # zakeširamo user_id za datapiping - $arryDataPiping = array(); - $qryDataPiping = sisplet_query("SELECT id,inv_res_id FROM srv_user WHERE ank_id='$this->sid' AND inv_res_id IS NOT NULL"); - while (list($dpUid,$dpInvResId) = mysqli_fetch_row($qryDataPiping)) { - if ((int)$dpInvResId > 0 && (int)$dpUid > 0) { - $arryDataPiping[$dpInvResId] = (int)$dpUid; - } - } - - # array za rezultate - $send_ok = array(); - $send_ok_ids = array(); - $send_users_data = array(); - $send_error = array(); - $send_error_ids = array(); - - # če mamo SEO - $nice_url = SurveyInfo::getSurveyLink(); - # zloopamo skozi prejemnike in personaliziramo sporočila in jih pošljemo + + # zloopamo skozi prejemnike in personaliziramo sporočila in jih pošljemo $date_sent = date ("Y-m-d H:i:s"); $numRows = mysqli_num_rows($sql_query); @@ -3822,181 +3860,36 @@ class SurveyInvitationsNew { "); $arch_id = mysqli_insert_id($GLOBALS['connect_db']); - $duplicated = array(); - while ($sql_row = mysqli_fetch_assoc($sql_query)) { + // Podatki posiljatelja + list($name, $surname, $email) = mysqli_fetch_row(sisplet_query("SELECT name, surname, email FROM users WHERE id='$global_user_id'")); - $password = $sql_row['password']; - - $email = $sql_row['email']; - if ($dont_send_duplicated == true && isset($duplicated[$email])) { - $duplicated[$email] ++; - continue; - } - - $duplicated[$email] = 1; - $individual = (int)$this->surveySettings['individual_invitation']; - - if ( ($individual == 1 && trim($email) != '' && trim($password) != '') || ($individual == 0 && trim($email) != '') ){ - - // odvisno ali imamo url za jezik. - if ($msg_url != null && trim($msg_url) != '' ) { - $url = $msg_url . ($individual == 1 ? '?code='.$password : ''); - } - else { - $url = $nice_url . ($individual == 1 ? '&code='.$password : ''); - } - - $url .= '&ai='.(int)$arch_id; - - // odjava - $unsubscribe = $site_url . 'admin/survey/unsubscribe.php?anketa=' . $this->sid . '&code='.$password; - - $user_body_text = str_replace( - array( - '#URL#', - '#URLLINK#', - '#UNSUBSCRIBE#', - '#FIRSTNAME#', - '#LASTNAME#', - '#EMAIL#', - '#CODE#', - '#PASSWORD#', - '#PHONE#', - '#SALUTATION#', - '#CUSTOM#', - '#RELATION#', - ), - array( - '' . $url . '', - $url, - '' . $lang['user_bye_hl'] . '', - $sql_row['firstname'], - $sql_row['lastname'], - $sql_row['email'], - $sql_row['password'], - $sql_row['password'], - $sql_row['phone'], - $sql_row['salutation'], - $sql_row['custom'], - $sql_row['relation'], - ), - $body_text - ); - - - // naredimo DataPiping; - if (isset($arryDataPiping[$sql_row['id']])) { - $user_body_text = Common::getInstance()->dataPiping($user_body_text, $arryDataPiping[$sql_row['id']], 0); - } - $resultX = null; - - try{ - $MA = new MailAdapter($this->sid, $type='invitation'); - $MA->addRecipients($email); - $resultX = $MA->sendMail($user_body_text, $subject_text); - - } - catch (Exception $e){ - // todo fajn bi bilo zalogirat kaj se dogaja - $__error = $e->getMessage(); - $__errStack = $e->getTraceAsString(); - } - - $_user_data = $sql_row; - if ($resultX) { - $send_ok[] = $email; - $send_ok_ids[] = $sql_row['id']; - $_user_data['status'] = 1; - # poslalo ok - } - else { - // ni poslalo - $send_error[] = $email; - $send_error_ids[] = $sql_row['id']; - $_user_data['status'] = 2; - } - - $send_users_data[] = $_user_data; - - - // updejtamo userja da mu je bilo poslano - PO NOVEM TO DELAMO SPROTI - if ( count($send_ok_ids) > 0) { - - $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET sent = '1', date_sent = '".$date_sent."' WHERE id IN (".implode(',',$send_ok_ids).")"); - if (!$sqlQuery) { - $error = mysqli_error($GLOBALS['connect_db']); - } - - // statuse popravimo samo če vabilo še ni bilo poslano ali je bila napaka - $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status = '1' WHERE id IN (".implode(',',$send_ok_ids).") AND last_status IN ('0','2')"); - if (!$sqlQuery) { - $error = mysqli_error($GLOBALS['connect_db']); - } - } - - # updejtamo status za errorje - if ( count($send_error_ids) > 0) { - - $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status = GREATEST(last_status,2) WHERE id IN (".implode(',',$send_error_ids).") AND last_status IN ('0')"); - if (!$sqlQuery) { - $error = mysqli_error($GLOBALS['connect_db']); - } - } - - - // če mamo personalizirana email vabila, userje dodamo v bazo - if ($individual == 1) { - - // dodamo še userja v srv_user da je kompatibilno s staro logiko - $strInsertDataText = array(); - $strInsertDataVrednost = array(); - - $_r = sisplet_query("INSERT INTO srv_user - (ank_id, email, cookie, pass, last_status, time_insert, inv_res_id) - VALUES - ('".$this->sid."', '".$_user_data['email']."', '".$_user_data['cookie']."', '".$_user_data['password']."', '".$_user_data['status']."', NOW(), '".$_user_data['id']."') ON DUPLICATE KEY UPDATE cookie = '".$_user_data['cookie']."', pass='".$_user_data['password']."' - "); - $usr_id = mysqli_insert_id($GLOBALS['connect_db']); - - if ($usr_id) { - - // dodamo še srv_userbase in srv userstatus - sisplet_query("INSERT INTO srv_userbase (usr_id, tip, datetime, admin_id) VALUES ('".$usr_id."','0',NOW(),'".$global_user_id."')"); - sisplet_query("INSERT INTO srv_userstatus (usr_id, tip, status, datetime) VALUES ('".$usr_id."', '0', '0', NOW())"); - - // dodamo še podatke za posameznega userja za sistemske spremenljivke - foreach ($sys_vars AS $sid => $spremenljivka) { - - $_user_variable = $this->inv_variables_link[$spremenljivka['variable']]; - - if (trim($_user_data[$_user_variable]) != '' && $_user_data[$_user_variable] != null) { - if($spremenljivka['variable'] == 'odnos') - $strInsertDataVrednost[] = "('".$sid."','".$spremenljivka['vre_id'][trim($_user_data[$_user_variable])]."','".$usr_id."')"; - else - $strInsertDataText[] = "('".$sid."','".$spremenljivka['vre_id']."','".trim($_user_data[$_user_variable])."','".$usr_id."')"; - } - } - } - else { - // lahko da user že obstaja in je šlo za duplicated keys - } - - // vstavimo v srv_data_text - if (count($strInsertDataText) > 0) { - $strInsert = "INSERT INTO srv_data_text".$this->db_table." (spr_id, vre_id, text, usr_id) VALUES "; - $strInsert .= implode(',',$strInsertDataText); - sisplet_query($strInsert); - } - // vstavimo v srv_data_vrednost - if (count($strInsertDataVrednost) > 0) { - $strInsert = "INSERT INTO srv_data_vrednost".$this->db_table." (spr_id, vre_id, usr_id) VALUES "; - $strInsert .= implode(',',$strInsertDataVrednost); - sisplet_query($strInsert); - } - } - } - } + // Podatki za posiljanje + $sending_data = array( + 'body_text' => $body_text, + 'subject_text' => $subject_text, + 'arch_id' => $arch_id, + 'msg_url' => $msg_url, + 'date_sent' => $date_sent, + 'from_email' => $email, + 'from_name' => $name.' '.$surname, + 'reply_to_email' => $reply_to + ); + + // Loop po prejemnikih in posiljanje mailov + $squalo = new SurveyInvitationsSqualo($this->sid); + if($squalo->getSqualoActive()){ + $sending_results = $squalo->sendSqualoInvitations($sql_query, $sending_data); + } + else{ + $sending_results = $this->sendMailToUsers($sql_query, $sending_data); + } + + $send_ok = $sending_results['send_ok']; + $send_ok_ids = $sending_results['send_ok_ids']; + $send_users_data = $sending_results['send_users_data']; + $send_error = $sending_results['send_error']; + $send_error_ids = $sending_results['send_error_ids']; // dodajmo še userje v povezovalno tabelo (arhiv) @@ -4010,7 +3903,8 @@ class SurveyInvitationsNew { // za arhive $_archive_recipients = array(); - // za tracking + + // za tracking $_tracking = array(); if (count($send_ok_ids) > 0) { @@ -4051,7 +3945,6 @@ class SurveyInvitationsNew { } else if (count($send_ok) > 0 ) { - list($name,$surname,$email) = mysqli_fetch_row(sisplet_query("SELECT name, surname,email FROM users WHERE id='$global_user_id'")); $who=''; if (trim($name) != '') { @@ -4133,7 +4026,292 @@ class SurveyInvitationsNew { #$this->viewAarchive($return['msg']); $this->viewSendMailFinish($return['msg']); } - + + // Posljemo mail userjem - loop in send + private function sendMailToUsers($sql_recipients_query, $sending_data){ + global $global_user_id; + global $site_url; + + + // Preverimo ce je vklopljen modul za volitve + $voting = SurveyInfo::getInstance()->checkSurveyModule('voting'); + + # če mamo SEO + $nice_url = SurveyInfo::getSurveyLink(); + + // Polovimo sistemske spremenljivke + $sys_vars = $this->getSystemVars(); + + # zakeširamo user_id za datapiping + $arryDataPiping = array(); + $qryDataPiping = sisplet_query("SELECT id, inv_res_id FROM srv_user WHERE ank_id='$this->sid' AND inv_res_id IS NOT NULL"); + while (list($dpUid, $dpInvResId) = mysqli_fetch_row($qryDataPiping)) { + + if ((int)$dpInvResId > 0 && (int)$dpUid > 0) { + $arryDataPiping[$dpInvResId] = (int)$dpUid; + } + } + + $duplicated = array(); + + # array za rezultate + $send_ok = array(); + $send_ok_ids = array(); + $send_users_data = array(); + $send_error = array(); + $send_error_ids = array(); + + // Loop po prejemnikih + while ($sql_row = mysqli_fetch_assoc($sql_recipients_query)) { + + $password = $sql_row['password']; + + $email = $sql_row['email']; + + // Preverimo ce je duplikat + if ($dont_send_duplicated == true && isset($duplicated[$email])) { + $duplicated[$email] ++; + continue; + } + + $duplicated[$email] = 1; + + $individual = (int)$this->surveySettings['individual_invitation']; + + if ( ($individual == 1 && trim($email) != '' && trim($password) != '') || ($individual == 0 && trim($email) != '') ){ + + // odvisno ali imamo url za jezik. + if ($sending_data['msg_url'] != null && trim($sending_data['msg_url']) != '' ) { + $url = $sending_data['msg_url'] . ($individual == 1 ? '?code='.$password : ''); + } + else { + $url = $nice_url . ($individual == 1 ? '&code='.$password : ''); + } + + $url .= '&ai='.(int)$sending_data['$arch_id']; + + // odjava + $unsubscribe = $site_url . 'admin/survey/unsubscribe.php?anketa=' . $this->sid . '&code='.$password; + + $user_body_text = str_replace( + array( + '#URL#', + '#URLLINK#', + '#UNSUBSCRIBE#', + '#FIRSTNAME#', + '#LASTNAME#', + '#EMAIL#', + '#CODE#', + '#PASSWORD#', + '#PHONE#', + '#SALUTATION#', + '#CUSTOM#', + '#RELATION#', + ), + array( + '' . $url . '', + $url, + '' . $lang['user_bye_hl'] . '', + $sql_row['firstname'], + $sql_row['lastname'], + $sql_row['email'], + $sql_row['password'], + $sql_row['password'], + $sql_row['phone'], + $sql_row['salutation'], + $sql_row['custom'], + $sql_row['relation'], + ), + $sending_data['body_text'] + ); + + + // naredimo DataPiping; + if (isset($arryDataPiping[$sql_row['id']])) { + $user_body_text = Common::getInstance()->dataPiping($user_body_text, $arryDataPiping[$sql_row['id']], 0); + } + $resultX = null; + + try{ + $MA = new MailAdapter($this->sid, $type='invitation'); + $MA->addRecipients($email); + $resultX = $MA->sendMail($user_body_text, $sending_data['subject_text']); + + } + catch (Exception $e){ + // todo fajn bi bilo zalogirat kaj se dogaja + $__error = $e->getMessage(); + $__errStack = $e->getTraceAsString(); + } + + $_user_data = $sql_row; + if ($resultX) { + $send_ok[] = $email; + $send_ok_ids[] = $sql_row['id']; + $_user_data['status'] = 1; + # poslalo ok + } + else { + // ni poslalo + $send_error[] = $email; + $send_error_ids[] = $sql_row['id']; + $_user_data['status'] = 2; + } + + $send_users_data[] = $_user_data; + + + // updejtamo userja da mu je bilo poslano - PO NOVEM TO DELAMO SPROTI + if ( count($send_ok_ids) > 0) { + + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET sent='1', date_sent='".$sending_data['date_sent']."' WHERE id IN (".implode(',',$send_ok_ids).")"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + + // statuse popravimo samo če vabilo še ni bilo poslano ali je bila napaka + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status='1' WHERE id IN (".implode(',',$send_ok_ids).") AND last_status IN ('0','2')"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + + // Pri volitvah za sabo pobrisemo podatke preko katerih bi lahko povezali prejemnike z responsi + if($voting){ + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients + SET cookie='', password='' + WHERE id IN (".implode(',',$send_ok_ids).") AND sent='1' AND last_status='1' AND ank_id='".$this->sid."' + "); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + } + } + + # updejtamo status za errorje + if ( count($send_error_ids) > 0) { + + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status = GREATEST(last_status,2) WHERE id IN (".implode(',',$send_error_ids).") AND last_status IN ('0')"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + } + + // če mamo personalizirana email vabila, userje dodamo v bazo + if ($individual == 1) { + + // dodamo še userja v srv_user da je kompatibilno s staro logiko + $strInsertDataText = array(); + $strInsertDataVrednost = array(); + + // Pri volitvah zaradi anonimizacije ignoriramo vse identifikatorje + if($voting){ + $_r = sisplet_query("INSERT INTO srv_user + (ank_id, cookie, pass, last_status, inv_res_id) + VALUES + ('".$this->sid."', '".$_user_data['cookie']."', '".$_user_data['password']."', '".$_user_data['status']."', '-1') ON DUPLICATE KEY UPDATE cookie = '".$_user_data['cookie']."', pass='".$_user_data['password']."' + "); + + // Ce ne belezimo parapodatka za cas responsa, anonimno zabelezimo cas zadnjega responsa + sisplet_query("UPDATE srv_anketa SET last_response_time=NOW() WHERE id='".$this->sid."'"); + } + else{ + $_r = sisplet_query("INSERT INTO srv_user + (ank_id, email, cookie, pass, last_status, time_insert, inv_res_id) + VALUES + ('".$this->sid."', '".$_user_data['email']."', '".$_user_data['cookie']."', '".$_user_data['password']."', '".$_user_data['status']."', NOW(), '".$_user_data['id']."') ON DUPLICATE KEY UPDATE cookie = '".$_user_data['cookie']."', pass='".$_user_data['password']."' + "); + } + $usr_id = mysqli_insert_id($GLOBALS['connect_db']); + + if ($usr_id) { + + // dodamo še srv_userbase in srv userstatus + sisplet_query("INSERT INTO srv_userbase (usr_id, tip, datetime, admin_id) VALUES ('".$usr_id."','0',NOW(),'".$global_user_id."')"); + sisplet_query("INSERT INTO srv_userstatus (usr_id, tip, status, datetime) VALUES ('".$usr_id."', '0', '0', NOW())"); + + // dodamo še podatke za posameznega userja za sistemske spremenljivke + foreach ($sys_vars AS $sid => $spremenljivka) { + + $_user_variable = $this->inv_variables_link[$spremenljivka['variable']]; + + if (trim($_user_data[$_user_variable]) != '' && $_user_data[$_user_variable] != null) { + if($spremenljivka['variable'] == 'odnos') + $strInsertDataVrednost[] = "('".$sid."','".$spremenljivka['vre_id'][trim($_user_data[$_user_variable])]."','".$usr_id."')"; + else + $strInsertDataText[] = "('".$sid."','".$spremenljivka['vre_id']."','".trim($_user_data[$_user_variable])."','".$usr_id."')"; + } + } + } + else { + // lahko da user že obstaja in je šlo za duplicated keys + } + + + // Pri volitvah zaradi anonimizacije ne vsatvimo nicesar v sistemske spremenljivke + if(!$voting){ + + // vstavimo v srv_data_text + if (count($strInsertDataText) > 0) { + $strInsert = "INSERT INTO srv_data_text".$this->db_table." (spr_id, vre_id, text, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataText); + sisplet_query($strInsert); + } + // vstavimo v srv_data_vrednost + if (count($strInsertDataVrednost) > 0) { + $strInsert = "INSERT INTO srv_data_vrednost".$this->db_table." (spr_id, vre_id, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataVrednost); + sisplet_query($strInsert); + } + } + } + } + } + + $results = array( + 'send_ok' => $send_ok, + 'send_ok_ids' => $send_ok_ids, + 'send_users_data' => $send_users_data, + 'send_error' => $send_error, + 'send_error_ids' => $send_error_ids, + ); + + return $results; + } + + private function getSystemVars(){ + + // polovimo sistemske spremenljivke z vrednostmi + $qrySistemske = sisplet_query("SELECT s.id, s.naslov, s.variable + FROM srv_spremenljivka s, srv_grupa g + WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->sid."' AND variable IN ("."'" . implode("','",$this->inv_variables)."') + ORDER BY g.vrstni_red, s.vrstni_red + "); + $sys_vars = array(); + $sys_vars_ids = array(); + + while ($row = mysqli_fetch_assoc($qrySistemske)) { + $sys_vars[$row['id']] = array('id'=>$row['id'], 'variable'=>$row['variable'],'naslov'=>$row['naslov']); + $sys_vars_ids[] = $row['id']; + } + + $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable FROM srv_vrednost WHERE spr_id IN(".implode(',',$sys_vars_ids).") ORDER BY vrstni_red ASC "); + while ($row = mysqli_fetch_assoc($sqlVrednost)) { + + // Ce gre za odnos imamo radio + if($sys_vars[$row['spr_id']]['variable'] == 'odnos'){ + + if(!isset($sys_vars[$row['spr_id']]['vre_id'][$row['vrstni_red']])) + $sys_vars[$row['spr_id']]['vre_id'][$row['variable']] = $row['vre_id']; + } + elseif (!isset($sys_vars[$row['spr_id']]['vre_id'])) { + $sys_vars[$row['spr_id']]['vre_id'] = $row['vre_id']; + } + } + + return $sys_vars; + } + + // Rocna aktivacija vabil function sendMailNoEmailing() { global $lang, $site_path, $site_url, $global_user_id; @@ -4206,34 +4384,9 @@ class SurveyInvitationsNew { $arch_id = mysqli_insert_id($GLOBALS['connect_db']); $duplicated = array(); - - - // polovimo sistemske spremenljivke z vrednostmi - $qrySistemske = sisplet_query("SELECT s.id, s.naslov, s.variable - FROM srv_spremenljivka s, srv_grupa g - WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->sid."' AND variable IN ("."'" . implode("','",$this->inv_variables)."') - ORDER BY g.vrstni_red, s.vrstni_red - "); - $sys_vars = array(); - $sys_vars_ids = array(); - while ($row = mysqli_fetch_assoc($qrySistemske)) { - $sys_vars[$row['id']] = array('id'=>$row['id'], 'variable'=>$row['variable'],'naslov'=>$row['naslov']); - $sys_vars_ids[] = $row['id']; - } - - $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable FROM srv_vrednost WHERE spr_id IN(".implode(',',$sys_vars_ids).") ORDER BY vrstni_red ASC "); - while ($row = mysqli_fetch_assoc($sqlVrednost)) { - - // Ce gre za odnos imamo radio - if($sys_vars[$row['spr_id']]['variable'] == 'odnos'){ - if(!isset($sys_vars[$row['spr_id']]['vre_id'][$row['vrstni_red']])) - $sys_vars[$row['spr_id']]['vre_id'][$row['variable']] = $row['vre_id']; - } - elseif (!isset($sys_vars[$row['spr_id']]['vre_id'])) { - $sys_vars[$row['spr_id']]['vre_id'] = $row['vre_id']; - } - } - + + // Polovimo sistemske spremenljivke + $sys_vars = $this->getSystemVars(); // prejeminki besedila $sql_query = sisplet_query("SELECT id, firstname, lastname, email, password, cookie, phone, salutation, custom, relation @@ -4422,6 +4575,7 @@ class SurveyInvitationsNew { $this->viewSendMailFinish($return['msg']); } + function viewSendMailFinish($msg) { global $lang, $site_url; @@ -4560,28 +4714,53 @@ class SurveyInvitationsNew { echo ''; echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; + + // Pri volitvah ne prikazemo nekaterih stolpcev + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + else{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; while ($sql_row = mysqli_fetch_assoc($sql_query)) { echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; + + // Pri volitvah ne prikazemo nekaterih stolpcev + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + else{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; } @@ -4792,6 +4971,10 @@ class SurveyInvitationsNew { function addSystemVariables($variables) { global $site_path, $lang; + // Pri modulu za volitve so responsi anonimni, zato nimamo nobenih sistemskih spremenljivk + if(SurveyInfo::getInstance()->checkSurveyModule('voting')) + return; + $system_fields = array( 'inv_field_email' => 'email', 'inv_field_firstname' => 'ime', @@ -4932,63 +5115,72 @@ class SurveyInvitationsNew { session_start(); $fields = array(); - $recipients_list=null; + $recipients_list = null; $noEmailing = SurveySession::get('inv_noEmailing'); - + # če ne obstaja začasen seznam ga naredimo (praznega) if (!isset($_SESSION['inv_rec_profile'][$this->sid])) { + $_SESSION['inv_rec_profile'][$this->sid] = array( - 'pid'=>-1, - 'name'=>$lang['srv_invitation_new_templist'], - 'fields'=>($noEmailing == 1 ? 'firstname,lastname' : 'email'), - 'respondents'=>'', - 'comment'=>$lang['srv_invitation_new_templist']); + 'pid' => -1, + 'name' => $lang['srv_invitation_new_templist'], + 'fields' => ($noEmailing == 1 ? 'firstname,lastname' : 'email'), + 'respondents' => '', + 'comment' => $lang['srv_invitation_new_templist'] + ); } #polovimo emaile in poljaiz seznama - if ( $pid > 0) { - # če imamo pid in je večji kot nič polovimo podatke iz tabele - $sql_string = "SELECT fields,respondents FROM srv_invitations_recipients_profiles WHERE pid = '".$pid."'"; - $sql_query = sisplet_query($sql_string); - $sql_row = mysqli_fetch_assoc($sql_query); - if (trim($sql_row['respondents']) != '') { - //$recipients_list = explode("\n",trim($sql_row['respondents'])); - // Zamenjamo 1ka delimiter z default vejico, ker drugače je v seznamih porušeno - $recipients_list = explode("\n",str_replace ("|~|", ",", trim($sql_row['respondents']))); - } - $_fields = explode(",", $sql_row['fields']); - if (count($_fields) > 0) { - foreach ($_fields AS $field) { - $fields[] = 'inv_field_'.$field; - } - } + if ($pid > 0) { - } else if ($pid == 0) { - # če ne je začasin porfil - avtor - $sql_string = "SELECT email, name, surname FROM users WHERE id = '".$global_user_id."'"; - $sql_query = sisplet_query($sql_string); + # če imamo pid in je večji kot nič polovimo podatke iz tabele + $sql_query = sisplet_query("SELECT fields,respondents FROM srv_invitations_recipients_profiles WHERE pid = '".$pid."'"); + $sql_row = mysqli_fetch_assoc($sql_query); + + if (trim($sql_row['respondents']) != '') { + //$recipients_list = explode("\n",trim($sql_row['respondents'])); + // Zamenjamo 1ka delimiter z default vejico, ker drugače je v seznamih porušeno + $recipients_list = explode("\n",str_replace ("|~|", ",", trim($sql_row['respondents']))); + } + + $_fields = explode(",", $sql_row['fields']); + + if (count($_fields) > 0) { + foreach ($_fields AS $field) { + $fields[] = 'inv_field_'.$field; + } + } + } + else if ($pid == 0) { + + # če ne je začasin porfil - avtor + $sql_query = sisplet_query("SELECT email, name, surname FROM users WHERE id = '".$global_user_id."'"); $rowEmail = mysqli_fetch_assoc($sql_query); // default smo rekli je vejica, ane? - $recipients_list[]= $rowEmail['email'].','.$rowEmail['name'].','.$rowEmail['surname']; - //$recipients_list[]= $rowEmail['email'].'|~|'.$rowEmail['name'].'|~|'.$rowEmail['surname']; - $fields[]= 'inv_field_email'; - $fields[]= 'inv_field_firstname'; - $fields[]= 'inv_field_lastname'; + $recipients_list[] = $rowEmail['email']; + //$recipients_list[] = $rowEmail['email'].','.$rowEmail['name'].','.$rowEmail['surname']; - } else if ($pid == -1) { + $fields[]= 'inv_field_email'; + /*$fields[]= 'inv_field_firstname'; + $fields[]= 'inv_field_lastname';*/ + } + else if ($pid == -1) { # začasen profil iz seje $_fields = explode(",",$_SESSION['inv_rec_profile'][$this->sid]['fields']); - if (count($_fields) > 0) { + + if (count($_fields) > 0) { foreach ($_fields AS $field) { $fields[] = 'inv_field_'.$field; } } + if (trim($_SESSION['inv_rec_profile'][$this->sid]['respondents']) != '') { $recipients_list = explode("\n",trim($_SESSION['inv_rec_profile'][$this->sid]['respondents'])); } - } else { + } + else { $recipients_list[] = ''; $fields[]= 'inv_field_email'; } @@ -5733,32 +5925,22 @@ class SurveyInvitationsNew { $delimit = $convertTypes['delimit']; } - #echo $delimit.$lang['srv_inv_recipients_count_inv']; echo $convertTypes['newLine']; $sqlString = "SELECT sir.*, IF(sirp.name IS NULL, '".$lang['srv_invitation_new_templist_author']."', sirp.name) AS list_name " ." FROM srv_invitations_recipients AS sir" ." LEFT JOIN srv_invitations_recipients_profiles AS sirp ON (sir.list_id = sirp.pid)" - #." LEFT JOIN srv_invitations_archive_recipients AS siar ON (sir.id = siar.rec_id)" - ." WHERE sir.ank_id = '$this->sid' AND deleted='0' ORDER BY id"; - - /* - ." FROM srv_invitations_recipients AS sir" - ." LEFT JOIN srv_invitations_recipients_profiles AS sirp ON (sir.list_id = sirp.pid)" - ." LEFT JOIN srv_invitations_archive_recipients AS siar ON (sir.id = siar.rec_id)" - ." WHERE sir.ank_id = '$this->sid' AND deleted='0' GROUP BY siar.rec_id ORDER BY id"; - */ - #$sqlString = "SELECT * FROM srv_invitations_recipients WHERE ank_id = '$this->sid' AND deleted='0' ORDER BY id"; - + $sqlQuery = sisplet_query($sqlString); if (mysqli_num_rows($sqlQuery)) { + while ($sql_row = mysqli_fetch_assoc($sqlQuery)) { + foreach ($this->inv_variables_excel AS $vkey => $inv_variable) { echo $sql_row[$inv_variable].$convertTypes['delimit']; } - #echo $sql_row['count_inv']; echo $convertTypes['newLine']; } } @@ -5796,22 +5978,39 @@ class SurveyInvitationsNew { // Dodamo vse userje v bazo podatkov kot respondente function add_users_to_database() { + // Preverimo ce je vklopljen modul za volitve + $voting = SurveyInfo::getInstance()->checkSurveyModule('voting'); + # prejeminki besedila - $sql_string = "SELECT id,firstname, lastname, email, password, password, cookie, phone, salutation, custom, relation FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted='0' AND sent='0'"; - $sql_query = sisplet_query($sql_string); + $sql_query = sisplet_query("SELECT id, firstname, lastname, email, password, password, cookie, phone, salutation, custom, relation + FROM srv_invitations_recipients + WHERE ank_id = '".$this->sid."' AND deleted='0' AND sent='0' + "); # polovimo sistemske spremenljivke z vrednostmi - $strSistemske = "SELECT s.id, s.naslov, s.variable FROM srv_spremenljivka s, srv_grupa g WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->sid."' AND variable IN("."'" . implode("','",$this->inv_variables)."') ORDER BY g.vrstni_red, s.vrstni_red"; - $qrySistemske = sisplet_query($strSistemske); + $qrySistemske = sisplet_query("SELECT s.id, s.naslov, s.variable + FROM srv_spremenljivka s, srv_grupa g + WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->sid."' + AND variable IN("."'" . implode("','",$this->inv_variables)."') + ORDER BY g.vrstni_red, s.vrstni_red + "); + $sys_vars = array(); $sys_vars_ids = array(); + while ($row = mysqli_fetch_assoc($qrySistemske)) { $sys_vars[$row['id']] = array('id'=>$row['id'], 'variable'=>$row['variable'],'naslov'=>$row['naslov']); $sys_vars_ids[] = $row['id']; } - $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable FROM srv_vrednost WHERE spr_id IN(".implode(',',$sys_vars_ids).") ORDER BY vrstni_red ASC "); + + $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable + FROM srv_vrednost + WHERE spr_id IN(".implode(',',$sys_vars_ids).") + ORDER BY vrstni_red ASC + "); while ($row = mysqli_fetch_assoc($sqlVrednost)) { - // Ce gre za odnos imamo radio + + // Ce gre za odnos imamo radio if($sys_vars[$row['spr_id']]['variable'] == 'odnos'){ if(!isset($sys_vars[$row['spr_id']]['vre_id'][$row['vrstni_red']])) $sys_vars[$row['spr_id']]['vre_id'][$row['variable']] = $row['vre_id']; @@ -5837,16 +6036,38 @@ class SurveyInvitationsNew { $strInsertUserbase = array(); $strInsertUserstatus = array(); foreach ($send_users_data AS $user_data) { - $strInsert = "INSERT INTO srv_user (ank_id, email, cookie, pass, last_status, time_insert, inv_res_id) VALUES ('".$this->sid."', '".$user_data['email']."', '".$user_data['cookie']."', '".$user_data['password']."', '".$user_data['status']."', NOW(), '".$user_data['id']."') ON DUPLICATE KEY UPDATE last_status=VALUES(last_status), inv_res_id=VALUES(inv_res_id)"; - - sisplet_query($strInsert); - $usr_id = mysqli_insert_id($GLOBALS['connect_db']); + + // Pri volitvah zaradi anonimizacije ignoriramo vse identifikatorje + if($voting){ + sisplet_query("INSERT INTO srv_user + (ank_id, cookie, pass, last_status, inv_res_id) + VALUES + ('".$this->sid."', '".$user_data['cookie']."', '".$user_data['password']."', '".$user_data['status']."', '-1') ON DUPLICATE KEY UPDATE last_status=VALUES(last_status) + "); + + // Ce ne belezimo parapodatka za cas responsa, anonimno zabelezimo cas zadnjega responsa + sisplet_query("UPDATE srv_anketa SET last_response_time=NOW() WHERE id='".$this->sid."'"); + } + else{ + sisplet_query("INSERT INTO srv_user + (ank_id, email, cookie, pass, last_status, time_insert, inv_res_id) + VALUES + ('".$this->sid."', '".$user_data['email']."', '".$user_data['cookie']."', '".$user_data['password']."', '".$user_data['status']."', NOW(), '".$user_data['id']."') ON DUPLICATE KEY UPDATE last_status=VALUES(last_status), inv_res_id=VALUES(inv_res_id) + "); + } + + + + $usr_id = mysqli_insert_id($GLOBALS['connect_db']); + if ($usr_id) { # za update v srv_invitations_respondents $send_ok_ids[] = $user_data['id']; - } else { + } + else { $send_error_ids[] = $user_data; } + # dodamo še srv_userbase in srv userstatus $strInsertUserbase[] = "('".$usr_id."','0',NOW(),'".$global_user_id."')"; $strInsertUserstatus[] = "('".$usr_id."', '0', '0', NOW())"; @@ -5877,18 +6098,24 @@ class SurveyInvitationsNew { $strInsert .= implode(',',$strInsertUserstatus); sisplet_query($strInsert); } - # vstavimo v srv_data_text - if (count($strInsertDataText) > 0) { - $strInsert = "INSERT INTO srv_data_text".$this->db_table." (spr_id, vre_id, text, usr_id) VALUES "; - $strInsert .= implode(',',$strInsertDataText); - sisplet_query($strInsert); - } - # vstavimo v srv_data_vrednost - if (count($strInsertDataVrednost) > 0) { - $strInsert = "INSERT INTO srv_data_vrednost".$this->db_table." (spr_id, vre_id, usr_id) VALUES "; - $strInsert .= implode(',',$strInsertDataVrednost); - sisplet_query($strInsert); - } + + // Pri volitvah zaradi anonimizacije ne vsatvimo nicesar v sistemske spremenljivke + if(!$voting){ + + # vstavimo v srv_data_text + if (count($strInsertDataText) > 0) { + $strInsert = "INSERT INTO srv_data_text".$this->db_table." (spr_id, vre_id, text, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataText); + sisplet_query($strInsert); + } + # vstavimo v srv_data_vrednost + if (count($strInsertDataVrednost) > 0) { + $strInsert = "INSERT INTO srv_data_vrednost".$this->db_table." (spr_id, vre_id, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataVrednost); + sisplet_query($strInsert); + } + } + sisplet_query("COMMIT"); # zloopamo skozi prejemnike in personaliziramo sporočila in jih pošljemo @@ -5897,19 +6124,13 @@ class SurveyInvitationsNew { # updejtamo userja da mu je bilo poslano if ( count($send_ok_ids) > 0) { - $sqlString = "UPDATE srv_invitations_recipients SET sent = '1', date_sent = '".$date_sent."' WHERE id IN (".implode(',',$send_ok_ids).")"; - $sqlQuery = sisplet_query($sqlString); - - if (!$sqlQuery) { + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET sent='1', date_sent = '".$date_sent."' WHERE id IN (".implode(',',$send_ok_ids).")"); + if (!$sqlQuery) $error = mysqli_error($GLOBALS['connect_db']); - } - $sqlString = "UPDATE srv_invitations_recipients SET last_status = '1' WHERE id IN (".implode(',',$send_ok_ids).") AND last_status IN ('0','2')"; - $sqlQuery = sisplet_query($sqlString); - - if (!$sqlQuery) { + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status='1' WHERE id IN (".implode(',',$send_ok_ids).") AND last_status IN ('0','2')"); + if (!$sqlQuery) $error = mysqli_error($GLOBALS['connect_db']); - } } $msg = array($lang['srv_inv_activate_respondents']. count($send_ok_ids)); @@ -6126,6 +6347,9 @@ class SurveyInvitationsNew { $sql_sub_condition = " AND i.last_status IN (".$_POST['checkboxes'].")"; } } + + // Ce imamo vklopljene volitve potem posiljamo samo tistim, katerim še nismo poslali vabila (ponovno posiljanje ni mogoce) + $sql_voting_condition = (SurveyInfo::getInstance()->checkSurveyModule('voting')) ? " AND i.sent = '0' AND i.cookie != '' AND i.password != ''" : ""; // Ce imamo posiljanje brez emaila, ni potrebno da je email vnesen za posameznega respondenta if($noEmailing == 1){ @@ -6150,11 +6374,12 @@ class SurveyInvitationsNew { else{ $sql_fields = "SELECT DISTINCT i.password, i.id, i.email, i.last_status, i.list_id FROM srv_invitations_recipients AS i"; $sql_main_condition = " WHERE i.ank_id = '".$this->sid."' AND i.deleted = '0' AND i.unsubscribed = '0' AND i.email IS NOT NULL"; - $sql_sort = " ORDER BY i.id ASC"; + $sql_sort = " ORDER BY i.id ASC"; $sql_string = $sql_fields . $advancedConditionJoin . $sql_main_condition + . $sql_voting_condition . $advancedCondition . $sql_sub_condition . $sub_query @@ -6526,12 +6751,20 @@ class SurveyInvitationsNew { echo ')'; echo ''; + echo '
    '; + echo '
    '.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_responded'].''.$lang['srv_inv_recipients_unsubscribed'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_password'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_last_status'].''.$lang['srv_inv_recipients_list_id'].''.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_list_id'].''.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_responded'].''.$lang['srv_inv_recipients_unsubscribed'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_password'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_last_status'].''.$lang['srv_inv_recipients_list_id'].'
    '.$sql_row['email'].''.$sql_row['password'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lang['srv_userstatus_'.$sql_row['last_status']].' ('.$sql_row['last_status'].')'.''.$lists[$sql_row['list_id']].''.$sql_row['email'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lists[$sql_row['list_id']].''.$sql_row['email'].''.$sql_row['password'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lang['srv_userstatus_'.$sql_row['last_status']].' ('.$sql_row['last_status'].')'.''.$lists[$sql_row['list_id']].'
    '; - echo ''; - echo ''; + + echo ''; + + echo ''; + // Volitve nimajo nekaterih polj + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')) + echo ''; echo ''; - echo ''; + + echo ''; $sql_string1 = "SELECT status, DATE_FORMAT(time_insert,'%d.%m.%Y, %T') AS status_time FROM srv_invitations_tracking WHERE res_id = '$_rec_id' AND inv_arch_id='".$sql_row['id']."' ORDER BY uniq ASC"; $sql_query1 = sisplet_query($sql_string1); @@ -6540,8 +6773,10 @@ class SurveyInvitationsNew { echo ''; - echo ''; - + // Volitve nimajo nekaterih polj + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')) + echo ''; + echo '
    '.$lang['srv_invitation_user_chronology_date'].''.$lang['srv_invitation_user_chronology_status'].'
    '.$lang['srv_invitation_user_chronology_date'].''.$lang['srv_invitation_user_chronology_status'].''.$lang['srv_inv_message_type'].'
    '.$sql_row1['status_time'].'('.$sql_row1['status'].') - '.$lang['srv_userstatus_'.$sql_row1['status']].'('.$sql_row1['status'].') - '.$lang['srv_userstatus_'.$sql_row1['status']].''; if ($sql_row['tip'] == '0') echo $lang['srv_inv_message_noemailing_type1']; @@ -6558,12 +6793,13 @@ class SurveyInvitationsNew { echo '
    '; echo ''; } - echo '
    '; - echo ''.$lang['srv_inv_recipients_final_status'].' ('.$lastStatus.') - '.$lang['srv_userstatus_'.$lastStatus]; - echo '
    '; - - - + + // Volitve nimajo nekaterih polj + if(!SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo '
    '; + echo ''.$lang['srv_inv_recipients_final_status'].' ('.$lastStatus.') - '.$lang['srv_userstatus_'.$lastStatus]; + echo '
    '; + } echo ''; // inv_select_mail_preview @@ -6682,35 +6918,64 @@ class SurveyInvitationsNew { echo ''; echo '
    '; + echo ''; + echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; + + // Pri volitvah ne prikazemo nekaterih stolpcev + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + else{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + while ($sql_row = mysqli_fetch_assoc($sql_query)) { echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - $status = $arch_user_max_status[$sql_row['res_id']]; - echo ''; - echo ''; - echo ''; + + // Pri volitvah ne prikazemo nekaterih stolpcev + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + else{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + $status = $arch_user_max_status[$sql_row['res_id']]; + echo ''; + echo ''; + echo ''; + } + echo ''; } + echo '
    '.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_responded'].''.$lang['srv_inv_recipients_unsubscribed'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_password'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_max_archive_status'].''.$lang['srv_inv_recipients_last_status'].''.$lang['srv_inv_recipients_list_id'].''.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_list_id'].''.$lang['srv_inv_recipients_sent'].''.$lang['srv_inv_recipients_responded'].''.$lang['srv_inv_recipients_unsubscribed'].''.$lang['srv_inv_recipients_email'].''.$lang['srv_inv_recipients_password'].''.$lang['srv_inv_recipients_firstname'].''.$lang['srv_inv_recipients_lastname'].''.$lang['srv_inv_recipients_max_archive_status'].''.$lang['srv_inv_recipients_last_status'].''.$lang['srv_inv_recipients_list_id'].'
    '.$sql_row['email'].''.$sql_row['password'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lang['srv_userstatus_'.$status].' ('.$status.')'.''.$lang['srv_userstatus_'.$sql_row['last_status']].' ('.$sql_row['last_status'].')'.''.$lists[$sql_row['list_id']].''.$sql_row['email'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lists[$sql_row['list_id']].''.$sql_row['email'].''.$sql_row['password'].''.$sql_row['firstname'].''.$sql_row['lastname'].''.$lang['srv_userstatus_'.$status].' ('.$status.')'.''.$lang['srv_userstatus_'.$sql_row['last_status']].' ('.$sql_row['last_status'].')'.''.$lists[$sql_row['list_id']].'
    '; + echo '
    '; // inv_select_mail_preview echo ''; // id="arc_content" @@ -6821,8 +7086,20 @@ class SurveyInvitationsNew { echo ''; - # nov način z trackingom - if ($this->newTracking == true) { + // Pri volitvah prikazemo samo osnovne stevilke - zaradi anonimizacije ni trackinga + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + + $userAccess = UserAccess::getInstance($global_user_id); + + // Ce so izklopljena ne prikazemo leve strani + if((int)$isEmail > 0 && $userAccess->checkUserAccess($what='invitations')){ + echo ''; + } + } + // Nov način z trackingom + elseif($this->newTracking == true) { $userAccess = UserAccess::getInstance($global_user_id); @@ -7161,7 +7438,8 @@ class SurveyInvitationsNew { } } - function displayInvitationStatusNew() { + // Prikaz statusov posiljanj + private function displayInvitationStatusNew() { global $lang, $admin_type, $global_user_id, $site_url, $site_path, $app_settings; $isEmail = (int)SurveyInfo::getInstance()->checkSurveyModule('email'); @@ -7176,15 +7454,17 @@ class SurveyInvitationsNew { echo '

    '; #koliko je vseh uporabnikov v bazi - $sql_string = "SELECT count(*) as cnt FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted ='0'"; - $sql_query = sisplet_query($sql_string); + $sql_query = sisplet_query("SELECT count(*) as cnt FROM srv_invitations_recipients WHERE ank_id = '".$this->sid."' AND deleted ='0'"); list($cnt_all_in_db) = mysqli_fetch_row($sql_query); #zloopamo skozi posamezna pošiljanja in preštejemo vse potrebno - $sql_string = "SELECT sia.id, sia.tip, rec_in_db, DATE_FORMAT(sia.date_send,'%d.%m.%Y, %T') AS ds, u.name, u.surname, u.email FROM srv_invitations_archive AS sia INNER JOIN users AS u ON sia.uid = u.id WHERE ank_id = '".$this->sid."' ORDER BY sia.date_send ASC;"; - #$sql_string = "SELECT * FROM srv_invitations_archive WHERE ank_id = '".$this->sid."'"; - $sql_query = sisplet_query($sql_string); - + $sql_query = sisplet_query("SELECT sia.id, sia.tip, rec_in_db, DATE_FORMAT(sia.date_send,'%d.%m.%Y, %T') AS ds, u.name, u.surname, u.email + FROM srv_invitations_archive AS sia + INNER JOIN users AS u ON sia.uid = u.id + WHERE ank_id = '".$this->sid."' + ORDER BY sia.date_send ASC; + "); + $array_dashboard = array(); $array_archive_subdata = array(); $user_max_status = array(); @@ -7624,6 +7904,146 @@ class SurveyInvitationsNew { } } + // Prikaz statusov posiljanj pri volitvah + private function displayInvitationStatusVoting() { + global $lang, $admin_type, $global_user_id, $site_url, $site_path, $app_settings; + + $isEmail = (int)SurveyInfo::getInstance()->checkSurveyModule('email'); + + $userAccess = UserAccess::getInstance($global_user_id); + + // Email vabila so omogocena + if ((int)$isEmail > 0 && $userAccess->checkUserAccess($what='invitations')) { + + echo '

    '.$lang['srv_inv_nav_email_status'].''; + echo '
    '; + echo '

    '; + + #koliko je vseh uporabnikov v bazi in kolkim je bil mail poslan + $sql_count = sisplet_query("SELECT count(id) as cnt, sent + FROM srv_invitations_recipients + WHERE ank_id='".$this->sid."' AND deleted ='0' + GROUP BY sent + "); + + $cnt_all_in_db = 0; + $cnt_sent_in_db = 0; + while($row_count = mysqli_fetch_array($sql_count)){ + + $cnt_all_in_db += (int)$row_count['cnt']; + + if($row_count['sent'] == '1'){ + $cnt_sent_in_db += (int)$row_count['cnt']; + } + } + + + echo '

    '; + $this->displayInvitationStatusVoting(); + echo '
    '; + + // Vsi v bazi + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + // Poslani + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo '
    '.$lang['srv_inv_dashboard_tbl_all'].''.(int)$cnt_all_in_db.'-100%
    '.$lang['srv_inv_dashboard_tbl_send'].''.(int)$cnt_sent_in_db.''.((int)$cnt_sent_in_db > 0 ? '100%' : '0%').''.$this->formatNumber(((int)$cnt_sent_in_db > 0 ? (int)$cnt_sent_in_db*100/(int)$cnt_all_in_db : 0),0,'%').'
    '; + + echo '

    '; + echo ''; + echo '
  • '; + } + // Email vabila niso omogocena + else { + echo '
    '.$lang['srv_inv_nav_email_status'].''; + echo '
    '; + echo '

    '; + + echo $lang['srv_inv_dashboard_not_enabled']; + + # uporabnik nima pravic omogočit vabil + if (!$userAccess->checkUserAccess($what='invitations')) { + echo '
    '.$lang['srv_inv_dashboard_no_permissions']; + } + # uporabnik lahko vklopi email vabila + else { + echo ' '.$lang['srv_omogoci'].''; + } + + echo '

    '; + echo '
    '; + echo '
    '; + } + + + // predpripravimo podatke za vsa pošiljanja + /*$cnt_by_sendings = array(); + + $all_units_count = count($cnt_by_user); + if ($all_units_count > 0) { + foreach ($cnt_by_user AS $uid => $ucnt) { + $cnt_by_sendings[$ucnt]++; + } + + echo '
    '; + + #pregled po pošiljanjih + echo '
    '; + + echo ''; + echo ''; + echo '+ '; + echo '- '; + echo ''.$lang['srv_inv_nav_email_sending_status'].''; + echo ''; + echo Help::display('srv_inv_cnt_by_sending'); + echo ''; + + echo '
    '; + + echo '
    '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($cnt_by_sendings > 0) { + foreach ($cnt_by_sendings AS $cnt => $units) { + echo ''; + echo ''; + echo ''; + $percent = ($all_units_count > 0) ? $units / $all_units_count * 100 : 0; + echo ''; + echo ''; + } + } + echo ''; + echo ''; + echo ''; + $percent = ($all_units_count > 0) ? $all_units_count / $all_units_count * 100 : 0; + echo ''; + echo ''; + echo '
    '.$lang['srv_inv_sending_overview_cnt'].''.$lang['srv_inv_sending_overview_units'].''.$lang['srv_inv_sending_overview_percentage'].'
    '.$cnt.''.$units.''.Common::formatNumber ($percent,0,null,'%').'
    '.$lang['srv_inv_sending_overview_sum'].''.$all_units_count.''.Common::formatNumber ($percent,0,null,'%').'
    '; + echo '
    '; + echo '
    '; + }*/ + } + function showInvitationSettings() { global $lang, $admin_type, $global_user_id, $site_url, $site_path, $app_settings; @@ -7682,10 +8102,10 @@ class SurveyInvitationsNew { else{ if($noEmailing == 1){ - echo ''; + echo '
    '; } else{ - echo '
    '; + echo '
    '; echo ''; echo ''; } @@ -7699,6 +8119,15 @@ class SurveyInvitationsNew { echo '
    '; echo '
    '; + + // Preverimo ce je vklopljen modul za volitve - potem ne pustimo nobenih preklopov + $voting_disabled = ''; + if(SurveyInfo::getInstance()->checkSurveyModule('voting')){ + $voting_disabled = ' disabled'; + + // Warning za volitve + echo '

    '.$lang['srv_voting_warning'].'

    '; + } $individual = (int)$this->surveySettings['individual_invitation']; @@ -7709,8 +8138,8 @@ class SurveyInvitationsNew { echo ' '.Help::display('srv_user_base_individual_invitaition_note2').' '; else echo ' '.Help::display('srv_user_base_individual_invitaition_note').' '; - echo ''; - echo ''; + echo ''; + echo ''; echo '

    '; // Ce niso indvidualizirana imamo samo nacin posiljanja @@ -7719,8 +8148,8 @@ class SurveyInvitationsNew { // Nacin posiljanja (email, posta, sms...) echo '

    '; echo ''; - echo ''; - echo ''; + echo ''; + echo ''; echo '

    '; // Nacin dokumentiranja (posta, sms, drugo) @@ -7728,9 +8157,9 @@ class SurveyInvitationsNew { $noEmailingType = SurveySession::get('inv_noEmailing_type'); echo '

    '; echo ''; - echo ''; - echo ''; - echo ''; + echo ''; + echo ''; + echo ''; echo '

    '; } } @@ -7740,8 +8169,8 @@ class SurveyInvitationsNew { // Nacin posiljanja (email, posta, sms...) echo '

    '; echo ''; - echo ''; - echo ''; + echo ''; + echo ''; echo '

    '; // Nacin dokumentiranja (posta, sms, drugo) @@ -7749,9 +8178,9 @@ class SurveyInvitationsNew { $noEmailingType = SurveySession::get('inv_noEmailing_type'); echo '

    '; echo ''; - echo ''; - echo ''; - echo ''; + echo ''; + echo ''; + echo ''; echo '

    '; } @@ -7759,8 +8188,8 @@ class SurveyInvitationsNew { if($noEmailing != 1){ echo '

    '; echo ''; - echo ''; - echo ''; + echo ''; + echo ''; echo '

    '; } @@ -7778,10 +8207,10 @@ class SurveyInvitationsNew { // Dostop brez kode - echo '

    '; - echo ''; - echo ''; - echo '

    '; + echo '

    '; if($row['usercode_skip'] > 0){ echo '
    '; echo ''; @@ -7818,15 +8247,13 @@ class SurveyInvitationsNew { // Gorenje tega nima if (!Common::checkModule('gorenje')){ + echo '
    '; echo '
    '.$lang['srv_email_setting_title'].''; echo '
    '; echo '
    '; - - //echo '

    V delu...

    '; $this->viewServerSettings(); - echo '
    '; echo '
    '; @@ -9850,6 +10277,7 @@ class SurveyInvitationsNew { global $global_user_id; global $mysql_database_name; global $aai_instalacija; + $row = SurveyInfo::getInstance()->getSurveyRow(); @@ -9866,7 +10294,36 @@ class SurveyInvitationsNew { // Admini na testu, www in virtualkah imajo 1ka smtp if(($admin_type == 0) && ($mysql_database_name == 'www1kasi' || $mysql_database_name == 'test1kasi' || $mysql_database_name == 'real1kasi')) $enabled1ka = true; + + // Squalo + $squalo = new SurveyInvitationsSqualo($this->sid); + if($squalo->getSqualoEnabled()){ + + // Vklop squalo + echo '

    '; + + echo ''.$lang['srv_squalo'].': '; + + echo ''; + echo ''; + + echo '

    '; + + + // Squalo nastavitve... + echo '
    '; + + echo $lang['srv_squalo_active']; + + echo '
    '; + } + + + // Izbira streznika + echo '
    '; + // Opozorilo, ce imamo vklopljena vabila, da gre za iste nastavitve echo '

    '.$lang['srv_email_server_settings_warning'].'

    '; @@ -9894,10 +10351,13 @@ class SurveyInvitationsNew { echo $lang['srv_email_setting_adapter2'].' '; echo Help :: display('srv_mail_mode'); + + echo '
    '; + #1KA $enkaSettings = $MA->get1KASettings($raziskave=true); - echo '
    is1KA() ? ' class="displayNone"' : '').'>'; + echo '
    is1KA() || $squalo->getSqualoActive() ? ' class="displayNone"' : '').'>'; echo '
    '.$lang['srv_email_setting_settings'].''; echo '
    '; # from @@ -9928,7 +10388,7 @@ class SurveyInvitationsNew { #GMAIL - Google $enkaSettings = $MA->getGoogleSettings(); - echo '
    isGoogle() ? ' class="displayNone"' : '').'>'; + echo '
    isGoogle() || $squalo->getSqualoActive() ? ' class="displayNone"' : '').'>'; echo '
    '.$lang['srv_email_setting_adapter1_note'].'
    '; echo '
    '.$lang['srv_email_setting_settings'].'
    '; # from @@ -9944,7 +10404,7 @@ class SurveyInvitationsNew { #SMTP $enkaSettings = $MA->getSMTPSettings(); - echo '
    isSMTP() ? ' class="displayNone"' : '').'>'; + echo '
    isSMTP() || $squalo->getSqualoActive() ? ' class="displayNone"' : '').'>'; echo '
    '.$lang['srv_email_setting_adapter2_note'].'
    '; echo '
    '.$lang['srv_email_setting_settings'].'
    '; # from - NICE @@ -9965,7 +10425,7 @@ class SurveyInvitationsNew { #autentikacija echo '

    '; echo $lang['srv_email_setting_autentication']; - echo ''; + echo ''; echo ''; @@ -9973,7 +10433,7 @@ class SurveyInvitationsNew { #Varnost SMTPSecure echo '

    '; echo $lang['srv_email_setting_encryption']; - echo ''; + echo ''; echo ''; @@ -10018,8 +10478,8 @@ class SurveyInvitationsNew { echo $lang['srv_email_setting_btn_save'] . '

    '; // Gumb preveri nastavitve - echo ''; + echo ''; if ($_GET['s'] == '1') { diff --git a/admin/survey/classes/surveyEmails/squalo/api_test.php b/admin/survey/classes/surveyEmails/squalo/api_test.php new file mode 100644 index 000000000..52fcec065 --- /dev/null +++ b/admin/survey/classes/surveyEmails/squalo/api_test.php @@ -0,0 +1,97 @@ +createList($list_name='Testni seznam Xyz'); + +/*$result = $squalo->createNewsletter( + $list_id = '11', + $subject = 'Testek', + $body = 'Testno sporočilo...', + $body_alt = 'Testno sporočilo ALT...', + $from_email = 'peter.hrvatin@gmail.com', + $from_name = 'Peter', + $reply_to_email = 'peter.hrvatin@gmail.com', + $language = 'sl' +);*/ + +//$result = $squalo->addRecipient($email='peter.h1203@gmail.com', $list_id='5'); + +//$result = $squalo->sendEmails($newsletter_id='24'); + +//$result = $squalo->deleteRecipient($recipient_id='2'); + + + + +$squalo = new SurveyInvitationsSqualo($ank_id='10'); + + +// Ustvarimo testni seznam z respondenti +/*$list_name = 'List test 1'; +$recipients = array( + array( + 'email' => 'peter.hrvatin@gmail.com', + 'custom_attributes' => array( + 'firstname' => 'Peterx', + 'lastname' => 'Hrvax', + 'url' => 'www.google.si' + ) + ), + array( + 'email' => 'peter.h1203@gmail.com', + 'custom_attributes' => array( + 'firstname' => 'Peter12', + 'lastname' => 'Hrva12', + 'url' => 'www.najdi.si' + ) + ), + array( + 'email' => 'peter.hrvatin@siol.net', + 'custom_attributes' => array( + 'firstname' => 'Petersiol', + 'lastname' => 'Hrvaxsiol', + 'url' => 'www.1ka.si' + ) + ) +); + +$result = $squalo->createList($list_name, $recipients);*/ + + +// Ustvarimo mail in posljemo +/*$list_id = '11'; + +$subject = 'Testni subject'; +$body = 'Sporočilo v emailu... Še nekaj atributov url: #URL#, ime: #FIRSTNAME#'; + +$from_email = 'peter.hrvatin@gmail.com'; +$from_name = 'Peter Gmail'; +$reply_to_email = 'peter.hrvatin@gmail.com'; + +$result = $squalo->sendEmail($subject, $body, $list_id, $from_email, $from_name, $reply_to_email);*/ + + + + +echo '
    '; print_r($result); echo '
    '; + + + + \ No newline at end of file diff --git a/admin/survey/modules/mod_squalo/class.SqualoApi.php b/admin/survey/classes/surveyEmails/squalo/class.SqualoApi.php similarity index 84% rename from admin/survey/modules/mod_squalo/class.SqualoApi.php rename to admin/survey/classes/surveyEmails/squalo/class.SqualoApi.php index ce821357a..6c1c8cf73 100644 --- a/admin/survey/modules/mod_squalo/class.SqualoApi.php +++ b/admin/survey/classes/surveyEmails/squalo/class.SqualoApi.php @@ -2,7 +2,7 @@ /** * - * Class ki vsebuje funkcije APIJA (prijava, registracija v 1ko) + * Class ki vsebuje funkcije Squalo APIJA (dodajanje prejemnikov, posiljanje...) * */ @@ -37,15 +37,24 @@ class SqualoApi { // Decode json response $response_array = json_decode($response, true); + // Zalogiramo kaj se je dogajalo + $SL = new SurveyLog(); + // Error if($response_array['errorCode'] != '0'){ $result['error'] = $response_array["errorMessage"]. ' (code '.$response_array["errorCode"].')'; $result['success'] = false; + + $SL->addMessage(SurveyLog::MAILER, "NAPAKA pri SQUALO API klicu ('.$action.')! ".$result['error']); } else{ $result = $response_array; $result['success'] = true; + + $SL->addMessage(SurveyLog::MAILER, "USPEŠEN SQUALO API klic ('.$action.')."); } + + $SL->write(); return $result; } @@ -142,10 +151,12 @@ class SqualoApi { 'ordering' => $list_number, 'published' => true ); - + $response = $this->executeCall($action, $method, $data); - return $response; + $list_id = ($response['success']) ? $response['list']['id'] : '0'; + + return $list_id; } /* @@ -182,7 +193,9 @@ class SqualoApi { $response = $this->executeCall($action, $method, $data); - return $response; + $newsletter_id = ($response['success']) ? $response['newsletter']['id'] : '0'; + + return $newsletter_id; } /* @@ -214,6 +227,19 @@ class SqualoApi { $action = 'create-recipient'; $method = 'POST'; + // Pretvorimo atribute po meri v pravo obliko + $custom_attributes_squalo = array(); + $i = 0; + foreach($custom_attributes as $key => $value){ + + $custom_attributes_squalo[$i] = array( + "name" => $key, + "value" => $value + ); + + $i++; + } + $data = array( 'email' => $email, 'listIds' => array($list_id), @@ -221,12 +247,15 @@ class SqualoApi { 'confirmed' => true, 'enabled' => true, 'gdprCanSend' => true, - 'gdprCanTrack' => true + 'gdprCanTrack' => true, + 'customAttributes' => $custom_attributes_squalo ); $response = $this->executeCall($action, $method, $data); - return $response; + $recipient_id = ($response['success']) ? $response['recipient']['id'] : '0'; + + return $recipient_id; } /* diff --git a/admin/survey/classes/surveyEmails/squalo/class.SurveyInvitationsSqualo.php b/admin/survey/classes/surveyEmails/squalo/class.SurveyInvitationsSqualo.php new file mode 100644 index 000000000..4dd983ae4 --- /dev/null +++ b/admin/survey/classes/surveyEmails/squalo/class.SurveyInvitationsSqualo.php @@ -0,0 +1,480 @@ +anketa = $anketa; + + // Preverimo ce je squalo omogocen na tej instalaciji in anketi + $this->squaloEnabled = $this->checkSqualoEnabled() ? true : false; + + // Preverimo ce je squalo vklopljen na anketi + $this->squaloActive = $this->checkSqualoActive() ? true : false; + } + + + public function getSqualoEnabled(){ + return $this->squaloEnabled; + } + + public function getSqualoActive(){ + return $this->squaloActive; + } + + // Preverimo ce je squalo omogocen na instalaciji + private function checkSqualoEnabled(){ + global $mysql_database_name; + global $admin_type; + global $squalo_user; + global $squalo_key; + + // Zaenkrat imajo squalo samo admini + if($admin_type != 0) + return false; + + // Squalo je omogocen samo na testu, www in virtualkah + if($mysql_database_name != 'www1kasi' && $mysql_database_name != 'test1kasi' && $mysql_database_name != 'real1kasi') + return false; + + // Zaenkrat imajo squalo samo admini + if(!isset($squalo_user) || $squalo_user == '' || !isset($squalo_key) || $squalo_key == '') + return false; + + return true; + } + + // Preverimo ce je squalo vklopljen na anketi + private function checkSqualoActive(){ + + $vabila_type = SurveyInfo::getSurveyModules('email'); + + // Vklopljen squalo + if($vabila_type === '2'){ + return true; + } + + return false; + } + + + // Izvedemo squalo pošiljanje (ustvarimo seznam, newsletter, pošljemo maile) + public function sendSqualoInvitations($sql_recipients_query, $sending_data){ + global $global_user_id; + global $site_url; + + + // Preverimo ce je vklopljen modul za volitve + $voting = SurveyInfo::getInstance()->checkSurveyModule('voting'); + + // Ce mamo SEO + $nice_url = SurveyInfo::getSurveyLink(); + + // Ali imamo individualizirana vabila s kodo + $surveySettings = SurveyInfo::getInstance()->getSurveyRow(); + $individual = (int)$surveySettings['individual_invitation']; + + # zakeširamo user_id za datapiping + $arryDataPiping = array(); + $qryDataPiping = sisplet_query("SELECT id, inv_res_id FROM srv_user WHERE ank_id='$this->anketa' AND inv_res_id IS NOT NULL"); + while (list($dpUid, $dpInvResId) = mysqli_fetch_row($qryDataPiping)) { + + if ((int)$dpInvResId > 0 && (int)$dpUid > 0) { + $arryDataPiping[$dpInvResId] = (int)$dpUid; + } + } + + $duplicated = array(); + + # array za rezultate + $send_ok = array(); + $send_ok_ids = array(); + $send_users_data = array(); + $send_error = array(); + $send_error_ids = array(); + + // Loop po prejemnikih + $recipients = array(); + while ($sql_row = mysqli_fetch_assoc($sql_recipients_query)) { + + $password = $sql_row['password']; + + $email = $sql_row['email']; + + // Preverimo ce je duplikat + if ($dont_send_duplicated == true && isset($duplicated[$email])) { + $duplicated[$email] ++; + continue; + } + + $duplicated[$email] = 1; + + + + if ( ($individual == 1 && trim($email) != '' && trim($password) != '') || ($individual == 0 && trim($email) != '') ){ + + // odvisno ali imamo url za jezik. + if ($sending_data['msg_url'] != null && trim($sending_data['msg_url']) != '' ) { + $url = $sending_data['msg_url'] . ($individual == 1 ? '?code='.$password : ''); + } + else { + $url = $nice_url . ($individual == 1 ? '&code='.$password : ''); + } + + $url .= '&ai='.(int)$sending_data['arch_id']; + + // odjava + $unsubscribe = $site_url . 'admin/survey/unsubscribe.php?anketa=' . $this->anketa . '&code='.$password; + + $custom_attributes = array( + 'url' => '' . $url . '', + 'urllink' => $url, + 'firstname' => $sql_row['firstname'], + 'lastname' => $sql_row['lastname'], + //'email' => $sql_row['email'], + 'code' => $sql_row['password'], + 'password' => $sql_row['password'], + 'phone' => $sql_row['phone'], + 'custom' => $sql_row['custom'], + 'unsubscribe' => $unsubscribe, + ); + + $recipients[] = array( + 'email' => $sql_row['email'], + 'name' => $sql_row['firstname'], + 'surname' => $sql_row['lastname'], + 'custom_attributes' => $custom_attributes + ); + + $_user_data = $sql_row; + $send_users_data[] = $_user_data; + + $send_emails[] = $email; + $send_ids[] = $sql_row['id']; + } + } + + // Ustvarimo squalo seznam z respondenti + $list_name = $global_user_id.'_'.$this->anketa.'_'.$sending_data['arch_id']; + + $list_id = $this->createList($list_name, $recipients); + + + // Ce so vsi prejemniki ok dodani na seznam, ustvarimo mail in posljemo + if($list_id != false) + $squalo_sending_result = $this->sendEmail($sending_data['subject_text'], $sending_data['body_text'], $list_id, $sending_data['from_email'], $sending_data['from_name'], $sending_data['reply_to_email']); + + + // Napaka pri squalo posiljanju oz. dodajanju - zabelezimo kot da ni noben ok poslan + if(!$squalo_sending_result || !$list_id){ + + $send_error = $send_emails; + $send_error_ids = $send_ids; + + foreach($send_users_data as $key => $val){ + $val['status'] = 2; + $send_users_data[$key] = $val; + } + + $send_ok = array(); + $send_ok_ids = array(); + + # updejtamo status za errorje + if (count($send_error_ids) > 0) { + + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status = GREATEST(last_status,2) WHERE id IN (".implode(',',$send_error_ids).") AND last_status IN ('0')"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + } + + $results = array( + 'send_ok' => $send_ok, + 'send_ok_ids' => $send_ok_ids, + 'send_users_data' => $send_users_data, + 'send_error' => $send_error, + 'send_error_ids' => $send_error_ids, + ); + + return $results; + } + + + // Ok squalo posiljanje - zabelezimo da so bili vsi ok poslani + $send_ok = $send_emails; + $send_ok_ids = $send_ids; + + foreach($send_users_data as $key => $val){ + $val['status'] = 1; + $send_users_data[$key] = $val; + } + + $send_error = array(); + $send_error_ids = array(); + + // updejtamo userja da mu je bilo poslano - SQUALO je vedno vse ok + if (count($send_ok_ids) > 0) { + + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET sent='1', date_sent='".$sending_data['date_sent']."' WHERE id IN (".implode(',',$send_ok_ids).")"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + + // statuse popravimo samo če vabilo še ni bilo poslano ali je bila napaka + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients SET last_status='1' WHERE id IN (".implode(',',$send_ok_ids).") AND last_status IN ('0','2')"); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + + // Pri volitvah za sabo pobrisemo podatke preko katerih bi lahko povezali prejemnike z responsi + if($voting){ + $sqlQuery = sisplet_query("UPDATE srv_invitations_recipients + SET cookie='', password='' + WHERE id IN (".implode(',',$send_ok_ids).") AND sent='1' AND last_status='1' AND ank_id='".$this->anketa."' + "); + if (!$sqlQuery) { + $error = mysqli_error($GLOBALS['connect_db']); + } + } + } + + + // če mamo personalizirana email vabila, userje dodamo v bazo + if ($individual == 1) { + + if (SurveyInfo::getInstance()->getSurveyColumn('db_table') == 1) + $db_table = '_active'; + + $inv_variables_link = array('email'=>'email','geslo'=>'password','ime'=>'firstname','priimek'=>'lastname','naziv'=>'salutation','telefon'=>'phone','drugo'=>'custom','odnos'=>'relation','last_status'=>'last_status','sent'=>'sent','responded'=>'responded','unsubscribed'=>'unsubscribed'); + + $sys_vars = $this->getSystemVars(); + + foreach($send_users_data as $_user_data){ + + // dodamo še userja v srv_user da je kompatibilno s staro logiko + $strInsertDataText = array(); + $strInsertDataVrednost = array(); + + // Pri volitvah zaradi anonimizacije ignoriramo vse identifikatorje + if($voting){ + $_r = sisplet_query("INSERT INTO srv_user + (ank_id, cookie, pass, last_status, inv_res_id) + VALUES + ('".$this->anketa."', '".$_user_data['cookie']."', '".$_user_data['password']."', '".$_user_data['status']."', '-1') ON DUPLICATE KEY UPDATE cookie = '".$_user_data['cookie']."', pass='".$_user_data['password']."' + "); + + // Ce ne belezimo parapodatka za cas responsa, anonimno zabelezimo cas zadnjega responsa + sisplet_query("UPDATE srv_anketa SET last_response_time=NOW() WHERE id='".$this->anketa."'"); + } + else{ + $_r = sisplet_query("INSERT INTO srv_user + (ank_id, email, cookie, pass, last_status, time_insert, inv_res_id) + VALUES + ('".$this->anketa."', '".$_user_data['email']."', '".$_user_data['cookie']."', '".$_user_data['password']."', '".$_user_data['status']."', NOW(), '".$_user_data['id']."') ON DUPLICATE KEY UPDATE cookie = '".$_user_data['cookie']."', pass='".$_user_data['password']."' + "); + } + $usr_id = mysqli_insert_id($GLOBALS['connect_db']); + + if ($usr_id) { + + // dodamo še srv_userbase in srv userstatus + sisplet_query("INSERT INTO srv_userbase (usr_id, tip, datetime, admin_id) VALUES ('".$usr_id."','0',NOW(),'".$global_user_id."')"); + sisplet_query("INSERT INTO srv_userstatus (usr_id, tip, status, datetime) VALUES ('".$usr_id."', '0', '0', NOW())"); + + + + // dodamo še podatke za posameznega userja za sistemske spremenljivke + foreach ($sys_vars AS $sid => $spremenljivka) { + + $_user_variable = $inv_variables_link[$spremenljivka['variable']]; + + if (trim($_user_data[$_user_variable]) != '' && $_user_data[$_user_variable] != null) { + if($spremenljivka['variable'] == 'odnos') + $strInsertDataVrednost[] = "('".$sid."','".$spremenljivka['vre_id'][trim($_user_data[$_user_variable])]."','".$usr_id."')"; + else + $strInsertDataText[] = "('".$sid."','".$spremenljivka['vre_id']."','".trim($_user_data[$_user_variable])."','".$usr_id."')"; + } + } + } + + // Pri volitvah zaradi anonimizacije ne vsatvimo nicesar v sistemske spremenljivke + if(!$voting){ + + // vstavimo v srv_data_text + if (count($strInsertDataText) > 0) { + $strInsert = "INSERT INTO srv_data_text".$db_table." (spr_id, vre_id, text, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataText); + sisplet_query($strInsert); + } + // vstavimo v srv_data_vrednost + if (count($strInsertDataVrednost) > 0) { + $strInsert = "INSERT INTO srv_data_vrednost".$db_table." (spr_id, vre_id, usr_id) VALUES "; + $strInsert .= implode(',',$strInsertDataVrednost); + sisplet_query($strInsert); + } + } + } + } + + + $results = array( + 'send_ok' => $send_ok, + 'send_ok_ids' => $send_ok_ids, + 'send_users_data' => $send_users_data, + 'send_error' => $send_error, + 'send_error_ids' => $send_error_ids, + ); + + return $results; + } + + + // Ustvarimo nov seznam in nanj dodamo respondente + private function createList($list_name, $recipients){ + + $squalo_api = new SqualoApi(); + + // Ustvarimo prazen seznam + $list_id = $squalo_api->createList($list_name); + + // Napaka pri ustvarjanju seznama + if($list_id == '0'){ + echo 'Napaka pri ustvarjanju Squalo seznama.'; + return false; + } + + // Dodamo respondente na ta seznam + foreach($recipients as $recipient){ + + $email = $recipient['email']; + + $custom_attributes = $recipient['custom_attributes']; + + $recipient_id = $squalo_api->addRecipient($email, $list_id, $custom_attributes); + + // Napaka pri ustvarjanju seznama + if($recipient_id == '0'){ + echo 'Napaka pri dodajanju prejemnika '.$email.' na Squalo seznam.'; + return false; + } + } + + return $list_id; + } + + // Ustvarimo nov email z vsebino (api klic createNewsletter) in posljemo na vse naslove v seznamu + private function sendEmail($subject, $body, $list_id, $from_email, $from_name, $reply_to_email){ + + $squalo_api = new SqualoApi(); + + // Zamenjamo datapiping npr. #URL# -> {subtag:url}... + $subject = self::squaloDatapiping($subject); + $body = self::squaloDatapiping($body); + + $language = 'sl'; + + // Api klic za ustvarjanje emaila + $newsletter_id = $squalo_api->createNewsletter($list_id, $subject, $body, $body, $from_email, $from_name, $reply_to_email, $language='sl'); + + // Napaka pri ustvarjanju newsletterja + if($newsletter_id == '0'){ + echo 'Napaka pri ustvarjanju Squalo newsletterja.'; + return false; + } + + // Api klic za posiljanje na naslove + $result = $squalo_api->sendEmails($newsletter_id); + + // Napaka pri ustvarjanju newsletterja + if(!$result['success']){ + echo 'Napaka pri pošiljanju emailov za Squalo newsletter '.$newsletter_id.'.'; + return false; + } + + return $result; + } + + + private static function squaloDatapiping($text){ + + $text_fixed = str_replace( + array( + '#URL#', + '#URLLINK#', + '#UNSUBSCRIBE#', + '#FIRSTNAME#', + '#LASTNAME#', + '#EMAIL#', + '#CODE#', + '#PASSWORD#', + '#PHONE#', + '#SALUTATION#', + '#CUSTOM#', + '#RELATION#', + ), + array( + '{subtag:url}', + '{subtag:urllink}', + '{subtag:unsubscribe}', + '{subtag:firstname}', + '{subtag:lastname}', + '{subtag:email}', + '{subtag:code}', + '{subtag:password}', + '{subtag:phone}', + '{subtag:salutation}', + '{subtag:custom}' + ), + $text + ); + + return $text_fixed; + } + + private function getSystemVars(){ + + $inv_variables = array('email','password','ime','priimek','naziv','telefon','drugo','odnos'); + + // polovimo sistemske spremenljivke z vrednostmi + $qrySistemske = sisplet_query("SELECT s.id, s.naslov, s.variable + FROM srv_spremenljivka s, srv_grupa g + WHERE s.sistem='1' AND s.gru_id=g.id AND g.ank_id='".$this->anketa."' AND variable IN ("."'" . implode("','",$inv_variables)."') + ORDER BY g.vrstni_red, s.vrstni_red + "); + $sys_vars = array(); + $sys_vars_ids = array(); + + while ($row = mysqli_fetch_assoc($qrySistemske)) { + $sys_vars[$row['id']] = array('id'=>$row['id'], 'variable'=>$row['variable'],'naslov'=>$row['naslov']); + $sys_vars_ids[] = $row['id']; + } + + $sqlVrednost = sisplet_query("SELECT spr_id, id AS vre_id, vrstni_red, variable FROM srv_vrednost WHERE spr_id IN(".implode(',',$sys_vars_ids).") ORDER BY vrstni_red ASC "); + while ($row = mysqli_fetch_assoc($sqlVrednost)) { + + // Ce gre za odnos imamo radio + if($sys_vars[$row['spr_id']]['variable'] == 'odnos'){ + + if(!isset($sys_vars[$row['spr_id']]['vre_id'][$row['vrstni_red']])) + $sys_vars[$row['spr_id']]['vre_id'][$row['variable']] = $row['vre_id']; + } + elseif (!isset($sys_vars[$row['spr_id']]['vre_id'])) { + $sys_vars[$row['spr_id']]['vre_id'] = $row['vre_id']; + } + } + + return $sys_vars; + } +} \ No newline at end of file diff --git a/admin/survey/classes/tracking/CrossRoad.php b/admin/survey/classes/tracking/CrossRoad.php index 45d89105b..2b584acde 100644 --- a/admin/survey/classes/tracking/CrossRoad.php +++ b/admin/survey/classes/tracking/CrossRoad.php @@ -35,6 +35,7 @@ class CrossRoad { break; case A_NONRESPONSE_GRAPH: case A_USABLE_RESP: + case A_KAKOVOST_RESP: case A_SPEEDER_INDEX: case A_TEXT_ANALYSIS: case A_GEOIP_LOCATION: @@ -183,6 +184,7 @@ class CrossRoad { case A_UPORABNOST: case A_HIERARHIJA_SUPERADMIN: case A_KVIZ: + case A_VOTING: case A_ADVANCED_PARADATA: case A_JSON_SURVEY_EXPORT: case A_VNOS: diff --git a/admin/survey/definition.php b/admin/survey/definition.php index 2c265a256..3a9176331 100644 --- a/admin/survey/definition.php +++ b/admin/survey/definition.php @@ -39,6 +39,7 @@ define("NAVI_HIERARHIJA_SUPERADMIN", "NAVI_HIERARHIJA_SUPERADMIN"); define("NAVI_HIERARHIJA", "NAVI_HIERARHIJA"); define("NAVI_KVIZ", "NAVI_KVIZ"); + define("NAVI_VOTING", "NAVI_VOTING"); define("NAVI_VNOS", "NAVI_VNOS"); define("NAVI_PHONE", "NAVI_PHONE"); define("NAVI_360", "NAVI_360"); @@ -56,6 +57,7 @@ define("A_NONRESPONSE_GRAPH", "nonresponse_graph"); define("A_PARA_GRAPH", "para_graph"); define("A_USABLE_RESP", "usable_resp"); + define("A_KAKOVOST_RESP", "kakovost_resp"); define("A_SPEEDER_INDEX", "speeder_index"); define("A_TEXT_ANALYSIS", "text_analysis"); define("A_GEOIP_LOCATION", "geoip_location"); @@ -178,6 +180,7 @@ define('A_UPORABNOST', 'uporabnost'); define('A_HIERARHIJA_SUPERADMIN', 'hierarhija_superadmin'); define('A_KVIZ', 'kviz'); + define('A_VOTING', 'voting'); define('A_VNOS', 'vnos'); define('A_PHONE', 'telefon'); # Telefon define('T_PHONE', 'telefon'); # Telefon diff --git a/admin/survey/export/latexclasses/Vprasanja/BesediloLatex.php b/admin/survey/export/latexclasses/Vprasanja/BesediloLatex.php index 1443e99cc..92516901e 100644 --- a/admin/survey/export/latexclasses/Vprasanja/BesediloLatex.php +++ b/admin/survey/export/latexclasses/Vprasanja/BesediloLatex.php @@ -141,12 +141,19 @@ class BesediloLatex extends LatexSurveyElement $rowVrednost = mysqli_fetch_array($sqlVrednosti); if($spremenljivke['tip'] == 21){ //ce je ta novo besedilo, ki je v uporabi - //$sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' AND loop_id $loop_id"); - $sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' "); + $sqlUserAnswerString = "SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' "; + if($loop_id){ //ce je prisoten se loop_id, je tega potrebno dodati sql stavku + $sqlUserAnswerString .= " AND loop_id=$loop_id"; + } + //echo $sqlUserAnswerString."
    "; }elseif($spremenljivke['tip'] == 4){ //ce je ta staro besedilo, ki ni vec v uporabi vsaj 9 let (2020) - $sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' "); + //$sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' "); + $sqlUserAnswerString = "SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' "; + if($loop_id){ //ce je prisoten se loop_id, je tega potrebno dodati sql stavku + $sqlUserAnswerString .= " AND loop_id=$loop_id"; + } } - + $sqlUserAnswer = sisplet_query($sqlUserAnswerString); $userAnswer = mysqli_fetch_assoc($sqlUserAnswer); //echo "userAnswer: ".$userAnswer['text']."
    "; //ureditev polja s podatki trenutnega uporabnika - konec ############################################## @@ -237,20 +244,28 @@ class BesediloLatex extends LatexSurveyElement $rowVrednost['naslov'] = $naslov; } + //ureditev polja s podatki trenutnega uporabnika ###################################################### if($spremenljivke['tip'] == 21){ //ce je ta novo besedilo, ki je v uporabi - //$sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' AND loop_id $loop_id"); - $sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' "); - $userAnswer = mysqli_fetch_assoc($sqlUserAnswer); - //echo "userAnswer: ".$userAnswer['text']."
    "; - }elseif($spremenljivke['tip'] == 4){ //ce je ta staro besedilo, ki ni vec v uporabi vsaj 9 let (2020) - //$sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' AND loop_id $loop_id"); - $sqlUserAnswer = sisplet_query("SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' "); - $userAnswer = mysqli_fetch_assoc($sqlUserAnswer); - //echo "SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."'
    "; + $sqlUserAnswerString = "SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' AND vre_id='".$rowVrednost['id']."' "; + if($loop_id){ //ce je prisoten se loop_id, je tega potrebno dodati sql stavku + $sqlUserAnswerString .= " AND loop_id=$loop_id"; + } //echo "userAnswer: ".$userAnswer['text']."
    "; + }elseif($spremenljivke['tip'] == 4){ //ce je ta staro besedilo, ki ni vec v uporabi vsaj 9 let (2020) + $sqlUserAnswerString = "SELECT text FROM srv_data_text".$db_table." WHERE spr_id='".$spremenljivke['id']."' AND usr_id='".$usr_id."' "; + if($loop_id){ //ce je prisoten se loop_id, je tega potrebno dodati sql stavku + $sqlUserAnswerString .= " AND loop_id=$loop_id"; + } } + $sqlUserAnswer = sisplet_query($sqlUserAnswerString); + $userAnswer = mysqli_fetch_assoc($sqlUserAnswer); //ureditev polja s podatki trenutnega uporabnika - konec ############################################## + + //priprava besedila za izpis + $stringNaslov = $rowVrednost['naslov']; + $stringNaslov = Common::getInstance()->dataPiping($stringNaslov, $usr_id, $loop_id); + //priprava besedila za izpis - konec //ce ni other ali missing if( (int)$rowVrednost['other'] == 0 ){ @@ -295,9 +310,7 @@ class BesediloLatex extends LatexSurveyElement } array_push($textBoxes, $dataTextBox); //filanje polja s praznimi text box-i - - array_push($besedila, $this->encodeText($rowVrednost['naslov']) ); //filanje polja z besedili - + array_push($besedila, $this->encodeText($stringNaslov)); //filanje polja z besedili if($okvir == 0){ if($spremenljivke['tip'] == 21){ //ce je ta novo besedilo, ki je v uporabi @@ -306,8 +319,8 @@ class BesediloLatex extends LatexSurveyElement } //izpis besedila - if($polozajBesedila!=0){ //ce je prisotno dodatno besedilo ob okvirju - $tex .= $this->encodeText($rowVrednost['naslov'])." "; + if($polozajBesedila!=0){ //ce je prisotno dodatno besedilo ob okvirju + $tex .= $this->encodeText($stringNaslov)." "; } $tex .= ' '.$dataTextBox; }elseif($spremenljivke['tip'] == 4){ //ce je ta staro besedilo, ki ni vec v uporabi vsaj 9 let (2020) @@ -325,7 +338,7 @@ class BesediloLatex extends LatexSurveyElement } //izpis besedila - $tex .= $this->encodeText($rowVrednost['naslov']); + $tex .= $this->encodeText($stringNaslov)." "; //izpis text box-a dolocene sirine in visine z besedilom odgovora $tex .= ' '.$dataTextBox; @@ -335,10 +348,15 @@ class BesediloLatex extends LatexSurveyElement } else { //drugace, ce imamo missinge ali podobne, jih zabelezi v polju // imamo polje drugo - ne vem, zavrnil... - $array_others[$rowVrednost['id']] = array( + /* $array_others[$rowVrednost['id']] = array( 'naslov'=>$rowVrednost['naslov'], 'vrstni_red'=>$rowVrednost['vrstni_red'], 'value'=>$text[$rowVrednost['vrstni_red']], + ); */ + $array_others[$rowVrednost['id']] = array( + 'naslov'=>$this->encodeText($stringNaslov), + 'vrstni_red'=>$rowVrednost['vrstni_red'], + 'value'=>$text[$rowVrednost['vrstni_red']], ); } @@ -434,7 +452,7 @@ class BesediloLatex extends LatexSurveyElement } } - + //echo "tex koda: ".$tex." in indeks $indeksZaWhile
    "; return $tex; } diff --git a/admin/survey/export/latexclasses/Vprasanja/RadioCheckboxSelectLatex.php b/admin/survey/export/latexclasses/Vprasanja/RadioCheckboxSelectLatex.php index a8636e28e..29283747b 100644 --- a/admin/survey/export/latexclasses/Vprasanja/RadioCheckboxSelectLatex.php +++ b/admin/survey/export/latexclasses/Vprasanja/RadioCheckboxSelectLatex.php @@ -95,8 +95,9 @@ class RadioCheckboxSelectLatex extends LatexSurveyElement if (strip_tags($rowl['naslov2']) != '') $rowVrednost['naslov2'] = $rowl['naslov2']; #ce je respondent odgovarjal v drugem jeziku - konec ################ - $stringTitle = ($this->encodeText(( $rowVrednost['naslov'] ) ? $rowVrednost['naslov'] : ( ( $rowVrednost['naslov2'] ) ? $rowVrednost['naslov2'] : $rowVrednost['variable'] ), $rowVrednost['id'] )); - $stringTitle = '\\textcolor{crta}{'.$stringTitle.'}'; + $stringTitle = ( $rowVrednost['naslov'] ) ? $rowVrednost['naslov'] : ( ( $rowVrednost['naslov2'] ) ? $rowVrednost['naslov2'] : $rowVrednost['variable'] ); + $stringTitle = Common::getInstance()->dataPiping($stringTitle, $usr_id, $loop_id); + $stringTitle = '\\textcolor{crta}{'.$this->encodeText($stringTitle).'}'; //echo $stringTitle."za indeks: ".$indeksZaWhile."
    "; //stetje stevila vrstic @@ -263,14 +264,14 @@ class RadioCheckboxSelectLatex extends LatexSurveyElement while ($rowVrednost = mysqli_fetch_assoc($sqlVrednosti)){ $prop['full'] = ( isset($userAnswer[$rowVrednost['id']]) ); - - //if($this->language>1){ //ce je prevod ankete if($this->prevod){ //ce je prevod ankete $rowl = $this->srv_language_vrednost($rowVrednost['id']); //pridobi prevod naslova v ustreznem jeziku $stringTitle = ((( $rowl['naslov'] ) ? $rowl['naslov'] : ( ( $rowl['naslov2'] ) ? $rowl['naslov2'] : $rowl['variable'] ) )); //prevod naslova v ustreznem jeziku }else{ $stringTitle = ((( $rowVrednost['naslov'] ) ? $rowVrednost['naslov'] : ( ( $rowVrednost['naslov2'] ) ? $rowVrednost['naslov2'] : $rowVrednost['variable'] ) )); } + + $stringTitle = Common::getInstance()->dataPiping($stringTitle, $usr_id, $loop_id); //echo "naslov: $stringTitle
    "; //echo "jezik: ".$this->language."
    "; diff --git a/admin/survey/export/latexclasses/Vprasanja/SteviloLatex.php b/admin/survey/export/latexclasses/Vprasanja/SteviloLatex.php index ade06a602..a102afaf9 100644 --- a/admin/survey/export/latexclasses/Vprasanja/SteviloLatex.php +++ b/admin/survey/export/latexclasses/Vprasanja/SteviloLatex.php @@ -273,7 +273,9 @@ class SteviloLatex extends LatexSurveyElement } //izpis besedila enote - $tex .= $this->encodeText($rowVrednost['naslov']); + $stringEnota = $rowVrednost['naslov']; + $stringEnota = Common::getInstance()->dataPiping($stringEnota, $usr_id, $loop_id); + $tex .= $this->encodeText($stringEnota); if($okvir == 1){ //ce rabimo prazen okvir, izpisi //izpis praznega text box-a dolocene sirine in visine @@ -315,9 +317,11 @@ class SteviloLatex extends LatexSurveyElement $tex .= ' \\\\ '; //pojdi v novo vrstico }else{ $tex .= ' & '; //v nov stolpec tabele - } - - $tex .= ' '.$this->encodeText($rowVrednost['naslov']); + } + + $stringEnota = $rowVrednost['naslov']; + $stringEnota = Common::getInstance()->dataPiping($stringEnota, $usr_id, $loop_id); + $tex .= ' '.$this->encodeText($stringEnota); if($indeksZaWhile==1&&$export_format=='pdf'){ //ce je prvi okvir in je pdf //$tex .= ' \hspace{0.5cm} '; //dodaj še nekaj prostora, za prvim okvirjem, da bo dovolj prostora diff --git a/admin/survey/export/latexclasses/class.LatexStatus.php b/admin/survey/export/latexclasses/class.LatexStatus.php index a4434a7de..590107af3 100644 --- a/admin/survey/export/latexclasses/class.LatexStatus.php +++ b/admin/survey/export/latexclasses/class.LatexStatus.php @@ -261,6 +261,10 @@ class LatexStatus { $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_6']; $prefix = ', '; } + if ($row['voting'] == 1) { + $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_18']; + $prefix = ', '; + } if ($row['phone'] == 1) { $enabled_advanced .= $prefix . $lang['srv_vrsta_survey_type_7']; $prefix = ', '; diff --git a/admin/survey/export/latexclasses/class.LatexSurvey.php b/admin/survey/export/latexclasses/class.LatexSurvey.php index fb0fcec11..2e1dce066 100644 --- a/admin/survey/export/latexclasses/class.LatexSurvey.php +++ b/admin/survey/export/latexclasses/class.LatexSurvey.php @@ -440,11 +440,12 @@ class LatexSurvey{ if ( !$this->getGrupa() ){ if ( SurveyInfo::getInstance()->getSurveyShowConcl() && SurveyInfo::getInstance()->getSurveyConcl() ) { // ce obstaja footer izpisemo footer + } } - // Izpis grafa in tabele za NIJZ na koncu dokumenta + // Izpis grafa in tabele za NIJZ na koncu dokumenta global $site_domain; if( ($site_domain == 'test.1ka.si' && $this->anketa == '8892') || ($site_domain == 'anketa.nijz.si' && $this->anketa == '126738') ){ @@ -1056,12 +1057,6 @@ class LatexSurvey{ $output .= ' '.$row_if['label'].' '; $output .= ') '; } - //echo $output."
    "; -/* $this->pdf->SetTextColor(0,0,150); - $this->pdf->setFont('','B',$this->font); - $this->pdf->MultiCell(90, 1, $this->encodeText($output),0,'L',0,1,0,0); - $this->pdf->SetTextColor(0,0,0); - $this->pdf->setFont('','',$this->font); */ return $output; } @@ -1249,56 +1244,7 @@ class LatexSurvey{ $posLi = strpos($text, $findLi); $posPar = strpos($text, $findPar); - //ureditev izrisa slike -/* if($posImg !== false){ - $numOfImgs = substr_count($text, $findImg); //stevilo ''); //pozicija, kjer se konca html koda za img - $textPotem = substr($textPotem, $posImgEnd+strlen('/>')); //tekst od konca html kode za img dalje - - //$text = $textPrej.' '.PIC_SIZE_ANS."{".$this->path2UploadedImages."".$this->getImageName($text, 0, 'path2UploadedImages."".$this->getImageName($text, 0, 'path2UploadedImages."".$this->getImageName($text, 0, '"); - if(filesize($imageNameTest) > 0){ - $text = $textPrej.' '.PIC_SIZE_ANS."{".$imageName."}".' '.$textPotem; - }else{ - $image = $lang['srv_pc_unavailable']; - $text = $textPrej.' '.$image.' '.$textPotem; - } - } - - //pred ureditvijo posebnih karakterjev, odstrani del teksta s kodo za sliko, da se ne pojavijo tezave zaradi imena datoteke od slike - $findImgCode = '\includegraphics'; - $posOfImgCode = strpos($text, $findImgCode); - //echo $posOfImgCode."
    "; - $textToImgCode = substr($text, 0, $posOfImgCode); //tekst do $findImgCode - //echo $textToImgCode."
    "; - $textFromImgCode = substr($text, $posOfImgCode); //tekst po $findImgCode - //echo $textFromImgCode."
    "; - $findImgCodeEnd = '}'; - //$posOfImgCodeEnd = strpos($text, $findImgCodeEnd); - $posOfImgCodeEnd = strpos($textFromImgCode, $findImgCodeEnd); - //echo $posOfImgCodeEnd."
    "; - $textAfterImgCode = substr($textFromImgCode, $posOfImgCodeEnd+1); //tekst po $findImgCodeEnd - //echo $textAfterImgCode."
    "; - $textOfImgCode = substr($text, $posOfImgCode, $posOfImgCodeEnd+1); - //echo $textOfImgCode."
    "; - - $text = $textToImgCode.$textAfterImgCode; - - //pred ureditvijo posebnih karakterjev, odstrani del teksta s kodo za sliko, da se ne pojavijo tezave zaradi imena datoteke od slike - konec - } */ - //ureditev izrisa slike - konec - + //ureditev posebnih karakterjev za Latex http://www.cespedes.org/blog/85/how-to-escape-latex-special-characters, https://en.wikibooks.org/wiki/LaTeX/Special_Characters#Other_symbols $text = str_replace('\\','\textbackslash{} ',$text); //$text = str_replace('{','\{',$text); @@ -1334,13 +1280,7 @@ class LatexSurvey{ ###################### } //ureditev preureditve html kode ul in li v latex itemize - konec - - //po ureditvi posebnih karakterjev, dodati del teksta s kodo za sliko, ce je slika prisotna -/* if($posImg !== false){ - $text = substr_replace($text, $textOfImgCode, $posOfImgCode, 0); - } */ - //po ureditvi posebnih karakterjev, dodati del teksta s kodo za sliko, ce je slika prisotna - + if($posPar !== false){ //ce je kaksen html tag

    , dodaj prazno vrstico oz. break if($numOfUl!=0 && $posLi !== false){ //ce imamo ul in li $divider = ' '; diff --git a/admin/survey/export/latexclasses/class.LatexSurveyElement.php b/admin/survey/export/latexclasses/class.LatexSurveyElement.php index 7dea1e3b3..f6a5bae6d 100644 --- a/admin/survey/export/latexclasses/class.LatexSurveyElement.php +++ b/admin/survey/export/latexclasses/class.LatexSurveyElement.php @@ -906,7 +906,12 @@ class LatexSurveyElement{ //$text = str_replace('{','\{',$text); //$text = str_replace('}','\}',$text); $text = str_replace('$','\$ ',$text); - $text = str_replace('#','\# ',$text); + + if(substr_count($text, '#')){ //ce je stevilo # vecje od 1 + $text = str_replace('#','\#',$text); + }else{ + $text = str_replace('#','\# ',$text); + } $text = str_replace('%','\% ',$text); $text = str_replace('€','\euro',$text); $text = str_replace('^','\textasciicircum{} ',$text); @@ -923,7 +928,7 @@ class LatexSurveyElement{ //$text = str_replace('>','\textgreater ',$text); $text = str_replace('>',' \textgreater ',$text); //ureditev posebnih karakterjev za Latex - konec - + //ureditev grskih crk $text = str_replace('α','\textalpha ',$text); $text = str_replace('β','\textbeta ',$text); @@ -962,6 +967,7 @@ class LatexSurveyElement{ $text = str_replace('

      ','\begin{itemize} ', $text); $text = str_replace('','\item ', $text); + $text = str_replace('','\end{itemize} \ ', $text); } //echo "prazno v html: ".strpos($text, '\r')."
      "; @@ -980,6 +986,7 @@ class LatexSurveyElement{ if($numOfOl!=0 && $posLi !== false){ //ce imamo ol in li $text = str_replace('
        ','\begin{enumerate} ', $text); $text = str_replace('
      1. ','\item ', $text); + $text = str_replace('','\end{enumerate} \ ', $text); } //echo "prazno v html: ".strpos($text, '\r')."
        "; @@ -1022,12 +1029,12 @@ class LatexSurveyElement{ $numOfAt = substr_count($text, $findAt); //stevilo '@' v besedilu $posAt = strpos($text, $findAt); - if($posAt){ //ce je prisotna afna + if($posAt && $posSpace1){ //ce je prisotna afna in je prisoten presledek v besedilu //echo "afna je: $posAt
        "; //echo "Encoding: ".$text."
        "; //najdi prvi presledek po afni - //echo substr($text, $posAt) ."
        "; + //echo substr($text, $posAt) ."
        "; $posSpace1Mail = strpos(substr($text, $posAt), $findSpace); //najdi pozicijo prvega presledka v besedilu po e-naslovu $posSpace1Mail = $posSpace1Mail+$posAt; //koncna pozicija, ce se gleda celotno besedilo //echo $posSpace1Mail."
        "; @@ -1058,16 +1065,18 @@ class LatexSurveyElement{ //RESEVANJE odstranitve dodatnih style tag-ov po ul, ipd. ####################################################### $findStyleTag = 'style="'; - $findStyleTagEnd = '"'; + //$findStyleTagEnd = '"'; + $findStyleTagEnd = '">'; $numOfStyleTags = substr_count($text, $findStyleTag); //stevilo 'style=" ' v tekstu - //echo "stevilo style: ".$numOfStyleTags."
        "; + //echo "stevilo style: ".$numOfStyleTags."
        "; + //echo $text."
        "; for($s=0; $s<$numOfStyleTags; $s++){ //za vsako najdeno 'style=" ' besedilo, uredi njeno odstranitev $posStyleTag = strpos($text, $findStyleTag); - $posStyleTagEnd = strpos($text, $findStyleTagEnd, $posStyleTag); //strpos(string,find,start) najdi $findStyleTagEnd v $text, isci od $posStyleTag dalje - $dolzinaOff = $posStyleTagEnd - $posStyleTag + 2; + $posStyleTagEnd = strpos($text, $findStyleTagEnd, $posStyleTag); //strpos(string,find,start) najdi $findStyleTagEnd v $text, isci od $posStyleTag dalje + $dolzinaOff = $posStyleTagEnd - $posStyleTag + 2; $text = substr_replace($text, "", $posStyleTag, $dolzinaOff); - } + } //RESEVANJE odstranitve dodatnih style tag-ov po ul, ipd. - konec ################################################# if($pos === false && $posImg === false) { //v tekstu ni br in img @@ -1933,16 +1942,28 @@ class LatexSurveyElement{ if($spremenljivke['tip'] != 24){ //ce ni kombinirana tabela z izberite s seznama (ali roleto) if($data[$userAnswerIndex[$spremenljivke['id']]]==($indeksRoleta)){ //ce je prisoten podatek za doloceni indeks seznama, ga izpisi //$tex .= '& \\textcolor{crta}{'.$vodoravniOdgovori[$j-1].'}'; //izris odgovora respondenta v roleti ali seznamu - $tex .= '& \\textcolor{crta}{\footnotesize{'.$vodoravniOdgovori[$j-1].'}}'; //izris odgovora respondenta v roleti ali seznamu + //$tex .= '& \\textcolor{crta}{\footnotesize{'.$vodoravniOdgovori[$j-1].'}}'; //izris odgovora respondenta v roleti ali seznamu + if($export_data_type==0||$export_data_type==2){ //ce skrcen izvoz + $tex .= '& \\textcolor{crta}{\footnotesize{'.$vodoravniOdgovori[$j-1].'}}'; //izris odgovora respondenta v roleti ali + }else{ //drugace, ce je razsirjen izvoz + $tex .= '\item[] \\textcolor{crta}{\footnotesize{'.$vodoravniOdgovori[$j-1].'}}'; //izris odgovora respondenta v roleti ali + } $noItem = 0; - //echo "podatek je prisoten: ".$vodoravniOdgovori[$j-1]."
        "; + }else{ - $tex .= ' & '.$vodoravniOdgovori[$j-1]; + //echo "tip exp: ".$export_data_type."
        "; + if($export_data_type==0||$export_data_type==2){ //ce skrcen izvoz + $tex .= ' & '.$vodoravniOdgovori[$j-1]; + }else{ //drugace, ce je razsirjen izvoz + + } + } }else{ //ce je kombinirana tabela z izberite s seznama (ali roleto) $tex .= ' & \\textcolor{crta}{\footnotesize{'.$data[$userAnswerIndex[$spremenljivke['id']]].'}}'; /* echo "odgovor : ".$data[$userAnswerIndex[$spremenljivke['id']]]."
        "; print_r($data); */ + } } @@ -1960,6 +1981,7 @@ class LatexSurveyElement{ } $roletaAliSeznam = 1; } + }elseif($enota == 4){ //ena moznost proti drugi //$tex .= '& '.$simbolTex.' & '.$lang['srv_tip_sample_t6_4_vmes'].' & '.$simbolTex; @@ -2098,7 +2120,7 @@ class LatexSurveyElement{ if( in_array($spremenljivkeTip, array(21, 4, 7, 8, 18, 17)) ){//ce je tip besedilo ali stevilo ali datum ali vsota ali razvrscanje $rowAnswers = mysqli_fetch_assoc($sqlUserAnswer); if($rowAnswers){ //ce je kaj v bazi - //echo "Nekaj je v bazi za spremenljivko".$spremenljivkeId." in usr".$usr_id."
        "; + //echo "Nekaj je v bazi za spremenljivko".$spremenljivkeId." in usr ".$usr_id."
        "; $userDataPresent++; } }else{ @@ -2119,7 +2141,7 @@ class LatexSurveyElement{ } } } - //echo "userDataPresent za tip ".$spremenljivkeTip." id".$spremenljivkeId." usr ".$usr_id." je:".$userDataPresent."
        "; + //echo "userDataPresent za tip ".$spremenljivkeTip." id ".$spremenljivkeId." usr ".$usr_id." je:".$userDataPresent." in loop: $loop_id
        "; return $userDataPresent; } #funkcija, ki skrbi za preverjanje obstoja podatkov za vprasanja, ki niso grid ali kombinirana tabela - konec diff --git a/admin/survey/export/latexclasses/textemp/images/1F600.png b/admin/survey/export/latexclasses/textemp/images/1F600.png new file mode 100644 index 000000000..1762bef48 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F600.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F601.png b/admin/survey/export/latexclasses/textemp/images/1F601.png new file mode 100644 index 000000000..446a48e43 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F601.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F602.png b/admin/survey/export/latexclasses/textemp/images/1F602.png new file mode 100644 index 000000000..78d899150 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F602.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F603.png b/admin/survey/export/latexclasses/textemp/images/1F603.png new file mode 100644 index 000000000..4fb253b7d Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F603.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F604.png b/admin/survey/export/latexclasses/textemp/images/1F604.png new file mode 100644 index 000000000..7da90823f Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F604.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F605.png b/admin/survey/export/latexclasses/textemp/images/1F605.png new file mode 100644 index 000000000..dd9011511 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F605.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F606.png b/admin/survey/export/latexclasses/textemp/images/1F606.png new file mode 100644 index 000000000..8e3dd87f1 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F606.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F607.png b/admin/survey/export/latexclasses/textemp/images/1F607.png new file mode 100644 index 000000000..133cac957 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F607.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F608.png b/admin/survey/export/latexclasses/textemp/images/1F608.png new file mode 100644 index 000000000..1f7573228 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F608.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F609.png b/admin/survey/export/latexclasses/textemp/images/1F609.png new file mode 100644 index 000000000..5a7928875 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F609.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60A.png b/admin/survey/export/latexclasses/textemp/images/1F60A.png new file mode 100644 index 000000000..803976558 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60A.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60B.png b/admin/survey/export/latexclasses/textemp/images/1F60B.png new file mode 100644 index 000000000..2dcb06503 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60B.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60C.png b/admin/survey/export/latexclasses/textemp/images/1F60C.png new file mode 100644 index 000000000..7e2461669 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60C.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60D.png b/admin/survey/export/latexclasses/textemp/images/1F60D.png new file mode 100644 index 000000000..27c54a967 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60D.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60E.png b/admin/survey/export/latexclasses/textemp/images/1F60E.png new file mode 100644 index 000000000..4adfdd620 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60E.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F60F.png b/admin/survey/export/latexclasses/textemp/images/1F60F.png new file mode 100644 index 000000000..bfcbb8ce3 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F60F.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F610.png b/admin/survey/export/latexclasses/textemp/images/1F610.png new file mode 100644 index 000000000..c395697f7 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F610.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F611.png b/admin/survey/export/latexclasses/textemp/images/1F611.png new file mode 100644 index 000000000..5ab59d805 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F611.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F612.png b/admin/survey/export/latexclasses/textemp/images/1F612.png new file mode 100644 index 000000000..9f3cd6a08 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F612.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F613.png b/admin/survey/export/latexclasses/textemp/images/1F613.png new file mode 100644 index 000000000..4ca29d0e2 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F613.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F614.png b/admin/survey/export/latexclasses/textemp/images/1F614.png new file mode 100644 index 000000000..8f331d86b Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F614.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F615.png b/admin/survey/export/latexclasses/textemp/images/1F615.png new file mode 100644 index 000000000..4b4f424c5 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F615.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F616.png b/admin/survey/export/latexclasses/textemp/images/1F616.png new file mode 100644 index 000000000..e4be491f5 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F616.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F617.png b/admin/survey/export/latexclasses/textemp/images/1F617.png new file mode 100644 index 000000000..ad8e1935a Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F617.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F618.png b/admin/survey/export/latexclasses/textemp/images/1F618.png new file mode 100644 index 000000000..cfe0184e1 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F618.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F619.png b/admin/survey/export/latexclasses/textemp/images/1F619.png new file mode 100644 index 000000000..62ca3a2ff Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F619.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61A.png b/admin/survey/export/latexclasses/textemp/images/1F61A.png new file mode 100644 index 000000000..8013f8b78 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61A.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61B.png b/admin/survey/export/latexclasses/textemp/images/1F61B.png new file mode 100644 index 000000000..7f7199b7e Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61B.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61C.png b/admin/survey/export/latexclasses/textemp/images/1F61C.png new file mode 100644 index 000000000..fd7f80fe5 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61C.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61D.png b/admin/survey/export/latexclasses/textemp/images/1F61D.png new file mode 100644 index 000000000..fbebd5212 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61D.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61E.png b/admin/survey/export/latexclasses/textemp/images/1F61E.png new file mode 100644 index 000000000..518b00146 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61E.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F61F.png b/admin/survey/export/latexclasses/textemp/images/1F61F.png new file mode 100644 index 000000000..767ab7d23 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F61F.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F620.png b/admin/survey/export/latexclasses/textemp/images/1F620.png new file mode 100644 index 000000000..c2368b138 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F620.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F621.png b/admin/survey/export/latexclasses/textemp/images/1F621.png new file mode 100644 index 000000000..0b6f15ee1 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F621.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F622.png b/admin/survey/export/latexclasses/textemp/images/1F622.png new file mode 100644 index 000000000..4ba1dbafb Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F622.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F623.png b/admin/survey/export/latexclasses/textemp/images/1F623.png new file mode 100644 index 000000000..ef291a2e6 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F623.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F624.png b/admin/survey/export/latexclasses/textemp/images/1F624.png new file mode 100644 index 000000000..0bf759fc5 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F624.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F625.png b/admin/survey/export/latexclasses/textemp/images/1F625.png new file mode 100644 index 000000000..8c815718d Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F625.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F626.png b/admin/survey/export/latexclasses/textemp/images/1F626.png new file mode 100644 index 000000000..fe8fa6b4f Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F626.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F627.png b/admin/survey/export/latexclasses/textemp/images/1F627.png new file mode 100644 index 000000000..bdfe7ea24 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F627.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F628.png b/admin/survey/export/latexclasses/textemp/images/1F628.png new file mode 100644 index 000000000..fce482dd7 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F628.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F629.png b/admin/survey/export/latexclasses/textemp/images/1F629.png new file mode 100644 index 000000000..60129d3b9 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F629.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62A.png b/admin/survey/export/latexclasses/textemp/images/1F62A.png new file mode 100644 index 000000000..46f792fb7 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62A.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62B.png b/admin/survey/export/latexclasses/textemp/images/1F62B.png new file mode 100644 index 000000000..d8db47bd8 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62B.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62C.png b/admin/survey/export/latexclasses/textemp/images/1F62C.png new file mode 100644 index 000000000..5d7f06aa8 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62C.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62D.png b/admin/survey/export/latexclasses/textemp/images/1F62D.png new file mode 100644 index 000000000..0f1f15f23 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62D.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62E.png b/admin/survey/export/latexclasses/textemp/images/1F62E.png new file mode 100644 index 000000000..ed91c85ff Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62E.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F62F.png b/admin/survey/export/latexclasses/textemp/images/1F62F.png new file mode 100644 index 000000000..eeee08377 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F62F.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F630.png b/admin/survey/export/latexclasses/textemp/images/1F630.png new file mode 100644 index 000000000..371efe8f8 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F630.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F631.png b/admin/survey/export/latexclasses/textemp/images/1F631.png new file mode 100644 index 000000000..16e8308fb Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F631.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F632.png b/admin/survey/export/latexclasses/textemp/images/1F632.png new file mode 100644 index 000000000..723dc121c Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F632.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F633.png b/admin/survey/export/latexclasses/textemp/images/1F633.png new file mode 100644 index 000000000..3601842d2 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F633.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F634.png b/admin/survey/export/latexclasses/textemp/images/1F634.png new file mode 100644 index 000000000..c220d7f19 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F634.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F635.png b/admin/survey/export/latexclasses/textemp/images/1F635.png new file mode 100644 index 000000000..a24ddb135 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F635.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F636.png b/admin/survey/export/latexclasses/textemp/images/1F636.png new file mode 100644 index 000000000..fb55393f2 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F636.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F637.png b/admin/survey/export/latexclasses/textemp/images/1F637.png new file mode 100644 index 000000000..72354e664 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F637.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F638.png b/admin/survey/export/latexclasses/textemp/images/1F638.png new file mode 100644 index 000000000..895ce2aba Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F638.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F639.png b/admin/survey/export/latexclasses/textemp/images/1F639.png new file mode 100644 index 000000000..e8f625c93 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F639.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63A.png b/admin/survey/export/latexclasses/textemp/images/1F63A.png new file mode 100644 index 000000000..9d7d3fbfd Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63A.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63B.png b/admin/survey/export/latexclasses/textemp/images/1F63B.png new file mode 100644 index 000000000..f66d46ef0 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63B.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63C.png b/admin/survey/export/latexclasses/textemp/images/1F63C.png new file mode 100644 index 000000000..b1f0656cd Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63C.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63D.png b/admin/survey/export/latexclasses/textemp/images/1F63D.png new file mode 100644 index 000000000..3ce1f1994 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63D.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63E.png b/admin/survey/export/latexclasses/textemp/images/1F63E.png new file mode 100644 index 000000000..c2920f263 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63E.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F63F.png b/admin/survey/export/latexclasses/textemp/images/1F63F.png new file mode 100644 index 000000000..60fff0bee Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F63F.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F640.png b/admin/survey/export/latexclasses/textemp/images/1F640.png new file mode 100644 index 000000000..e007fd6bd Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F640.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F641.png b/admin/survey/export/latexclasses/textemp/images/1F641.png new file mode 100644 index 000000000..297548acf Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F641.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F642.png b/admin/survey/export/latexclasses/textemp/images/1F642.png new file mode 100644 index 000000000..7d402140d Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F642.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F643.png b/admin/survey/export/latexclasses/textemp/images/1F643.png new file mode 100644 index 000000000..69a74b307 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F643.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F644.png b/admin/survey/export/latexclasses/textemp/images/1F644.png new file mode 100644 index 000000000..21001057b Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F644.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F645.png b/admin/survey/export/latexclasses/textemp/images/1F645.png new file mode 100644 index 000000000..64114e4f4 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F645.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F646.png b/admin/survey/export/latexclasses/textemp/images/1F646.png new file mode 100644 index 000000000..2e9f7c09e Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F646.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F647.png b/admin/survey/export/latexclasses/textemp/images/1F647.png new file mode 100644 index 000000000..9bb048a85 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F647.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F648.png b/admin/survey/export/latexclasses/textemp/images/1F648.png new file mode 100644 index 000000000..d95e64a53 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F648.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F649.png b/admin/survey/export/latexclasses/textemp/images/1F649.png new file mode 100644 index 000000000..f017f21ef Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F649.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64A.png b/admin/survey/export/latexclasses/textemp/images/1F64A.png new file mode 100644 index 000000000..c129f1ce4 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64A.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64B.png b/admin/survey/export/latexclasses/textemp/images/1F64B.png new file mode 100644 index 000000000..3e584ca7d Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64B.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64C.png b/admin/survey/export/latexclasses/textemp/images/1F64C.png new file mode 100644 index 000000000..4350588f5 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64C.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64D.png b/admin/survey/export/latexclasses/textemp/images/1F64D.png new file mode 100644 index 000000000..915d72271 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64D.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64E.png b/admin/survey/export/latexclasses/textemp/images/1F64E.png new file mode 100644 index 000000000..6ef48801d Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64E.png differ diff --git a/admin/survey/export/latexclasses/textemp/images/1F64F.png b/admin/survey/export/latexclasses/textemp/images/1F64F.png new file mode 100644 index 000000000..fe410f4a4 Binary files /dev/null and b/admin/survey/export/latexclasses/textemp/images/1F64F.png differ diff --git a/admin/survey/export/latexclasses/textemp/latexTemplatePdfSurvey.cls b/admin/survey/export/latexclasses/textemp/latexTemplatePdfSurvey.cls index 0770fd126..f0f732242 100644 --- a/admin/survey/export/latexclasses/textemp/latexTemplatePdfSurvey.cls +++ b/admin/survey/export/latexclasses/textemp/latexTemplatePdfSurvey.cls @@ -14,6 +14,8 @@ \usepackage[export]{adjustbox} % za poravnavo slik \usepackage{wasysym} % za izris radio button, checkbox +\usepackage{tikzsymbols} %za izpis emoji-jev + \usepackage{textgreek} %za resevanje grskih crk \usepackage[T2A,T1]{fontenc} %za cirilico @@ -22,6 +24,8 @@ \usepackage[utf8]{inputenc} % za uporabo utf8 \usepackage{amssymb} %za unicode simbole + +%DEFINIRANJE NADOMESTIL NEIZPISLJIVIH UNICODE CHARACTER-JEV \DeclareUnicodeCharacter{200B}{{\hskip 0pt}} \DeclareUnicodeCharacter{03C7}{$\chi$} \DeclareUnicodeCharacter{2265}{$\geq$} @@ -29,6 +33,86 @@ \DeclareUnicodeCharacter{2003}{$\enspace$} \DeclareUnicodeCharacter{0014}{$\thinspace$} %ni ta pravi nadomestek, ker v Latexu tak simbol ne obstaja +%Emoji - izpis urejen s slikicami emoji-jev +\DeclareUnicodeCharacter{1F600}{\includegraphics[height=1em]{1F600.png}} +\DeclareUnicodeCharacter{1F601}{\includegraphics[height=1em]{1F601.png}} +\DeclareUnicodeCharacter{1F602}{\includegraphics[height=1em]{1F602.png}} +\DeclareUnicodeCharacter{1F603}{\includegraphics[height=1em]{1F603.png}} +\DeclareUnicodeCharacter{1F604}{\includegraphics[height=1em]{1F604.png}} +\DeclareUnicodeCharacter{1F605}{\includegraphics[height=1em]{1F605.png}} +\DeclareUnicodeCharacter{1F606}{\includegraphics[height=1em]{1F606.png}} +\DeclareUnicodeCharacter{1F609}{\includegraphics[height=1em]{1F609.png}} +\DeclareUnicodeCharacter{1F60A}{\includegraphics[height=1em]{1F60A.png}} +\DeclareUnicodeCharacter{1F60B}{\includegraphics[height=1em]{1F60B.png}} +\DeclareUnicodeCharacter{1F60E}{\includegraphics[height=1em]{1F60C.png}} +\DeclareUnicodeCharacter{1F60D}{\includegraphics[height=1em]{1F60D.png}} +\DeclareUnicodeCharacter{1F60E}{\includegraphics[height=1em]{1F60E.png}} +\DeclareUnicodeCharacter{1F60D}{\includegraphics[height=1em]{1F60F.png}} + +\DeclareUnicodeCharacter{1F600}{\includegraphics[height=1em]{1F610.png}} +\DeclareUnicodeCharacter{1F611}{\includegraphics[height=1em]{1F611.png}} +\DeclareUnicodeCharacter{1F612}{\includegraphics[height=1em]{1F612.png}} +\DeclareUnicodeCharacter{1F613}{\includegraphics[height=1em]{1F613.png}} +\DeclareUnicodeCharacter{1F614}{\includegraphics[height=1em]{1F614.png}} +\DeclareUnicodeCharacter{1F615}{\includegraphics[height=1em]{1F615.png}} +\DeclareUnicodeCharacter{1F616}{\includegraphics[height=1em]{1F616.png}} +\DeclareUnicodeCharacter{1F619}{\includegraphics[height=1em]{1F619.png}} +\DeclareUnicodeCharacter{1F61A}{\includegraphics[height=1em]{1F61A.png}} +\DeclareUnicodeCharacter{1F61B}{\includegraphics[height=1em]{1F61B.png}} +\DeclareUnicodeCharacter{1F61E}{\includegraphics[height=1em]{1F61C.png}} +\DeclareUnicodeCharacter{1F61D}{\includegraphics[height=1em]{1F61D.png}} +\DeclareUnicodeCharacter{1F61E}{\includegraphics[height=1em]{1F61E.png}} +\DeclareUnicodeCharacter{1F61D}{\includegraphics[height=1em]{1F61F.png}} + +\DeclareUnicodeCharacter{1F600}{\includegraphics[height=1em]{1F620.png}} +\DeclareUnicodeCharacter{1F621}{\includegraphics[height=1em]{1F621.png}} +\DeclareUnicodeCharacter{1F622}{\includegraphics[height=1em]{1F622.png}} +\DeclareUnicodeCharacter{1F623}{\includegraphics[height=1em]{1F623.png}} +\DeclareUnicodeCharacter{1F624}{\includegraphics[height=1em]{1F624.png}} +\DeclareUnicodeCharacter{1F625}{\includegraphics[height=1em]{1F625.png}} +\DeclareUnicodeCharacter{1F626}{\includegraphics[height=1em]{1F626.png}} +\DeclareUnicodeCharacter{1F629}{\includegraphics[height=1em]{1F629.png}} +\DeclareUnicodeCharacter{1F62A}{\includegraphics[height=1em]{1F62A.png}} +\DeclareUnicodeCharacter{1F62B}{\includegraphics[height=1em]{1F62B.png}} +\DeclareUnicodeCharacter{1F62E}{\includegraphics[height=1em]{1F62C.png}} +\DeclareUnicodeCharacter{1F62D}{\includegraphics[height=1em]{1F62D.png}} +\DeclareUnicodeCharacter{1F62E}{\includegraphics[height=1em]{1F62E.png}} +\DeclareUnicodeCharacter{1F62D}{\includegraphics[height=1em]{1F62F.png}} + +\DeclareUnicodeCharacter{1F600}{\includegraphics[height=1em]{1F630.png}} +\DeclareUnicodeCharacter{1F631}{\includegraphics[height=1em]{1F631.png}} +\DeclareUnicodeCharacter{1F632}{\includegraphics[height=1em]{1F632.png}} +\DeclareUnicodeCharacter{1F633}{\includegraphics[height=1em]{1F633.png}} +\DeclareUnicodeCharacter{1F634}{\includegraphics[height=1em]{1F634.png}} +\DeclareUnicodeCharacter{1F635}{\includegraphics[height=1em]{1F635.png}} +\DeclareUnicodeCharacter{1F636}{\includegraphics[height=1em]{1F636.png}} +\DeclareUnicodeCharacter{1F639}{\includegraphics[height=1em]{1F639.png}} +\DeclareUnicodeCharacter{1F63A}{\includegraphics[height=1em]{1F63A.png}} +\DeclareUnicodeCharacter{1F63B}{\includegraphics[height=1em]{1F63B.png}} +\DeclareUnicodeCharacter{1F63E}{\includegraphics[height=1em]{1F63C.png}} +\DeclareUnicodeCharacter{1F63D}{\includegraphics[height=1em]{1F63D.png}} +\DeclareUnicodeCharacter{1F63E}{\includegraphics[height=1em]{1F63E.png}} +\DeclareUnicodeCharacter{1F63D}{\includegraphics[height=1em]{1F63F.png}} + +\DeclareUnicodeCharacter{1F600}{\includegraphics[height=1em]{1F640.png}} +\DeclareUnicodeCharacter{1F641}{\includegraphics[height=1em]{1F641.png}} +\DeclareUnicodeCharacter{1F642}{\includegraphics[height=1em]{1F642.png}} +\DeclareUnicodeCharacter{1F643}{\includegraphics[height=1em]{1F643.png}} +\DeclareUnicodeCharacter{1F644}{\includegraphics[height=1em]{1F644.png}} +\DeclareUnicodeCharacter{1F645}{\includegraphics[height=1em]{1F645.png}} +\DeclareUnicodeCharacter{1F646}{\includegraphics[height=1em]{1F646.png}} +\DeclareUnicodeCharacter{1F649}{\includegraphics[height=1em]{1F649.png}} +\DeclareUnicodeCharacter{1F64A}{\includegraphics[height=1em]{1F64A.png}} +\DeclareUnicodeCharacter{1F64B}{\includegraphics[height=1em]{1F64B.png}} +\DeclareUnicodeCharacter{1F64E}{\includegraphics[height=1em]{1F64C.png}} +\DeclareUnicodeCharacter{1F64D}{\includegraphics[height=1em]{1F64D.png}} +\DeclareUnicodeCharacter{1F64E}{\includegraphics[height=1em]{1F64E.png}} +\DeclareUnicodeCharacter{1F64D}{\includegraphics[height=1em]{1F64F.png}} + +%Emoji - izpis urejen s slikicami emoji-jev - konec + +%DEFINIRANJE NADOMESTIL NEIZPISLJIVIH UNICODE CHARACTER-JEV - KONEC + \usepackage{graphicx} % za prikazovanje slik in ostalih grafik \usepackage{colortbl} %The pack­age al­lows rows and columns to be coloured, and even in­di­vid­ual cells \usepackage{fancyhdr} % za ureditev glav in nog diff --git a/admin/survey/index.php b/admin/survey/index.php index 8ef62aea4..cf08a572d 100644 --- a/admin/survey/index.php +++ b/admin/survey/index.php @@ -182,10 +182,6 @@ $sql = sisplet_query("SELECT email FROM users WHERE id='$global_user_id'"); $row = mysqli_fetch_assoc($sql); - if ( $row['email'] == 'test@1ka.si') { - $cssBodyClass .= ' test_user'; - } - /**************** BODY ****************/ echo ''."\n"; diff --git a/admin/survey/minify/groupsConfig.php b/admin/survey/minify/groupsConfig.php index 1b15c942b..f2445e954 100644 --- a/admin/survey/minify/groupsConfig.php +++ b/admin/survey/minify/groupsConfig.php @@ -20,20 +20,12 @@ return [ // jquery in jquery ui vkljucimo ze minificirana, da bo slo mal hitrej new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/ui-1.8.18/js/jquery-1.7.1.min.js',]), - // fix za IE in draggables -- http://dev.jqueryui.com/ticket/5374 (v novi verziji zgleda da to dela) - //new Minify_Source( array('filepath' => dirname(__FILE__) .'/../script/jquery/ui-1.8.4/js/jquery-1.4.2.mod.min.js',)), - //dirname(__FILE__) .'/../script/jquery/ui-1.8.4/development-bundle/jquery-1.4.2.mod.js', - - // Priprava za posodobitev jQuery na novejšo verzijo - // dirname(__FILE__).'/../script/jquery/jquery-1.12.4.min.js', - // dirname(__FILE__).'/../script/jquery/jquery-migrate-1.4.1.js', - new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/ui-1.8.18/js/jquery-ui-1.8.18.custom.min.js',]), - //new Minify_Source( array('filepath' => dirname(__FILE__) .'/../script/jquery/ui-1.8.4/js/jquery-ui-1.8.4.custom.min.js',)), new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/ui.drag_drop_selectable.js',]), new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/jquery_touch_punch/jquery.ui.touch-punch.min.js',]), - //dirname(__FILE__) .'/../script/jquery/ui.draggable.orig.js', + + dirname(__FILE__).'/../script/mobileMenu/zeynep.min.js', dirname(__FILE__).'/../script/jquery/jquery.timer.js', dirname(__FILE__).'/../script/jquery/farbtastic.js', @@ -45,6 +37,7 @@ return [ dirname(__FILE__).'/../script/onload.js', dirname(__FILE__).'/../script/script.js', + dirname(__FILE__).'/../script/mobile.js', dirname(__FILE__).'/../script/branching.js', dirname(__FILE__).'/../script/folders.js', dirname(__FILE__).'/../script/surveyList.js', @@ -196,6 +189,11 @@ return [ new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/ui-1.8.18/js/jquery-1.7.1.min.js',]), new Minify_Source(['filepath' => dirname(__FILE__).'/../script/jquery/ui-1.8.18/js/jquery-ui-1.8.18.custom.min.js',]), + dirname(__FILE__).'/../script/script.js', + + dirname(__FILE__).'/../script/mobileMenu/zeynep.min.js', + dirname(__FILE__).'/../script/mobile.js', + dirname(__FILE__).'/../script/datatables/jquery.dataTables.min.js', dirname(__FILE__).'/../script/select2/select2.min.js', dirname(__FILE__).'/../script/datatables/dataTables.buttons.min.js', diff --git a/admin/survey/modules/mod_EVOLI/R/Evoli_quality_clime_dan.R b/admin/survey/modules/mod_EVOLI/R/Evoli_quality_clime_dan.R index 473fe5dbf..00149b0fb 100644 --- a/admin/survey/modules/mod_EVOLI/R/Evoli_quality_clime_dan.R +++ b/admin/survey/modules/mod_EVOLI/R/Evoli_quality_clime_dan.R @@ -966,6 +966,8 @@ organisation <- gsub("\u0160", "š", organisation, fixed = TRUE) organisation <- gsub("\u0111", "đ", organisation, fixed = TRUE) # Velik đ organisation <- gsub("\u0110", "Đ", organisation, fixed = TRUE) +# Velik đ +organisation <- gsub("\u00E6", "æ", organisation, fixed = TRUE) #--------------------- //ORGANIZATION INFORMATION// ------------------------# diff --git a/admin/survey/modules/mod_EVOLI/R/Evoli_team_meter_depart_ang.R b/admin/survey/modules/mod_EVOLI/R/Evoli_team_meter_depart_ang.R index 5fede2f2b..09a766b53 100644 --- a/admin/survey/modules/mod_EVOLI/R/Evoli_team_meter_depart_ang.R +++ b/admin/survey/modules/mod_EVOLI/R/Evoli_team_meter_depart_ang.R @@ -10,6 +10,15 @@ ID <- params[1] ## //Passing arguments// ## #---------------------------------------------------# +#------------- ENCODING 1KA STRE?NIK ----------------# +# Nastavimo encoding za potrebe stre?nika +# Pogosto se na 1KA stre?niku pokvarijo ?umniki, +# zato ?umniki (v PDF poro?ilu) lokalno delajo, +# na stre?niku pa ne +Sys.setlocale(category = "LC_ALL", locale = "slovenian") +#------------- ENCODING 1KA STRE?NIK ----------------# + + #-------------------- Load needed libraries ----------------------# # Load needed libraries diff --git a/admin/survey/modules/mod_EVOLI/latexkosi/drugi_del-quality_clime_dan.tex b/admin/survey/modules/mod_EVOLI/latexkosi/drugi_del-quality_clime_dan.tex index c436ebb6e..62abebfe0 100644 --- a/admin/survey/modules/mod_EVOLI/latexkosi/drugi_del-quality_clime_dan.tex +++ b/admin/survey/modules/mod_EVOLI/latexkosi/drugi_del-quality_clime_dan.tex @@ -3,7 +3,7 @@ \begin{center} \begin{minipage}{0.8\linewidth} \textbf{1. Kvalitetsudvikling i fokus} \\ -Kvalitet tages alvorligt. Kvalitetsudvikling er en lige så naturlig del af \\ +Kvalitet tages alvorligt. Kvalitetsudvikling er en lige s{\aa} naturlig del af \\ virksomhedens liv som budgetter og regnskaber. \\ \\ @@ -19,37 +19,37 @@ og serviceydelser og bevarer en loyal tilknytning til virksomheden. \textbf{4. Engagerede medarbejdere} \\ Medarbejderne trives og er dybt engagerede. \\ -Personaleomsætningshastigheden og fraværsprocenten er lav. Interne \\ - funktioner understøtter kundeorienterede ansatte i at opnå succes. +Personaleoms{\ae}tningshastigheden og frav{\ae}rsprocenten er lav. Interne \\ + funktioner underst{\o}tter kundeorienterede ansatte i at opn{\aa} succes. \\ \\ \textbf{5. Langsigtet kvalitetsudvikling} \\ -Virksomheden satser mere på langsigtet kvalitetsudvikling end på \\ +Virksomheden satser mere p{\aa} langsigtet kvalitetsudvikling end p{\aa} \\ omkostningsreduktion og fortjeneste. \\ \\ -\textbf{6. Klare og høje kvalitetsmål} \\ -Der findes klare og høje kvalitetsmål på alle områder. Resultaterne \\ -måles løbende, og nøgletallene gøres synlige for alle. +\textbf{6. Klare og h{\o}je kvalitetsm{\aa}l} \\ +Der findes klare og h{\o}je kvalitetsm{\aa}l p{\aa} alle omr{\aa}der. Resultaterne \\ +m{\aa}les l{\o}bende, og n{\o}gletallene g{\o}res synlige for alle. \\ \\ -\textbf{7. Belønning af kvalitetspræstationer} \\ -Kvalitetspræstationer belønnes synligt og er en forudsætning for \\ +\textbf{7. Bel{\o}nning af kvalitetspr{\ae}stationer} \\ +Kvalitetspr{\ae}stationer bel{\o}nnes synligt og er en foruds{\ae}tning for \\ forfremmelse. \\ \\ \textbf{8. Positiv opfattelse af kvalitetskontrol} \\ -Kvalitetskontrol opfattes ikke som et tegn på mistillid, men som et \\ +Kvalitetskontrol opfattes ikke som et tegn p{\aa} mistillid, men som et \\ middel til at udvikle kvaliteten.\\ -Afvigelser fra aftalte kvalitetsmål accepteres ikke, men fører til \\ -ændringer af præstationerne eller af målene. +Afvigelser fra aftalte kvalitetsm{\aa}l accepteres ikke, men f{\o}rer til \\ +{\ae}ndringer af pr{\ae}stationerne eller af m{\aa}lene. \\ \\ -\textbf{9. Næste person i arbejdsprocessen er en vigtig kunde} \\ -I virksomheden betragtes den næste person i arbejdsprocessen som\\ -en vigtig kunde. Intet led/menneske i arbejdsprocessen bør lide \\ -under fejl begået af andre. Hver person forpligter sig til \\ -at optræde som leverandør af kvalitetsprodukter til sine kunder. +\textbf{9. N{\ae}ste person i arbejdsprocessen er en vigtig kunde} \\ +I virksomheden betragtes den n{\ae}ste person i arbejdsprocessen som\\ +en vigtig kunde. Intet led/menneske i arbejdsprocessen b{\o}r lide \\ +under fejl beg{\aa}et af andre. Hver person forpligter sig til \\ +at optr{\ae}de som leverand{\o}r af kvalitetsprodukter til sine kunder. \end{minipage} \end{center} @@ -59,49 +59,49 @@ at optræde som leverandør af kvalitetsprodukter til sine kunder. \textbf{10. Investering i medarbejderudvikling} \\ Medarbejderne betragtes som virksomhedens vigtigste ressource. \\ Der investeres i udvikling og uddannelse af alle medarbejdere.\\ -Lederne deltager i karriereplanlægning med et fokus på at udvide \\ -erfaringsbasen i form af viden og evner -- og derved højne \\ -individets værdi. \\ +Lederne deltager i karriereplanl{\ae}gning med et fokus p{\aa} at udvide \\ +erfaringsbasen i form af viden og evner -- og derved h{\o}jne \\ +individets v{\ae}rdi. \\ Den enkelte udviser initiativ og tager ansvar for sin egen karriere. \\ \\ -\textbf{11. Forebyggelse og begrænsning af fejl} \\ -Der investeres meget i at forebygge og begrænse fejl. \\ +\textbf{11. Forebyggelse og begr{\ae}nsning af fejl} \\ +Der investeres meget i at forebygge og begr{\ae}nse fejl. \\ Virksomheden skelner mellem acceptable og uacceptable fejl. \\ De acceptable fejl er kreative fejl, som er udtryk for udvikling, \\ -afprøvning af ny viden og eksperimenter. De uacceptable \\ -fejl er ''sjuskefejl'', som er unødvendige, dyre og skadelige. +afpr{\o}vning af ny viden og eksperimenter. De uacceptable \\ +fejl er ''sjuskefejl'', som er un{\o}dvendige, dyre og skadelige. \\ \\ \textbf{12. Rigtigt beslutningsniveau} \\ -Beslutningsniveauet er ikke placeret højere i organisationen, end det \\ -er nødvendigt, for at beslutninger kan træffes med indsigt, og \\ +Beslutningsniveauet er ikke placeret h{\o}jere i organisationen, end det \\ +er n{\o}dvendigt, for at beslutninger kan tr{\ae}ffes med indsigt, og \\ kvalitetskravene kan indfries. \\ \\ \textbf{13. Enkel og direkte vej til den endelige bruger} \\ Produkter og serviceydelser producers og leveres til den endelige \\ -bruger så direkte og enkelt som muligt. +bruger s{\aa} direkte og enkelt som muligt. \\ \\ -\textbf{14. Fokus på både teknisk og menneskelig kvalitet} \\ -Virksomheden lægger stor vægt på både teknisk og menneskelig kvalitet. +\textbf{14. Fokus p{\aa} b{\aa}de teknisk og menneskelig kvalitet} \\ +Virksomheden l{\ae}gger stor v{\ae}gt p{\aa} b{\aa}de teknisk og menneskelig kvalitet. \\ \\ \textbf{15. Orientering mod kundebehov} \\ Indfrielsen af kundens eller den endelige brugers behov afspejles \\ -i alle virksomhedens handlinger. Kvalitetsmålinger finder ikke \\ -alene sted internt i virksomheden, men også hos kunderne. +i alle virksomhedens handlinger. Kvalitetsm{\aa}linger finder ikke \\ +alene sted internt i virksomheden, men ogs{\aa} hos kunderne. \\ \\ -\textbf{16. Løbende værdianalyser} \\ -Der gennemføres løbende værdianalyser for at bedømme, om det \\ -er de rigtige ting, der gøres, og om udbyttet står i forhold til indsatsen. \\ -Arbejdsaktiviteter, som ikke tilfører ''værdi'' nedlægges. +\textbf{16. L{\o}bende v{\ae}rdianalyser} \\ +Der gennemf{\o}res l{\o}bende v{\ae}rdianalyser for at bed{\o}mme, om det \\ +er de rigtige ting, der g{\o}res, og om udbyttet st{\aa}r i forhold til indsatsen. \\ +Arbejdsaktiviteter, som ikke tilf{\o}rer ''v{\ae}rdi'' nedl{\ae}gges. \\ \\ -\textbf{17. Virksomheden påtager sig sin samfundsroller} \\ -Virksomheden erkender og påtager sig sin samfundsrolle. +\textbf{17. Virksomheden p{\aa}tager sig sin samfundsroller} \\ +Virksomheden erkender og p{\aa}tager sig sin samfundsrolle. \end{minipage} \end{center} @@ -112,16 +112,16 @@ Virksomheden erkender og påtager sig sin samfundsrolle. \begin{center} \begin{minipage}{0.8\linewidth} For at skabe, vedligeholde og udvikle et \textit{Kvalitetsklima}, anbefaler \\ -vi at du lærer mere om de 15 kvalitetsområder i \textit{Kvalitetsklima } \\ -værktøjet og de 17 kendetegn for kvalitetsvirksomheder gennem \\ -anvendelsen af tre andre værktøjer: +vi at du l{\ae}rer mere om de 15 kvalitetsomr{\aa}der i \textit{Kvalitetsklima } \\ +v{\ae}rkt{\o}jet og de 17 kendetegn for kvalitetsvirksomheder gennem \\ +anvendelsen af tre andre v{\ae}rkt{\o}jer: \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} \addtolength{\itemindent}{2.2cm} \item Stjernekvalitet -\item Team Employeeship måler -\item Organisationens Employeeship Måler +\item Team Employeeship m{\aa}ler +\item Organisationens Employeeship M{\aa}ler \end{itemize} \end{minipage} \end{center} @@ -129,37 +129,37 @@ anvendelsen af tre andre værktøjer: \ \\ \\ \\ \\ \\ \begin{center} -{\Large \textbf{Stjernekvalitet værktøjet}} \\ -\textbf{Et værktøj til at skabe 3-stjernet Teamkvalitet} +{\Large \textbf{Stjernekvalitet v{\ae}rkt{\o}jet}} \\ +\textbf{Et v{\ae}rkt{\o}j til at skabe 3-stjernet Teamkvalitet} \begin{minipage}{0.8\linewidth} \ \\ \\ -\textit{'Stjernekvalitet'} er et værktøj til at overvåge, bedømme og udvikle \\ -kvaliteten af et teams kollektive præstationer så de konstant indfrier \\ +\textit{'Stjernekvalitet'} er et v{\ae}rkt{\o}j til at overv{\aa}ge, bed{\o}mme og udvikle \\ +kvaliteten af et teams kollektive pr{\ae}stationer s{\aa} de konstant indfrier \\ krav og forventninger fra andre.\\ \ \\ -Værktøjet har fem trin: +V{\ae}rkt{\o}jet har fem trin: \\ \renewcommand\labelitemi{\large$\bullet$} \textbf{ \begin{itemize} -\item Udvælg kvalitetsområder og -faktorer -\item Bestem målemetoden for hver faktor +\item Udv{\ae}lg kvalitetsomr{\aa}der og -faktorer +\item Bestem m{\aa}lemetoden for hver faktor \item Definér det ideale kvalitetsniveau for hver faktor -\item Mål det faktiske præstationsniveau for hver faktor +\item M{\aa}l det faktiske pr{\ae}stationsniveau for hver faktor \item Lav en udviklingsplan for kvalitet \end{itemize} } \ \\ -Teamets kvalitetsområder er bestemt af prioriteringen i teamet \\ -med tilhørende kvalitetsfaktorer og målemetoden besluttet \\ +Teamets kvalitetsomr{\aa}der er bestemt af prioriteringen i teamet \\ +med tilh{\o}rende kvalitetsfaktorer og m{\aa}lemetoden besluttet \\ for hver faktor. Derefter udvikles \\ hovedelementerne af kvalitetsudviklingsprocessen konsistent. \\ \\ -Hele Stjernekvalitetsværktøjet findes på web sitet \href{https://clausmoller.com/en/reachingforthestars}{\underline{www.ClausMoller.com}}. +Hele Stjernekvalitetsv{\ae}rkt{\o}jet findes p{\aa} web sitet \href{https://clausmoller.com/en/reachingforthestars}{\underline{www.ClausMoller.com}}. \end{minipage} @@ -169,32 +169,32 @@ Hele Stjernekvalitetsværktøjet findes på web sitet \href{https://clausmoller. % 4. STRAN PO GRAFIH \begin{center} -{\Large \textbf{Team Employeeship Måler}} +{\Large \textbf{Team Employeeship M{\aa}ler}} \begin{minipage}{0.8\linewidth} \ \\ \\ -\textit{Team Employeeship Måler} er et værktøj til at analysere, vurdere \\ +\textit{Team Employeeship M{\aa}ler} er et v{\ae}rkt{\o}j til at analysere, vurdere \\ og udvikle teamets og hele virksomhedens \textit{Employeeship} kultur. \\ \\ -Værktøjet er en enkel og hurtig hjælp til ledelse og medarbejdere \\ -til at identificere områder, hvor en forandring eller en mere \\ -indgående analyse af kulturen er påkrævet. +V{\ae}rkt{\o}jet er en enkel og hurtig hj{\ae}lp til ledelse og medarbejdere \\ +til at identificere omr{\aa}der, hvor en forandring eller en mere \\ +indg{\aa}ende analyse af kulturen er p{\aa}kr{\ae}vet. \\ \\ - Når alle ansatte er dybt engagerede i virksomhedens overlevelse, \\ -udvikling og stræben efter "business excellence" så har virksomheden en \\ + N{\aa}r alle ansatte er dybt engagerede i virksomhedens overlevelse, \\ +udvikling og str{\ae}ben efter "business excellence" s{\aa} har virksomheden en \\ \textit{Employeeship} kultur Den er karakteriseret ved at alle udviser \\ engagement, ansvarlighed, loyalitet, initiativ og positive energi. \\ \\ -\textit{Employeeship} er et unikt koncept udviklet af Claus Møller som handler \\ -om \textbf{ledelse for alle}. Det illustrerer hvad der kræves for at være en god \\ +\textit{Employeeship} er et unikt koncept udviklet af Claus M{\o}ller som handler \\ +om \textbf{ledelse for alle}. Det illustrerer hvad der kr{\ae}ves for at v{\ae}re en god \\ medarbejder og hvordan man kan inspirere alle til at yde deres bedste og \\ -dermed medvirke til virksomhedens overlevelse, vækst og \\ +dermed medvirke til virksomhedens overlevelse, v{\ae}kst og \\ langsigtede succes. @@ -205,58 +205,58 @@ langsigtede succes. % 5. STRAN PO GRAFIH \begin{center} -{\Large \textbf{Virksomhedens Employeeship Måler}} +{\Large \textbf{Virksomhedens Employeeship M{\aa}ler}} \begin{minipage}{0.8\linewidth} \ \\ \\ -Et værktøj til at måle, hvorvidt virksomhedens kultur, systemer \\ +Et v{\ae}rkt{\o}j til at m{\aa}le, hvorvidt virksomhedens kultur, systemer \\ og politikker inspirerer medarbejderne til at udvise \\ \textit{Employeeship}. \\ \\ -Medarbejdernes Employeeship holdning og adfærd påvirkes af en \\ -lang række forhold i virksomheden, som vi, ''lidt forenklet'', kalder \\ -virksomhedens ''systemer og politikker''. For at få alle i virksomheden \\ +Medarbejdernes Employeeship holdning og adf{\ae}rd p{\aa}virkes af en \\ +lang r{\ae}kke forhold i virksomheden, som vi, ''lidt forenklet'', kalder \\ +virksomhedens ''systemer og politikker''. For at f{\aa} alle i virksomheden \\ til at udvise Employeeship og dermed skabe en Employeeship kultur \\ - er det afgørende, at ledere og medarbejdere yder en indsats både \\ -på de \textbf{hårde} og de \textbf{bløde} områder. \\ - \hspace*{0.8\baselineskip} Det er også nødvendigt, at der i virksomheden findes \\ -politikker og systemer, der indeholder \textbf{både} hårde \textbf{og} \\ - \textbf{bløde} elementer. + er det afg{\o}rende, at ledere og medarbejdere yder en indsats b{\aa}de \\ +p{\aa} de \textbf{h{\aa}rde} og de \textbf{bl{\o}de} omr{\aa}der. \\ + \hspace*{0.8\baselineskip} Det er ogs{\aa} n{\o}dvendigt, at der i virksomheden findes \\ +politikker og systemer, der indeholder \textbf{b{\aa}de} h{\aa}rde \textbf{og} \\ + \textbf{bl{\o}de} elementer. \end{minipage} \end{center} \ \\ \\ \\ \begin{minipage}[t]{0.54\textwidth} -{\textbf{Hårde områder}} \\ \\ -De hårde områder omfatter de konkrete og \\ +{\textbf{H{\aa}rde omr{\aa}der}} \\ \\ +De h{\aa}rde omr{\aa}der omfatter de konkrete og \\ tekniske aspekter af virksomhedens liv: systemer, \\ -regler, procedure, kommandoveje, fysisk miljø etc. \\ \\ -De hårde områder er de rationelle og logiske \\ +regler, procedure, kommandoveje, fysisk milj{\o} etc. \\ \\ +De h{\aa}rde omr{\aa}der er de rationelle og logiske \\ aspekter i virksomheden. Dem som er lettest at \\ forklare og beskrive. \\ -\hspace*{0.8\baselineskip}På de hårde områder er det mest \\ -et spørgsmål om indhold: +\hspace*{0.8\baselineskip}P{\aa} de h{\aa}rde omr{\aa}der er det mest \\ +et sp{\o}rgsm{\aa}l om indhold: \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} -\item Hvad skal gøres? -\item Hvem skal gøre det? -\item Hvornår skal det gøres? -\item Hvordan skal det gøres? +\item Hvad skal g{\o}res? +\item Hvem skal g{\o}re det? +\item Hvorn{\aa}r skal det g{\o}res? +\item Hvordan skal det g{\o}res? \end{itemize} \ \\ -I mange virksomheder udøves ledelse \\ -hovedsageligt ud fra de forudsætninger og \\ -regler, der gælder i den hårde verden. \\ +I mange virksomheder ud{\o}ves ledelse \\ +hovedsageligt ud fra de foruds{\ae}tninger og \\ +regler, der g{\ae}lder i den h{\aa}rde verden. \\ Det afspejler sig eksempelvis i \\ - virksomhedens hårde: + virksomhedens h{\aa}rde: \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} \item Personalepolitik -\item Ansættelseskontrakter -\item Belønningssystemer +\item Ans{\ae}ttelseskontrakter +\item Bel{\o}nningssystemer \item Informationssystemer \end{itemize} @@ -264,35 +264,35 @@ Det afspejler sig eksempelvis i \\ % 5. STRAN PO GRAFIH drugi stolpec \begin{minipage}[t]{0.54\textwidth} -{\large \textbf{Bløde områder}} \\ \\ -De bløde områder omfatter de \\ -følelsesmæssige aspekter af virksomhedens\\ +{\large \textbf{Bl{\o}de omr{\aa}der}} \\ \\ +De bl{\o}de omr{\aa}der omfatter de \\ +f{\o}lelsesm{\ae}ssige aspekter af virksomhedens\\ liv: omgangsform, kommunikationsform, \\ -psykisk miljø, ledelsesstil, menneskesyn, \\ +psykisk milj{\o}, ledelsesstil, menneskesyn, \\ moral, traditioner, tryghed, udfordringer etc. \\ \\ \\ -De bløde områder beskæftiger sig med \\ +De bl{\o}de omr{\aa}der besk{\ae}ftiger sig med \\ det, der kaldes virksomhedskultur. \\ -De bløde områder kan være vanskelige at \\ +De bl{\o}de omr{\aa}der kan v{\ae}re vanskelige at \\ konkretisere og beskrive. \\ -\hspace*{0.8\baselineskip} På de bløde områder er det mest \\ -et spørgsmål om relationer: +\hspace*{0.8\baselineskip} P{\aa} de bl{\o}de omr{\aa}der er det mest \\ +et sp{\o}rgsm{\aa}l om relationer: \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} \item Hvordan skal vi inspirere og motivere \\ hinanden? \item Hvordan skal vi kommunikere for at \\ -forstå hinanden? +forst{\aa} hinanden? \item Hvordan skal vi skabe visioner og \\ udvikle kreativitet? -\item Hvordan skal vi håndtere konflikter \\ +\item Hvordan skal vi h{\aa}ndtere konflikter \\ og tilspidsede situationer? -\item Hvordan opnår vi begejstring, \\ -arbejdsglæde, stolthed, tolerance \\ +\item Hvordan opn{\aa}r vi begejstring, \\ +arbejdsgl{\ae}de, stolthed, tolerance \\ og fleksibilitet? \item Hvordan skaber vi sammenhold og \\ -teamfølelse? +teamf{\o}lelse? \end{itemize} \end{minipage} @@ -300,42 +300,42 @@ teamfølelse? % 7. STRAN PO GRAFIH \begin{center} -{\LARGE \textbf{Næste skridt \dots}} +{\LARGE \textbf{N{\ae}ste skridt \dots}} \end{center} \begin{minipage}[t]{0.54\textwidth} \ \\ -\textit{Kvalitetsklima testen } er et værktøj til at undersøge, \\ -bedømme og udvikle kvalitetskulturen \\ +\textit{Kvalitetsklima testen } er et v{\ae}rkt{\o}j til at unders{\o}ge, \\ +bed{\o}mme og udvikle kvalitetskulturen \\ i dit team og i din organisation. \\ \hspace*{0.8\baselineskip} Resultaterne af Kvalitetsklima vil \\ - inspirere dig til at lære mere om, og \\ + inspirere dig til at l{\ae}re mere om, og \\ udvikle, en kvalitetskultur. \\ \\ -Claus Møller Consulting har udviklet metoder \\ - og værktøjer til at hjælpe individer, teams og \\ - organisationer udvikle sig indenfor de områder \\ - hvor Kvalitetsklima testen har påvist muligheder \\ +Claus M{\o}ller Consulting har udviklet metoder \\ + og v{\ae}rkt{\o}jer til at hj{\ae}lpe individer, teams og \\ + organisationer udvikle sig indenfor de omr{\aa}der \\ + hvor Kvalitetsklima testen har p{\aa}vist muligheder \\ for forbedringer: Produktivitet, relationer, \\ kvalitet, engagement, personlig udvikling, \\ - anerkendelse og belønning, implementering og \\ -andre områder af generel \\ + anerkendelse og bel{\o}nning, implementering og \\ +andre omr{\aa}der af generel \\ virksomhedskompetence. \\ \\ -\hspace*{0.8\baselineskip} Hvis du ønsker at lære mere om din \\ +\hspace*{0.8\baselineskip} Hvis du {\o}nsker at l{\ae}re mere om din \\ kvalitetskultur, og hvordan du udvikler \\ -områder hvor Kvalitetsklima værktøjet \\ -har påvist mulighed for forbedringer, \\ -har du følgende muligheder: +omr{\aa}der hvor Kvalitetsklima v{\ae}rkt{\o}jet \\ +har p{\aa}vist mulighed for forbedringer, \\ +har du f{\o}lgende muligheder: \\ \\ -\textbf{1. \textit{\href{https://clausmoller.com/da/kontakt/}{ \underline {Kontakt Claus Møller Consulting}}}} \\ +\textbf{1. \textit{\href{https://clausmoller.com/da/kontakt/}{ \underline {Kontakt Claus M{\o}ller Consulting}}}} \\ for at modtage mere detaljeret verbal eller \\ skriftlig feedback -- eller coaching og \\ konsulentbistand. \\ \\ -\textbf{2. \textit{\href{}{ \underline {Deltag i Personlig Kvalitet og På vej mod } \\ \underline {Stjernerne kurser } }}} \\ \\ -for at lære mere om kvalitetsprocessen \\ +\textbf{2. \textit{\href{}{ \underline {Deltag i Personlig Kvalitet og P{\aa} vej mod } \\ \underline {Stjernerne kurser } }}} \\ \\ +for at l{\ae}re mere om kvalitetsprocessen \\ og hvordan du, dit team og din virksomhed \\ kan udvikle kvalitet. \end{minipage} @@ -343,51 +343,51 @@ kan udvikle kvalitet. % 7. STRAN PO GRAFIH drugi stolpec \begin{minipage}[t]{0.54\textwidth} \ \\ \ -\textbf{3. \textit{\href{https://clausmoller.com/da/product/personal-quality/}{ \underline {Læs bogen Personlig Kvalitet }}}} \\ \\ -for at få en introduktion til konceptet om \\ -\textit{Personlig Kvalitet}, der er fokuseret på den \\ +\textbf{3. \textit{\href{https://clausmoller.com/da/product/personal-quality/}{ \underline {L{\ae}s bogen Personlig Kvalitet }}}} \\ \\ +for at f{\aa} en introduktion til konceptet om \\ +\textit{Personlig Kvalitet}, der er fokuseret p{\aa} den \\ \textbf{menneskelige side af kvalitet } og som \\ danner grundlag for alle andre former for \\ kvalitet: produkt-, service-, team- og \\ virksomhedskvalitet. \\ \\ -\textbf{4. \textit{\href{https://clausmoller.com/da/produkt/heart-work/}{ \underline {Læs Heart Work}}}} \\ \\ -(En bog om følelsesmæssig intelligens) \\ \\ -for at opnå en dyb indsigt i følelsesmæssig \\ +\textbf{4. \textit{\href{https://clausmoller.com/da/produkt/heart-work/}{ \underline {L{\ae}s Heart Work}}}} \\ \\ +(En bog om f{\o}lelsesm{\ae}ssig intelligens) \\ \\ +for at opn{\aa} en dyb indsigt i f{\o}lelsesm{\ae}ssig \\ intelligens og dens rolle i forhold til kvalitet. \\ -Lær hvorfor det er vigtigt, både privat og på \\ -arbejde, og hvordan følelsesmæssig intelligens \\ +L{\ae}r hvorfor det er vigtigt, b{\aa}de privat og p{\aa} \\ +arbejde, og hvordan f{\o}lelsesm{\ae}ssig intelligens \\ kan udvikles af individer, teams og \\ organisationer. \\ \\ -\textbf{5. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Brug Team EQ Måler}}}} \\ \\ -til præcist at vurdere og udvikle dit teams \\ - og din organisations følelsesmæssige intelligens. +\textbf{5. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Brug Team EQ M{\aa}ler}}}} \\ \\ +til pr{\ae}cist at vurdere og udvikle dit teams \\ + og din organisations f{\o}lelsesm{\ae}ssige intelligens. \\ \\ -\textbf{6. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Brug Team Employeeship Måler}}}} \\ \\ -for at præcist vurdere og udvikle Employeeship \\ - holdninger og adfærd i dit team \\ +\textbf{6. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Brug Team Employeeship M{\aa}ler}}}} \\ \\ +for at pr{\ae}cist vurdere og udvikle Employeeship \\ + holdninger og adf{\ae}rd i dit team \\ og din organisation. \\ \\ -\textbf{7. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Anvend Virksomhedens } \\ \underline{Employeeship Måler}}}} \\ \\ +\textbf{7. \textit{\href{https://clausmoller.com/da/tests/}{ \underline {Anvend Virksomhedens } \\ \underline{Employeeship M{\aa}ler}}}} \\ \\ for at virksomheden kan identificere dens \\ -styrker og svagheder indenfor 14 områder \\ +styrker og svagheder indenfor 14 omr{\aa}der \\ af organisatoriske systemer og politikker. \end{minipage} \ \\ \begin{center} -{\small Claus Møller Consulting tilbyder lederuddannelse, skræddersyede kurser, executive coaching, \\ -diagnostiske værktøjer til virksomheder over hele verden for at gøre dem i stand til at \\ -opnå varige resultater for individer, teams og hele organisationen. } +{\small Claus M{\o}ller Consulting tilbyder lederuddannelse, skr{\ae}ddersyede kurser, executive coaching, \\ +diagnostiske v{\ae}rkt{\o}jer til virksomheder over hele verden for at g{\o}re dem i stand til at \\ +opn{\aa} varige resultater for individer, teams og hele organisationen. } \end{center} \begin{center} -{\small Claus Møller Consulting hjælper organisationer med at måle, forbedre og styre \\ +{\small Claus M{\o}ller Consulting hj{\ae}lper organisationer med at m{\aa}le, forbedre og styre \\ produktivitet, relationer, kvalitet, og ledelse.}. \end{center} @@ -396,6 +396,6 @@ produktivitet, relationer, kvalitet, og ledelse.}. \end{center} \begin{center} -Kontakt os for yderligere information om konsulentbistand, kurser og værktøjer. \\ +Kontakt os for yderligere information om konsulentbistand, kurser og v{\ae}rkt{\o}jer. \\ \href{info@clausmoller.com}{ \underline {info@clausmoller.com}} \small$\bullet$ \href{http://www.clausmoller.com}{ \underline {http://www.clausmoller.com}} \end{center} diff --git a/admin/survey/modules/mod_EVOLI/latexkosi/glava-quality_clime_dan.tex b/admin/survey/modules/mod_EVOLI/latexkosi/glava-quality_clime_dan.tex index 1d4c1f076..6dcfce020 100644 --- a/admin/survey/modules/mod_EVOLI/latexkosi/glava-quality_clime_dan.tex +++ b/admin/survey/modules/mod_EVOLI/latexkosi/glava-quality_clime_dan.tex @@ -79,7 +79,7 @@ \renewcommand{\headrulewidth}{0.4pt} \renewcommand{\footrulewidth}{0.4pt} \fancyhead[L]{Kvalitetsklima - !organisation!} -\fancyhead[R]{\color{red} {\textcopyright} Ophavsret 2020 - Claus Møller Consulting} +\fancyhead[R]{\color{red} {\textcopyright} Ophavsret 2020 - Claus M{\o}ller Consulting} \headsep 20pt \fancypagestyle{plain}{ \renewcommand{\headrulewidth}{0.5pt} @@ -92,7 +92,7 @@ { \renewcommand{\headrulewidth}{0pt} \renewcommand{\footrulewidth}{0pt} - \fancyfoot[C]{\textcopyright Ophavsret 2020 - Claus Møller Consulting.} + \fancyfoot[C]{\textcopyright Ophavsret 2020 - Claus M{\o}ller Consulting.} } % Simbol svincnika @@ -140,24 +140,24 @@ \\ \\ Tak for, at du har taget \textit{Kvalitetsklima} testen. \\ \\ -\textit{Kvalitetsklima } testen er et værktøj til at overvåge og udvikle \\ +\textit{Kvalitetsklima} testen er et v{\ae}rkt{\o}j til at overv{\aa}ge og udvikle \\ forholdene kulturen eller "klimaet'' i dit team og din organisation. \\ -Testens resultater vil inspirere dig til at lære mere om og forbedre \\ +Testens resultater vil inspirere dig til at l{\ae}re mere om og forbedre \\ din kvalitetskultur. \\ \\ -Claus M{\o}ller's \textit{Kvalitetsklima} er et "termometer'' der hjælper med at måle \\ -teamets "'helbred'' på et givent tidspunkt. Det kan også anvendes til at \\ +Claus M{\o}ller's \textit{Kvalitetsklima} er et "termometer'' der hj{\ae}lper med at m{\aa}le \\ +teamets "'helbred'' p{\aa} et givent tidspunkt. Det kan ogs{\aa} anvendes til at \\ vurdere hele virksomhedens "helbred". \\ \\ -Denne rapport viser tilstanden på kvalitetsbevidsthed og -holdning i din \\ +Denne rapport viser tilstanden p{\aa} kvalitetsbevidsthed og -holdning i din \\ virksomheds teams. \\ \\ -Vi anbefaler at du læser Claus Møller's bog ''Personlig Kvalitet'', som \\ +Vi anbefaler at du l{\ae}ser Claus M{\o}ller's bog ''Personlig Kvalitet'', som \\ giver specifikke forslag til udvikling og vedligeholdelse af Personlig \\ Kvalitet, Team Kvalitet og Kvalitetsklimaet i et team eller en hel \\ -organisation. På de følgende sider får du en introduktion til \\ +organisation. P{\aa} de f{\o}lgende sider f{\aa}r du en introduktion til \\ koncepterne \textit{Teamkvalitet} og \textit{Kvalitetsklima}. \end{minipage} @@ -174,83 +174,83 @@ organisation. På de følgende sider får du en introduktion til \\ {\large \textbf{Hvad er Teamkvalitet?}} \\ Med \textit{Teamkvalitet} menes: \textit{Et teams indfrielse af \\ de krav og forventninger der stilles til teamets \\ -samlede præstationer -- af omgivelserne og af \\ +samlede pr{\ae}stationer -- af omgivelserne og af \\ teamets medlemmer}. Teams og organisationer \\ -bør ligesom enkeltpersoner, gennemgå \\ -regelmæssige helbredsundersøgelser. \\ -\hspace*{0.8\baselineskip}Det er ikke altid nødvendigt at lave \\ -dybdegående og dyre undersøgelser. Enkle \\ -undersøgelser kan indikere de same resultater \\ -og medvirker til at den næste dybdegående \\ -undersøgelse foretages når og hvor der er \\ +b{\o}r ligesom enkeltpersoner, gennemg{\aa} \\ +regelm{\ae}ssige helbredsunders{\o}gelser. \\ +\hspace*{0.8\baselineskip}Det er ikke altid n{\o}dvendigt at lave \\ +dybdeg{\aa}ende og dyre unders{\o}gelser. Enkle \\ +unders{\o}gelser kan indikere de same resultater \\ +og medvirker til at den n{\ae}ste dybdeg{\aa}ende \\ +unders{\o}gelse foretages n{\aa}r og hvor der er \\ behov for den. \\ \hspace*{0.8\baselineskip} \textit{Kvalitetsklima} er en team indikator, eller et \\ -"termometer" , som klarlægger et teams helbred \\ -på et givent tidspunkt. \textit{Kvalitetsklima} kan også \\ +"termometer" , som klarl{\ae}gger et teams helbred \\ +p{\aa} et givent tidspunkt. \textit{Kvalitetsklima} kan ogs{\aa} \\ opridse helbredet for en hel organisation. \\ of an entire organisation. \\ -\hspace*{0.8\baselineskip}Indikatoren er ikke et værktøj til en \\ -dybtgående analyse. Det er en hurtig, enkel \\ - og praktisk måde for ledelsen at identificere de \\ -områder, der skal ændres eller hvor der er behov \\ -for en mere detaljeret undersøgelse af de \\ -nuværende tilstande. \\ +\hspace*{0.8\baselineskip}Indikatoren er ikke et v{\ae}rkt{\o}j til en \\ +dybtg{\aa}ende analyse. Det er en hurtig, enkel \\ + og praktisk m{\aa}de for ledelsen at identificere de \\ +omr{\aa}der, der skal {\ae}ndres eller hvor der er behov \\ +for en mere detaljeret unders{\o}gelse af de \\ +nuv{\ae}rende tilstande. \\ \hspace*{0.8\baselineskip} \textit{Kvalitetsklima } giver organisationer et effektivt \\ -tidligt varslingssystem, som kan hjælpe med at \\ +tidligt varslingssystem, som kan hj{\ae}lpe med at \\ spare en masse penge. \\ \\ -Forudsætningen for at virksomheden og alle dens \\ +Foruds{\ae}tningen for at virksomheden og alle dens \\ teams kan indfri omgivelsernes krav og \\ -forventninger til dem, er at de ansatte både \textbf{kan} \\ -og \textbf{vil} gøre deres bedste. \\ -\hspace*{0.8\baselineskip}For at få de ansatte til at engagere sig med \\ +forventninger til dem, er at de ansatte b{\aa}de \textbf{kan} \\ +og \textbf{vil} g{\o}re deres bedste. \\ +\hspace*{0.8\baselineskip}For at f{\aa} de ansatte til at engagere sig med \\ \textbf{hjernen} og med \textbf{hjertet} i teamets success, er \\ det ikke nok at teamet indfrier kravene og \\ forventningerne fra omgivelserne. \\ -\hspace*{0.8\baselineskip}Forholdene i teamet skal også kunne indfri \\ +\hspace*{0.8\baselineskip}Forholdene i teamet skal ogs{\aa} kunne indfri \\ kravene og forventningerne fra medlemmerne. \\ \\ -Det er ikke nok at ledelsen fokuserer på \\ +Det er ikke nok at ledelsen fokuserer p{\aa} \\ kvaliteten af et teams produkter, serviceydelser, \\ -systemer og procedure. Det er nødvendigt \\ - at fokusere på menneskene bag \\ - teamets præstationer. \\ -\hspace*{0.8\baselineskip}Det er ikke nok at overvåge \\ -kundetilfredsheden. Det er også nødvendigt at \\ -overvåge \textbf{medlemmernes} tilfredshed. +systemer og procedure. Det er n{\o}dvendigt \\ + at fokusere p{\aa} menneskene bag \\ + teamets pr{\ae}stationer. \\ +\hspace*{0.8\baselineskip}Det er ikke nok at overv{\aa}ge \\ +kundetilfredsheden. Det er ogs{\aa} n{\o}dvendigt at \\ +overv{\aa}ge \textbf{medlemmernes} tilfredshed. \end{minipage}} % STRAN 3 drugi stolpec \begin{minipage}[t]{0.65\textwidth} {\large \textbf{Kvalitetsklima}} \\ -Michelin udgiver årlige håndbøger (guides) over \\ +Michelin udgiver {\aa}rlige h{\aa}ndb{\o}ger (guides) over \\ forskellige landes og regioners restauranter, \\ -hoteller og turistattraktioner med tilhørende \\ +hoteller og turistattraktioner med tilh{\o}rende \\ kvalitetsvurdering og uddybende kommentarer. \\ - I Michelin guiden er det højeste man kan opnå \\ -tre stjerner. Prøv at forestille dig at dit team \\ blev -vurderet på samme måde som Michelin- \\ -inspektørerne vurderer restauranter. Hvor \\ - mange stjerner ville dit team få? Alle teamets \\ + I Michelin guiden er det h{\o}jeste man kan opn{\aa} \\ +tre stjerner. Pr{\o}v at forestille dig at dit team \\ blev +vurderet p{\aa} samme m{\aa}de som Michelin- \\ +inspekt{\o}rerne vurderer restauranter. Hvor \\ + mange stjerner ville dit team f{\aa}? Alle teamets \\ medlemmer skal engagere sig i at \\ - opnå 3-stjernet kvalitet. + opn{\aa} 3-stjernet kvalitet. \\ \\ -\textit{Kvalitetsklima } er et værktøj til at overvåge og \\ +\textit{Kvalitetsklima } er et v{\ae}rkt{\o}j til at overv{\aa}ge og \\ udvikle forholdene, kulturen eller ''klimaet'' i \\ -teamet, så det til stadighed kan indfri \\ +teamet, s{\aa} det til stadighed kan indfri \\ \textbf{medlemmernes krav} og forventninger til en ''3- \\ stjernet'' arbejdsplads. Den interne kvalitet i \\ -teamet sætter en øvre grænse for den kvalitet \\ +teamet s{\ae}tter en {\o}vre gr{\ae}nse for den kvalitet \\ teamet kan levere til omgivelserne. \\ -\hspace*{0.8\baselineskip} Værktøjet indeholder 15 nøje udvalgte \\ -områder. Erfaring viser os at hvor godt teamet \\ -fungererer indenfor disse områder, er afgørende \\ -for om medlemmerne synes det er godt at være \\ +\hspace*{0.8\baselineskip} V{\ae}rkt{\o}jet indeholder 15 n{\o}je udvalgte \\ +omr{\aa}der. Erfaring viser os at hvor godt teamet \\ +fungererer indenfor disse omr{\aa}der, er afg{\o}rende \\ +for om medlemmerne synes det er godt at v{\ae}re \\ med i teamet, og er en del af evalueringen af \\ team kvalitetskulturen lavet af medlemmerne. \begin{enumerate}[leftmargin=*] -\item Mål +\item M{\aa}l \item Ansvarsfordeling/organisation \item Effektivitet/produktivitet \item Beslutninger @@ -259,20 +259,20 @@ team kvalitetskulturen lavet af medlemmerne. koordination \item Ekstern kommunikation og koordination -\item Fysisk miljø +\item Fysisk milj{\o} \item Kvalitetsbevidsthed \item Kreativitet og innovation \item Engagement -\item Anerkendelse og belønning +\item Anerkendelse og bel{\o}nning \item Personlig udvikling -\item Øvrigt psykisk miljø +\item {\o}vrigt psykisk milj{\o} (relationer) \item Implementering \end{enumerate} \ \\ -På de efterfølgende sider finder du forklaringer af \\ -de 15 områder i Kvalitetsklima værktøjet som du \\ -og dine kollegaer kan benytte jer af når I er ''på \\ +P{\aa} de efterf{\o}lgende sider finder du forklaringer af \\ +de 15 omr{\aa}der i Kvalitetsklima v{\ae}rkt{\o}jet som du \\ +og dine kollegaer kan benytte jer af n{\aa}r I er ''p{\aa} \\ vej mod stjernerne''. \end{minipage} @@ -280,84 +280,84 @@ vej mod stjernerne''. % STRAN 4 \begin{minipage}[t]{0.54\textwidth} -{\large \textbf{Hvad får du ud af at udvikle et \\ Kvalitetsklima?}} +{\large \textbf{Hvad f{\aa}r du ud af at udvikle et \\ Kvalitetsklima?}} \\ \\ -\textit{Personlig Kvalitet} er forudsætningen for al anden \\ -kvalitet. For at forstå kvalitet på det personlige \\ -niveau er det vigtigt at forstå dette udsagn. \\ -Dette er det første skridt i at udvikle din \\ -personlige kvalitet -- eller i at hjælpe andre med \\ +\textit{Personlig Kvalitet} er foruds{\ae}tningen for al anden \\ +kvalitet. For at forst{\aa} kvalitet p{\aa} det personlige \\ +niveau er det vigtigt at forst{\aa} dette udsagn. \\ +Dette er det f{\o}rste skridt i at udvikle din \\ +personlige kvalitet -- eller i at hj{\ae}lpe andre med \\ at udvikle deres. \\ \\ \hspace*{0.8\baselineskip} Du gavner dit team, din virksomhed eller \\ -organisation ved at præstere høj kvalitet. Din \\ -personlige kvalitet er altafgørende for både \\ +organisation ved at pr{\ae}stere h{\o}j kvalitet. Din \\ +personlige kvalitet er altafg{\o}rende for b{\aa}de \\ produkt- og servicekvalitet or er grundlaget for \\ team- og virksomhedskvalitet. Det bedste sted \\ at starte en kvalitetsudvikling i en virksomhed \\ og organisation er ved det enkelte menneskes \\ -præstationer og holdning til kvalitet. +pr{\ae}stationer og holdning til kvalitet. \\ \\ -Du glæder også din familie, dine venner og \\ +Du gl{\ae}der ogs{\aa} din familie, dine venner og \\ kollegaer med dine kvalitetsydelser. \\ \\ -\textit{De allerstørste fordele opnår du dog selv \\ --- bade privat og på arbejde} +\textit{De allerst{\o}rste fordele opn{\aa}r du dog selv \\ +-- bade privat og p{\aa} arbejde} \\ \\ -Alle ansatte skal konstant søge at udvikle og \\ -vedligeholde kvalitetsholdninger og -adfærd \\ +Alle ansatte skal konstant s{\o}ge at udvikle og \\ +vedligeholde kvalitetsholdninger og -adf{\ae}rd \\ (f.eks. ideelle kvalitetsstandarder). \\ \\ -\textbf{Dine fordele ved at præstere \\ -høj Personlig Kvalitet} +\textbf{Dine fordele ved at pr{\ae}stere \\ +h{\o}j Personlig Kvalitet} \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} -\item Større ansvar og indflydelse +\item St{\o}rre ansvar og indflydelse \item Bedre karrieremuligheder \item Bedre relationer med andre -\item Færre undgåelige fejl -\item Flere venner og støtter -\item Andre mennesker stoler på dig -\item Højere selvværd +\item F{\ae}rre undg{\aa}elige fejl +\item Flere venner og st{\o}tter +\item Andre mennesker stoler p{\aa} dig +\item H{\o}jere selvv{\ae}rd \item Bedre livskvalitet \end{itemize} \end{minipage} % STRAN 4 drugi stolpec \begin{minipage}[t]{0.5\textwidth} -{\large \textbf{Hvad får teams ud af at \\ udvikle et Kvalitetsklima?}} +{\large \textbf{Hvad f{\aa}r teams ud af at \\ udvikle et Kvalitetsklima?}} \\ \\ Virksomhedens overlevelse er bestemt af dens \\ evne til at indfri de krav og forventninger som \\ -interessenterne -- interne såvel som eksterne -- \\ +interessenterne -- interne s{\aa}vel som eksterne -- \\ har. \\ -\hspace*{0.8\baselineskip}For at beholde kunder er det nødvendigt at \\ +\hspace*{0.8\baselineskip}For at beholde kunder er det n{\o}dvendigt at \\ indfri deres forventninger til kvaliteten af \\ virksomhedens produkter og serviceydelser. \\ -\hspace*{0.8\baselineskip} De bedste resultater opnås når organisation \\ -også fokuserer på de mennesker der leverer \\ +\hspace*{0.8\baselineskip} De bedste resultater opn{\aa}s n{\aa}r organisation \\ +ogs{\aa} fokuserer p{\aa} de mennesker der leverer \\ kvaliteten.\\ \hspace*{0.8\baselineskip} Virksomhedens kvalitet er et resultat af \\ -individers og teams' præstationer (afdelinger, \\ +individers og teams' pr{\ae}stationer (afdelinger, \\ projektgrupper eller teams). \\ \hspace*{0.8\baselineskip}Den samlede \textit{Teamkvalitet} er et resultat af \\ -præstationerne af de enkelte medlemmer -- og \\ +pr{\ae}stationerne af de enkelte medlemmer -- og \\ deres evne til at koordinere deres indsats og skabe \\ -et miljø, som inspirerer alle til at yde deres bedste.\\ +et milj{\o}, som inspirerer alle til at yde deres bedste.\\ \\ \textbf{Teamets fordele ved at forbedre \\ Teamkvalitet} \\ Teams forbedrer deres evne til at: \renewcommand\labelitemi{\small$\bullet$} \begin{itemize}[leftmargin=*] -\item Definere teamets kvalitetsmål -\item Overvåge teamkvaliteten -\item Opnå de aftalte kvalitetsmål -\item Identificere de områder hvor \\ +\item Definere teamets kvalitetsm{\aa}l +\item Overv{\aa}ge teamkvaliteten +\item Opn{\aa} de aftalte kvalitetsm{\aa}l +\item Identificere de omr{\aa}der hvor \\ teamkvalitet skal udvikles -\item Bedømme om teamets kultur \\ +\item Bed{\o}mme om teamets kultur \\ inspirerer medlemmerne til at yde \\ deres bedste -\item Forbedre kvaliteten af både \\ +\item Forbedre kvaliteten af b{\aa}de \\ individer og teams \item Inspirere og engagere alle i \\ kvalitetsprocessen @@ -365,49 +365,49 @@ kvalitetsprocessen relationer med teamets \\ interessenter \item Forbedre \textit{Kvalitetsklimaet } i teamet og \\ -skabe en bedre holdånd +skabe en bedre hold{\aa}nd \item Sikre at teamets navn forbindes med \\ kvalitet \end{itemize} \end{minipage} % STRAN 5 -\chapter{\Large \textbf{15 Kvalitetsklima områder}} +\chapter{\Large \textbf{15 Kvalitetsklima omr{\aa}der}} \begin{minipage}[t]{0.54\textwidth} \ \\ -{\large \textbf{1. Mål}} \\ -Vores mål er meningsfulde. De er klart \\ -definerede. De er realistiske, men også \\ -udfordrende. Vores mål har en tidsfrist. Vi har \\ -både små og store mål, kortsigtede og \\ -langsigtede mål, kvantitative og kvalitative mål. \\ -Vi sætter løbende mål på det personlige plan \\ +{\large \textbf{1. M{\aa}l}} \\ +Vores m{\aa}l er meningsfulde. De er klart \\ +definerede. De er realistiske, men ogs{\aa} \\ +udfordrende. Vores m{\aa}l har en tidsfrist. Vi har \\ +b{\aa}de sm{\aa} og store m{\aa}l, kortsigtede og \\ +langsigtede m{\aa}l, kvantitative og kvalitative m{\aa}l. \\ +Vi s{\ae}tter l{\o}bende m{\aa}l p{\aa} det personlige plan \\ og for teamet for forskellige perioder: dage, \\ -uger, måneder, år og langsigtede visioner. Vi \\ -justerer vores langsigtede mål mindst en gang \\ -om året. \\ -\hspace*{0.8\baselineskip}Læs mere i Claus Møllers bog \textit{Mit \\ Life Livstræ}, -i kapitlet 'Mine personlige mål'. +uger, m{\aa}neder, {\aa}r og langsigtede visioner. Vi \\ +justerer vores langsigtede m{\aa}l mindst en gang \\ +om {\aa}ret. \\ +\hspace*{0.8\baselineskip}L{\ae}s mere i Claus M{\o}llers bog \textit{Mit \\ Life Livstr{\ae}}, +i kapitlet 'Mine personlige m{\aa}l'. \\ \\ {\large \textbf{2. Ansvarsfordeling/organisation}} \\ Alle har overblik og kontrol over de krav der \\ -bliver stillet til ham ellers hende, områderne \\ +bliver stillet til ham ellers hende, omr{\aa}derne \\ hvor de er ansvarlige for at skabe resultater og \\ -opnå mål. Alle holder øje med de store opgaver \\ -og projekter som de løbende er involveret i \\ -og forstår deres autoritetsbeføjelser. +opn{\aa} m{\aa}l. Alle holder {\o}je med de store opgaver \\ +og projekter som de l{\o}bende er involveret i \\ +og forst{\aa}r deres autoritetsbef{\o}jelser. \\ \\ -Primære opgaver indeholder aktiviteter, som en \\ -person skal udføre for at nå et specifikt resultat. \\ +Prim{\ae}re opgaver indeholder aktiviteter, som en \\ +person skal udf{\o}re for at n{\aa} et specifikt resultat. \\ Det er en liste over alle opgaver og aktiviteter de \\ -bliver nødt til at udføre inden for hvert ansvars \\ -nøgleområde. Aktiviteter er de specifikke ting \\ -der skal udføres for at kunne løse en opgave. \\ -Hvis nye krav opstår er det nemt for alle at \\ -tilpasse sig disse og de er alle villige til at påtage \\ +bliver n{\o}dt til at udf{\o}re inden for hvert ansvars \\ +n{\o}gleomr{\aa}de. Aktiviteter er de specifikke ting \\ +der skal udf{\o}res for at kunne l{\o}se en opgave. \\ +Hvis nye krav opst{\aa}r er det nemt for alle at \\ +tilpasse sig disse og de er alle villige til at p{\aa}tage \\ sig nye ansvar, en ny type af arbejdsopgaver -- \\ -selv på tværs af professionelle eller team skel og \\ +selv p{\aa} tv{\ae}rs af professionelle eller team skel og \\ der er behov for at diskutere spillereglerne. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ \textit{Employeeship}, i kapitlet 'Ansvar' og afsnittet \\ om 'Multifunktionelle ansatte'. \end{minipage} @@ -415,43 +415,43 @@ om 'Multifunktionelle ansatte'. \begin{minipage}[t]{0.5\textwidth} \ \\ {\large \textbf{3. Effektivitet/produktivitet}} \\ -Alle prioriterer rigtigt. Vi gør de rigtige ting -- \\ -dem der fører til de ønskede resultater. Vi når \\ -vores mål med mindst mulig brug af ressourcer. \\ -Vi gennemfører ting til den aftalte tid. \\ - Udstyr, kontorer m.v. er pæne og rede. Alle har \\ -overblik og styr på tingene. Alle sørger for at deres \\ -bidrag til teamet og virksomheden er mere værd \\ -end deres løn. Teams indfrier omgivelsernes \\ +Alle prioriterer rigtigt. Vi g{\o}r de rigtige ting -- \\ +dem der f{\o}rer til de {\o}nskede resultater. Vi n{\aa}r \\ +vores m{\aa}l med mindst mulig brug af ressourcer. \\ +Vi gennemf{\o}rer ting til den aftalte tid. \\ + Udstyr, kontorer m.v. er p{\ae}ne og rede. Alle har \\ +overblik og styr p{\aa} tingene. Alle s{\o}rger for at deres \\ +bidrag til teamet og virksomheden er mere v{\ae}rd \\ +end deres l{\o}n. Teams indfrier omgivelsernes \\ effektivitets- og produktivitetskrav. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bøger \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers b{\o}ger \\ \textit{Employeeship}, i kapitlet om 'Produktivitet' \\ og \textit{Heart Work}, i kapitlet 'At styre \\ - sine følelser'. + sine f{\o}lelser'. \\ \\ {\large \textbf{4. Beslutninger}} \\ -Beslutninger træffes i teamet på et \\ -hensigtsmæssigt niveau. Beslutningshastigheden \\ +Beslutninger tr{\ae}ffes i teamet p{\aa} et \\ +hensigtsm{\ae}ssigt niveau. Beslutningshastigheden \\ er tilfredsstillende -- beslutninger bliver ikke \\ -forsinkede eller udsat. De er truffet på et \\ -tilstrækkeligt grundlag. Vi involverer de rigtige \\ -mennesker i vores beslutninger. Vi træffer \\ -beslutninger der går ud over hvad vi plejer og \\ +forsinkede eller udsat. De er truffet p{\aa} et \\ +tilstr{\ae}kkeligt grundlag. Vi involverer de rigtige \\ +mennesker i vores beslutninger. Vi tr{\ae}ffer \\ +beslutninger der g{\aa}r ud over hvad vi plejer og \\ hvad omgivelserne forventer, selv om de \\ -indebærer risiko. Vi er I stand til at træffe gode \\ -beslutninger hurtigt når situationen \\ -kræver det. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \textit{Heart \\ Work}, +indeb{\ae}rer risiko. Vi er I stand til at tr{\ae}ffe gode \\ +beslutninger hurtigt n{\aa}r situationen \\ +kr{\ae}ver det. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \textit{Heart \\ Work}, specielt i sektionen om 'EI-personen'. \\ \\ {\large \textbf{5. Delegering}} \\ -Lederne delegerer tilstrækkeligt. Alles evner \\ -udnyttes. Alle får mulighed for at gøre det de kan \\ -og vil. Lederne er indstillet på at afgive ansvar og \\ -magt. Alle er indstillet på at påtage sig ansvar. \\ -Alle udvikles til at kunne påtage sig stadig større \\ +Lederne delegerer tilstr{\ae}kkeligt. Alles evner \\ +udnyttes. Alle f{\aa}r mulighed for at g{\o}re det de kan \\ +og vil. Lederne er indstillet p{\aa} at afgive ansvar og \\ +magt. Alle er indstillet p{\aa} at p{\aa}tage sig ansvar. \\ +Alle udvikles til at kunne p{\aa}tage sig stadig st{\o}rre \\ ansvar. Delegeringsteknikken er effektiv. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møller bog \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}ller bog \\ \textit{Employeeship}, i kapitlet om 'Employeeship \\ delegering'. \end{minipage} @@ -461,16 +461,16 @@ delegering'. % STRAN 6 \begin{minipage}[t]{0.54\textwidth} {\large \textbf{6. Intern kommunikation og \\ koordination}} \\ -Vi fungerer som et team. Alle trækker i samme \\ -retning. Vi informerer hinanden tilstrækkeligt. \\ -Vores kommunikationsform er hensigtsmæssig. \\ -Vores møder er effektive: velforberedte, god \\ -mødedisciplin, aktiv deltagelse af alle, \\ +Vi fungerer som et team. Alle tr{\ae}kker i samme \\ +retning. Vi informerer hinanden tilstr{\ae}kkeligt. \\ +Vores kommunikationsform er hensigtsm{\ae}ssig. \\ +Vores m{\o}der er effektive: velforberedte, god \\ +m{\o}dedisciplin, aktiv deltagelse af alle, \\ handlingsplaner med navne og tidsfrister og \\ konkrete resultater. Vi viser respekt for \\ hinandens tid og vi udnytter hinandens viden og \\ -lærer fra hinanden -- på tværs af faggrænser. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ +l{\ae}rer fra hinanden -- p{\aa} tv{\ae}rs af faggr{\ae}nser. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ \textit{Employeeship}, i afsnittet 'Employeeship \\ og kommunikation' og i kapitlet \\ 'Employeeship og 'vi-kultur'. @@ -481,59 +481,59 @@ virksomheden. Vi fungerer godt med \\ interessenter uden for virksomheden -- \\ og kender dem (''vores kunder''). Vi kender hver \\ interessents forventninger til teamet. Teamet \\ -informerer interessenterne hensigtsmæssigt. Vi \\ -kender vores konkurrenter og deres stærke og \\ +informerer interessenterne hensigtsm{\ae}ssigt. Vi \\ +kender vores konkurrenter og deres st{\ae}rke og \\ svage sider. Vi registrerer og tilpasser os \\ forandringerne omkring os. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bøger \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers b{\o}ger \\ \textit{En reklamation er en gave}, og \textit{Employeeship}, i \\ afsnittet 'Nye krav til virksomheder'. \\ \\ -{\large \textbf{8. Fysisk miljø}} \\ -Det fysiske miljø fremmer teamets trivsel og \\ +{\large \textbf{8. Fysisk milj{\o}}} \\ +Det fysiske milj{\o} fremmer teamets trivsel og \\ effektivitet. Kontorerne er lyse og har god plads. \\ -Møblerne er moderne, velholdte og ergonomisk \\ -gennemtænkte. Der er nok plads til alle også til \\ -gæster udefra. Belysningen er veltilpasset. Støj er \\ -under kontrol og alle kan undgå for meget af den. \\ +M{\o}blerne er moderne, velholdte og ergonomisk \\ +gennemt{\ae}nkte. Der er nok plads til alle ogs{\aa} til \\ +g{\ae}ster udefra. Belysningen er veltilpasset. St{\o}j er \\ +under kontrol og alle kan undg{\aa} for meget af den. \\ Der er nok frisk luft eller tilfredsstillende \\ ventilation. Udstyr og materiel er i god stand. \end{minipage} % STRAN 6 drugi stolpec \begin{minipage}[t]{0.5\textwidth} {\large \textbf{9. Kvalitetsbevidsthed}} \\ -Der er klare kvalitetsmål for alle funktioner, \\ +Der er klare kvalitetsm{\aa}l for alle funktioner, \\ produkter og aktiviteter. Alle team medlemmer \\ -kender teamets kvalitetsområder og -faktorer. \\ +kender teamets kvalitetsomr{\aa}der og -faktorer. \\ Alle ved hvordan teamets kvalitet males. Vi \\ -kontrollerer løbende, om der er forskel på det vi \\ -gerne vil præstere og det vi faktisk præsterer. \\ +kontrollerer l{\o}bende, om der er forskel p{\aa} det vi \\ +gerne vil pr{\ae}stere og det vi faktisk pr{\ae}sterer. \\ Alle i teamet arbejder med kvalitetsudvikling. Vi \\ behandler andre mennesker og teams som \\ -''vigtige kunder''. Vi efterprøver løbende \\ -fornuften af vores handlinger (værdianalyse). \\ +''vigtige kunder''. Vi efterpr{\o}ver l{\o}bende \\ +fornuften af vores handlinger (v{\ae}rdianalyse). \\ Den enkelte garanterer kvaliteten af sit eget \\ arbejde. Vi forlanger kvalitet af hinanden -- og \\ -af vores omgivelser. Vi gør noget aktivt for \\ - at forebygge fejl. Vi håndterer fejl effektivt, \\ - glæder os når vi konstaterer dem, takker de \\ -personer der gør os opmærksom på fejlene, \\ - retter dem og undgår at gentage dem. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ +af vores omgivelser. Vi g{\o}r noget aktivt for \\ + at forebygge fejl. Vi h{\aa}ndterer fejl effektivt, \\ + gl{\ae}der os n{\aa}r vi konstaterer dem, takker de \\ +personer der g{\o}r os opm{\ae}rksom p{\aa} fejlene, \\ + retter dem og undg{\aa}r at gentage dem. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ \textit{Personlig kvalitet}, specielt afsnittet om \\ -'Personlig garanti' eller læs pjecen \\ +'Personlig garanti' eller l{\ae}s pjecen \\ \textit{Personlig garanti}. \\ \\ {\large \textbf{10. Kreativitet og innovation}} \\ Vi ser udviklingen omkring os. Alle indser \\ -nødvendigheden af forandring. Vi har let ved at \\ +n{\o}dvendigheden af forandring. Vi har let ved at \\ omstille os. Vi er fleksible. Vi tager ny teknologi i \\ -anvendelse. Vi anvender kreativ tænkning og \\ +anvendelse. Vi anvender kreativ t{\ae}nkning og \\ kreativitetsteknikker. Alle ager initiativ til at \\ -forbedre teamets indsats: gøre tingene hurtigere, \\ -med færre omkostninger, mere kreativt og med \\ -en højere kvalitet. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ +forbedre teamets indsats: g{\o}re tingene hurtigere, \\ +med f{\ae}rre omkostninger, mere kreativt og med \\ +en h{\o}jere kvalitet. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ \textit{Employeeship}, i kapitlerne 'Employeeship \\ delegering' og 'Initiativ'. \end{minipage} @@ -544,30 +544,30 @@ delegering' og 'Initiativ'. \begin{minipage}[t]{0.54\textwidth} {\large \textbf{11. Engagement}} \\ Alle i vores team er engagerede i deres arbejde -- \\ -både med hjernen og med hjertet. Alle føler \\ +b{\aa}de med hjernen og med hjertet. Alle f{\o}ler \\ medansvar for teamets succeser og fiaskoer. Vi \\ stiller alle op og yder vores bedste. Vi holder \\ vores aftaler. Vi tager alle initiativ til forbedring \\ -og udvikling. Alle er indstillet på at yde en ekstra \\ -indsats, når det er påkrævet. Alle ser en \\ +og udvikling. Alle er indstillet p{\aa} at yde en ekstra \\ +indsats, n{\aa}r det er p{\aa}kr{\ae}vet. Alle ser en \\ udfordring i enhver funktion og handling. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bøger \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers b{\o}ger \\ \textit{Employeeship} og \textit{Heart Work}. \\ \\ -{\large \textbf{12. Anerkendelse og belønning}} \\ +{\large \textbf{12. Anerkendelse og bel{\o}nning}} \\ Anerkendelse forekommer hyppigere end kritik \\ -og irettesættelse. Gode resultater bliver \\ -synliggjort. Gode præstationer belønnes. Gode \\ -præstationer fremmer mulighederne for \\ +og irettes{\ae}ttelse. Gode resultater bliver \\ +synliggjort. Gode pr{\ae}stationer bel{\o}nnes. Gode \\ +pr{\ae}stationer fremmer mulighederne for \\ forfremmelse og udvikling. Ledelsen fokuserer \\ -mere på medlemmernes stærke sider end på \\ +mere p{\aa} medlemmernes st{\ae}rke sider end p{\aa} \\ deres svage. Vi sikrer os, at vi ikke har \\ -medlemmer som stort set aldrig får \\ -opmærksomhed og anerkendelse. Vi forventer \\ -anerkendelse snarere end kritik, når et medlem \\ +medlemmer som stort set aldrig f{\aa}r \\ +opm{\ae}rksomhed og anerkendelse. Vi forventer \\ +anerkendelse snarere end kritik, n{\aa}r et medlem \\ kaldes til lederen. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \textit{Mit \\ livstræ}, -i kapitlet 'Miljøet -- grobunden'. +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \textit{Mit \\ livstr{\ae}}, +i kapitlet 'Milj{\o}et -- grobunden'. \\ \\ {\large \textbf{13. Personlig udvikling}} \\ Alle inspireres til udvikling. Der findes planer for \\ @@ -578,58 +578,58 @@ muligheder for jobrotation, fleksible arbejdstider \\ og -steder, jobdeling, variation, og forfremmelse. \\ Hver enkelt stilles over for rimelige \\ udfordringer. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ -\textit{Employeeship}, i afsnittet 'Den bløde \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ +\textit{Employeeship}, i afsnittet 'Den bl{\o}de \\ kontrakt' og kapitlet \\ 'Employeeship personalepolitik'. \end{minipage} % STRAN 7 drugi stolpec \begin{minipage}[t]{0.5\textwidth} -{\large \textbf{14. Øvrigt psykisk miljø (relationer)}} \\ -Vi har det sjovt sammen. Vi glæder os til at \\ -komme på arbejde -- hver dag. Vi føler os trygge i \\ -ansættelsen. Vi hjælper og opmuntrer hinanden. \\ -Vores kommunikation er baseret på åbenhed og \\ -ærlighed. Vi viser tillid til hinanden. Vi taler med \\ +{\large \textbf{14. {\o}vrigt psykisk milj{\o} (relationer)}} \\ +Vi har det sjovt sammen. Vi gl{\ae}der os til at \\ +komme p{\aa} arbejde -- hver dag. Vi f{\o}ler os trygge i \\ +ans{\ae}ttelsen. Vi hj{\ae}lper og opmuntrer hinanden. \\ +Vores kommunikation er baseret p{\aa} {\aa}benhed og \\ +{\ae}rlighed. Vi viser tillid til hinanden. Vi taler med \\ hinanden -- og ikke om hinanden. Vi respekterer \\ -hinandens kunnen, ønsker og særpræg. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \textit{Mit \\ Life livstræ}. +hinandens kunnen, {\o}nsker og s{\ae}rpr{\ae}g. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \textit{Mit \\ Life livstr{\ae}}. \\ \\ {\large \textbf{15. Implementering}} \\ -Vi kan omskrive ideer og tanker til handling. Vi får \\ +Vi kan omskrive ideer og tanker til handling. Vi f{\aa}r \\ tingene gjort i stedet for kun at tale om dem. Vi \\ -færdiggør de ting, vi har sat i gang. Vi følger op pa \\ -at opgaver udføres som aftalt. Ledelsen følger op \\ -på og bevarer interessen for opgaver, som den \\ -har sat i gang. Vi koncentrerer os om få vigtige \\ +f{\ae}rdigg{\o}r de ting, vi har sat i gang. Vi f{\o}lger op pa \\ +at opgaver udf{\o}res som aftalt. Ledelsen f{\o}lger op \\ +p{\aa} og bevarer interessen for opgaver, som den \\ +har sat i gang. Vi koncentrerer os om f{\aa} vigtige \\ opgaver ad gangen, frem for at have en masse \\ -bolde i luften på en gang. Ansvaret for et projekt \\ +bolde i luften p{\aa} en gang. Ansvaret for et projekt \\ er entydigt placeret hos én projektleder. \\ -Projektlederen har beføjelser og myndighed til at \\ -træffe alle nødvendige beslutninger på vej mod \\ -målet. Vi kan fjerne forhindringerne på vej mod \\ -målet. \\ -\hspace*{0.8\baselineskip} Læs mere i Claus Møllers bog \\ +Projektlederen har bef{\o}jelser og myndighed til at \\ +tr{\ae}ffe alle n{\o}dvendige beslutninger p{\aa} vej mod \\ +m{\aa}let. Vi kan fjerne forhindringerne p{\aa} vej mod \\ +m{\aa}let. \\ +\hspace*{0.8\baselineskip} L{\ae}s mere i Claus M{\o}llers bog \\ \textit{Employeeship}, i kapitlet 'Implementering \\ af Employeeship' og i bogen \textit{Personlig kvalitet}, \\ -specifikt afsnittet 'Lær at fuldføre -- skab \\ +specifikt afsnittet 'L{\ae}r at fuldf{\o}re -- skab \\ selvdisciplin'. \end{minipage} \newpage -\chapter{\Large \textbf{Sådan anvendes Kvalitetsklima værktøjet}} +\chapter{\Large \textbf{S{\aa}dan anvendes Kvalitetsklima v{\ae}rkt{\o}jet}} \begin{center} \begin{minipage}{0.8\linewidth} \begin{center} -{\large \textbf{Sådan anvender den enkelte værktøjet}} +{\large \textbf{S{\aa}dan anvender den enkelte v{\ae}rkt{\o}jet}} \end{center} \ \\ \renewcommand\labelitemi{\large$\bullet$} \begin{itemize} -\item Hver enkelt gennemgår sin egen besvarelse af \textit{Kvalitetsklima}. Derved \\ -opnås en bevidsthed om, på hvilke områder teamkvaliteten efter den \\ +\item Hver enkelt gennemg{\aa}r sin egen besvarelse af \textit{Kvalitetsklima}. Derved \\ +opn{\aa}s en bevidsthed om, p{\aa} hvilke omr{\aa}der teamkvaliteten efter den \\ enkeltes opfattelse skal udvikles. \item Ved at sammenligne egen besvarelse med det samlede teamresultat \\ kan hver enkelt se, hvordan deres vurdering er i forhold til kollegaernes \\ @@ -641,23 +641,23 @@ vurdering. \ \\ \\ \\ \renewcommand\labelitemi{\large$\bullet$} \begin{center} -{\large \textbf{Sådan anvender virksomheden værktøjet}} \\ +{\large \textbf{S{\aa}dan anvender virksomheden v{\ae}rkt{\o}jet}} \\ \end{center} \begin{itemize} -\item \textit{Kvalitetsklima} værktøjet anvendes af alle teams i virksomheden. \\ +\item \textit{Kvalitetsklima} v{\ae}rkt{\o}jet anvendes af alle teams i virksomheden. \\ Resultaterne i de enkelte teams sammenlignes og diskuteres med \\ -henblik på at opnå forbedringer i hele virksomheden. -\item Den øverste ledelse kan anvende Kvalitetsklima til at identificere teams, \\ -der trænger til hjælp, eller teams, som resten af virksomheden kan lære \\ +henblik p{\aa} at opn{\aa} forbedringer i hele virksomheden. +\item Den {\o}verste ledelse kan anvende Kvalitetsklima til at identificere teams, \\ +der tr{\ae}nger til hj{\ae}lp, eller teams, som resten af virksomheden kan l{\ae}re \\ noget af. -\item Man kan få et indtryk af hele virksomhedens stærke og svage sider ved \\ -at se på de områder, hvor alle teams stort set får samme vurdering. -\item Hvis et teams resultat for et eller flere områder afviger væsentligt fra \\ -alle andre teams resultater, får man et indtryk af teamets særpræg. +\item Man kan f{\aa} et indtryk af hele virksomhedens st{\ae}rke og svage sider ved \\ +at se p{\aa} de omr{\aa}der, hvor alle teams stort set f{\aa}r samme vurdering. +\item Hvis et teams resultat for et eller flere omr{\aa}der afviger v{\ae}sentligt fra \\ +alle andre teams resultater, f{\aa}r man et indtryk af teamets s{\ae}rpr{\ae}g. \item For at registrere forandringer og succeser anvendes Kvalitetsklima af \\ -alle teams mindst en gang om året, og når der sker strukturelle \\ -forandringer på markedet og internt I virksomheden. +alle teams mindst en gang om {\aa}ret, og n{\aa}r der sker strukturelle \\ +forandringer p{\aa} markedet og internt I virksomheden. \end{itemize} \end{minipage} \end{center} @@ -666,7 +666,7 @@ forandringer på markedet og internt I virksomheden. % STRAN 8 \chapter{\Large \textbf{Hvad viser graferne?}} \begin{minipage}[t]{0.5\textwidth} -De ansatte har vurderet områderne på en skala \\ +De ansatte har vurderet omr{\aa}derne p{\aa} en skala \\ fra 1 til 5.\\ \vspace*{-2.2\baselineskip} \hspace*{-0.2\baselineskip} \includegraphics[width=5.6cm]{../latexkosi/logo/legend_description_dan.png}~\\[1cm] @@ -674,7 +674,7 @@ fra 1 til 5.\\ \newline Graferne viser svarenes fordeling i procent. \\ -En god tommelfingerregel er, at der bør ske \\ +En god tommelfingerregel er, at der b{\o}r ske \\ forbedringer for de faktorer, hvor: \begin{itemize} \item[--] \textbf{mere end 20 \%} af de ansatte har \\ @@ -683,7 +683,7 @@ markeret 'Utilfredsstillende' eller \\ og/eller \item[--] \textbf{mindre end 50 \%} af de ansatte har \\ markeret 'Meget tilfredsstillende' eller \\ - 'Særdeles tilfredsstillende'. + 'S{\ae}rdeles tilfredsstillende'. \end{itemize} \ \\ \\ \\ \includegraphics[width=16cm]{../latexkosi/logo/klima_kakovosti_middle.png} % horizontal space @@ -693,13 +693,13 @@ markeret 'Meget tilfredsstillende' eller \\ \renewcommand\labelitemi{\small$\bullet$} \begin{itemize} \item Det samlede resultat af \\ -\textbf{Kvalitetsklima} bør diskuteres \\ -grundigt på et møde for afdelingens \\ +\textbf{Kvalitetsklima} b{\o}r diskuteres \\ +grundigt p{\aa} et m{\o}de for afdelingens \\ ansatte.\\ -Mødet bør munde ud i en konkret plan for \\ +M{\o}det b{\o}r munde ud i en konkret plan for \\ udvikling og forbedring af kvalitetskulturen. \\ -Denne plan bør med mellemrum justeres \\ -på afdelingsmøder. +Denne plan b{\o}r med mellemrum justeres \\ +p{\aa} afdelingsm{\o}der. \end{itemize} \end{minipage} diff --git a/admin/survey/modules/mod_EVOLI/latexkosi/tabela_The 15_components_ang.tex b/admin/survey/modules/mod_EVOLI/latexkosi/tabela_The 15_components_ang.tex index 47652dfdc..898d2b7c7 100644 --- a/admin/survey/modules/mod_EVOLI/latexkosi/tabela_The 15_components_ang.tex +++ b/admin/survey/modules/mod_EVOLI/latexkosi/tabela_The 15_components_ang.tex @@ -16,7 +16,7 @@ The 15 components of emotional intelligence & \hspace*{-0.5cm} 1. Self-awareness & Higher scores mean: &\\ \taburowcolors[2] 2{tableLineOne .. tableLineTwo} & \hspace*{-0.5cm} Self-appraisal & {\fontsize{10}{10}\selectfont The ability to be aware of, understand, accept and respect oneself. It means knowing one{'}s inner resources, strengths and weaknesses.} & \\ - & \hspace*{-0.5cm} Emotional \hspace*{-0.5cm} self-awareness & {\fontsize{10}{10}\selectfont The ability to recognise and understand one{'}s emotions and the way they affect oneself and others. To differentiate between one{'}s emotions. To know what one is feeling, why and what caused those feelings.} & \\ + & \hspace*{-0.5cm} Emotional \hspace*{-0.1cm} self-awareness & {\fontsize{10}{10}\selectfont The ability to recognise and understand one{'}s emotions and the way they affect oneself and others. To differentiate between one{'}s emotions. To know what one is feeling, why and what caused those feelings.} & \\ & \hspace*{-0.5cm} Objectivity & {\fontsize{10}{10}\selectfont The ability to assess the correspondence between what is subjectively experienced and what objectively exists. "Tuning in" to the immediate situation, keeping things in correct perspective without excessive fantasising.} & \\ & \hspace*{-0.5cm} Self-expression & {\fontsize{10}{10}\selectfont The ability to express emotions, beliefs and thoughts and to defend one{'}s rights in an assertive and non-destructive manner.} & \\ \tableHeaderStyle diff --git a/admin/survey/modules/mod_hierarhija/class/HierarhijaClass.php b/admin/survey/modules/mod_hierarhija/class/HierarhijaClass.php index e3dc747e2..a004aaaa9 100644 --- a/admin/survey/modules/mod_hierarhija/class/HierarhijaClass.php +++ b/admin/survey/modules/mod_hierarhija/class/HierarhijaClass.php @@ -392,7 +392,7 @@ class Hierarhija { echo '
    '; // popup za urejanje vrednosti - echo '
    '; + echo '
    '; echo '
    '; // fade pri fullscreen urejanje spremenljivke diff --git a/admin/survey/modules/mod_hierarhija/class/HierarhijaHelper.php b/admin/survey/modules/mod_hierarhija/class/HierarhijaHelper.php index b6b96d352..6c57ae424 100644 --- a/admin/survey/modules/mod_hierarhija/class/HierarhijaHelper.php +++ b/admin/survey/modules/mod_hierarhija/class/HierarhijaHelper.php @@ -110,10 +110,7 @@ class HierarhijaHelper $zamenjaj = $match; // email ne sme biti enak imenu ali priimku - if ($iskanje == 'ime ucitelja' && ! in_array($user->email, [ - $user->name, - $user->surname, - ])) { + if ($iskanje == 'ime ucitelja' && ! in_array($user->email, [$user->name, $user->surname])) { $zamenjaj = $user->name." ".$user->surname; } elseif ($iskanje == 'ime ucitelja') { preg_match('/(\w+)((?:\.)(\w+))?/', $user->email, $ucitelj); diff --git a/admin/survey/modules/mod_hierarhija/js/vendor/custom.js b/admin/survey/modules/mod_hierarhija/js/vendor/custom.js index 1714333f3..c601daabd 100644 --- a/admin/survey/modules/mod_hierarhija/js/vendor/custom.js +++ b/admin/survey/modules/mod_hierarhija/js/vendor/custom.js @@ -739,7 +739,7 @@ function jstree_vkljuci(jsonData) { function dodajKomentar() { $('#fade').fadeTo('slow', 1); - $('#vrednost_edit').html('').fadeIn('slow').load('ajax.php?anketa=' + anketa_id + '&t=hierarhija-ajax&a=komentar-k-hierarhiji&m=get'); + $('#vrednost_edit').wrapAll('
    ').html('').fadeIn('slow').load('ajax.php?anketa=' + anketa_id + '&t=hierarhija-ajax&a=komentar-k-hierarhiji&m=get'); } /** @@ -747,7 +747,7 @@ function dodajKomentar() { */ function uploadLogo() { $('#fade').fadeTo('slow', 1); - $('#vrednost_edit').html('').fadeIn('slow').load('ajax.php?anketa=' + anketa_id + '&t=hierarhija-ajax&a=upload-logo&m=get', function () { + $('#vrednost_edit').wrapAll('
    ').html('').fadeIn('slow').load('ajax.php?anketa=' + anketa_id + '&t=hierarhija-ajax&a=upload-logo&m=get', function () { //Vklopi nice input file $("input[type=file]").nicefileinput({ diff --git a/admin/survey/modules/mod_kakovost/R/calc.usability.R b/admin/survey/modules/mod_kakovost/R/calc.usability.R new file mode 100644 index 000000000..a464c3024 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/R/calc.usability.R @@ -0,0 +1,72 @@ +calc.usability <- function(m.all, return.type){ + # return.type: + # 1: return only absolute + # 2: return only % + # 3: return both (even rows: absolute, odd rows: %) + + ## calculations + m.all[, Prekinitve:=v3] + m.all[, Neodgovori:=v1] + m.all[, Nevsebinski:=v96+v97+v98+v99] + m.all[, Izpostavljen:=allqs-(v2+v3+v4+v5)] + setnames(m.all, "va", "Veljavni") + + m.all[, UNL:=Neodgovori/Izpostavljen] + m.all[is.na(UNL)==T, UNL:=0] + m.all[, UML:=(v3/allqs)+(1-(v3/allqs))*UNL] + m.all[, UCL:=1-UML] + m.all[, UIL:=v2/(v2+Izpostavljen)] + m.all[is.na(UIL)==T, UIL:=0] + m.all[, UAQ:=v4/allqs] + + m.all[, Uporabnost:=1-UML] + + #tidy up + setcolorder(m.all, c("recnum", "allqs", "Veljavni", "Nevsebinski", "Neodgovori", + "Izpostavljen", "Prekinitve", "Uporabnost", + "v1", "v2", "v3", "v4", "v5", "v96", "v97", "v98", "v99", + "UNL", "UML", "UCL", "UIL", "UAQ")) + + if(return.type==1){ + return(m.all) + }else{ + m.all.p <- copy(m.all) + + m.all.p[, (c("Veljavni", "Nevsebinski", "Neodgovori")) := lapply(.SD, "/", m.all.p$Izpostavljen), .SDcols=c("Veljavni", "Nevsebinski", "Neodgovori")] + m.all.p[, (c("Prekinitve", "v1", "v2", "v3", "v4", "v5", "v96", "v97", "v98", "v99")) := lapply(.SD, "/", m.all.p$allqs), .SDcols=c("Prekinitve", "v1", "v2", "v3", "v4", "v5", "v96", "v97", "v98", "v99")] + m.all.p[, Izpostavljen:=1] + + if(return.type==2){ + return(m.all.p) + }else{ + m.all[, Uporabnost:=Veljavni] + m.all[, c("UNL", "UML", "UCL", "UIL", "UAQ"):=NA] + m.all <- m.all[, lapply(.SD, as.character)] + + m.all.p[, allqs:=NA] + m.all.p[, allqs:=as.character(allqs)] + + change.cols <- c("Veljavni", "Nevsebinski", "Neodgovori", "Izpostavljen", "Prekinitve", "Uporabnost", + "v1", "v2", "v3", "v4", "v5", "v96", "v97", "v98", "v99", + "UNL", "UML", "UCL", "UIL", "UAQ") + m.all.p[, (change.cols):=lapply(.SD, function(x){paste0(round(x*100, 0), "%")}), .SD=change.cols] + + m.1ka <- data.table(matrix("", nrow=nrow(m.all)*2, ncol=ncol(m.all))) + + a.rows <- as.integer(seq(1, nrow(m.1ka), by=2)) + p.rows <- as.integer(seq(2, nrow(m.1ka), by=2)) + + set(m.1ka, a.rows, 1:ncol(m.1ka), value=m.all) + suppressWarnings(set(m.1ka, p.rows, 1:ncol(m.1ka), value=m.all.p)) + + setnames(m.1ka, colnames(m.all)) + m.1ka[, Status:=NA_character_] + setcolorder(m.1ka, c("recnum", "allqs", "Veljavni", "Nevsebinski", "Neodgovori", + "Izpostavljen", "Prekinitve", "Uporabnost", "Status", + "v1", "v2", "v3", "v4", "v5", "v96", "v97", "v98", "v99", + "UNL", "UML", "UCL", "UIL", "UAQ")) + + return(m.1ka) + } + } +} \ No newline at end of file diff --git a/admin/survey/modules/mod_kakovost/R/gen.survey.str.R b/admin/survey/modules/mod_kakovost/R/gen.survey.str.R new file mode 100644 index 000000000..0d523b177 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/R/gen.survey.str.R @@ -0,0 +1,71 @@ +gen.survey.str <- function(colnames.dsa, questions.file, items.file){ + #import questions file + questions <- fread(questions.file, skip=1, header=F, + select=c(2, 5, 6, 8, 9, 10), + col.names=c("question.id", "variable", "tip", "size", "visible", "params")) + + #create variable list from survey data file + #remove "recnum" and "_text" fields + var.data <- colnames.dsa[sapply(colnames.dsa, function(x){substr(x, nchar(x)-4, nchar(x))})!="_text"] + + #create variable list from questions file + var.questions <- questions$variable + + #generate data.table from var.data list + survey.str <- data.table(variable = var.data) + + setkey(questions, "variable") + setkey(survey.str, "variable") + + #if all var.data in var.questions, do the simple merge and return file + if(all(var.data %in% var.questions)){ + survey.str <- questions[survey.str,] + return(survey.str) + }else{ #if not, import items file and do additional merge with it... + #import items file + items <- fread(items.file, skip=1, header=F, + select=c(2, 3, 4), + col.names=c("question.id", "item.id", "variable")) + + setkey(items, "question.id") + setkey(questions, "question.id") + + #bind variables from questions and items (for the later, only take instances with no match in the questions file...) + survey.str.qi <- rbindlist(list(questions[var.questions %in% var.data,], + items[questions[!(var.questions %in% var.data), -"variable", with=F], nomatch=0L]), + fill=T) + + #merge questions+items with survey data... + setkey(survey.str.qi, "variable") + setkey(survey.str, "variable") + survey.str <- survey.str.qi[survey.str,] + + #if all var.data is now matched, return the survey.str + if(!(any(is.na(survey.str)))){ + return(survey.str) + }else{ #if not, do additional merging... + #create index of all NA instaces from survey.str... + index <- apply(cbind(survey.str[, is.na(tip)], + (sapply(survey.str[, variable], function(x){ + substr(x, 1, regexpr("\\_[^\\_]*$", x)-1) + }) %in% survey.str.qi$variable) + ), + 1, all) + + #... using regex to find matches among unmatched instances from survey.str.qi + add <- merge(survey.str[index, list(variable, substr(variable, 1, regexpr("\\_[^\\_]*$", variable)-1))], + survey.str.qi[!(variable %in% survey.str$variable),], + by.x="V2", by.y="variable", all.y=F)[, list(question.id, item.id, tip, visible, size, params)] + + #update survey.str with new values + survey.str[index, c("question.id", "item.id", "tip", "visible", "size", "params") := as.list(add)] + + #if there is no NAs left, return survey.str, else return msg + if(!(any(is.na(survey.str$tip)))){ + return(survey.str) + }else{ + return(paste("No match found for: ", survey.str[is.na(tip), variable])) + } + } + } +} diff --git a/admin/survey/modules/mod_kakovost/R/gen.usability.matrix.R b/admin/survey/modules/mod_kakovost/R/gen.usability.matrix.R new file mode 100644 index 000000000..2f0409a9e --- /dev/null +++ b/admin/survey/modules/mod_kakovost/R/gen.usability.matrix.R @@ -0,0 +1,181 @@ +gen.usability.matrix <- function(dsa, survey.str){ + #define special values to detect + #order of this values is important: + # in case of conflicts @ chk.t types of questions the order sets the priporty of which values to keep + special.v <- c(-1, -3, -5, -96, -97, -98, -99, -4, -2) + + #define which variables belong to checkbox-like* questions + #(* i.e.: check for special values @ ANY variable per question/item ID) + # 2: normal checkbox + # 16: multicheckbox + # 17: ranking + chkbox.t <- c(2, 16, 17) + + ##all other variables belong to normal** questions + #(** i.e.: check for special values @ each variable per question/item ID) + #if there are no normal questions, create 0 matrix, otherwise... + if(nrow(survey.str[!(tip %in% chkbox.t),])==0){ + m.n <- matrix(0, nrow = nrow(dsa), ncol=length(special.v)+1) + }else{ + #create list of all normal questions + c.n <- colnames(dsa)[which(colnames(dsa) %in% survey.str[!(tip %in% chkbox.t), variable])] + + #...count all non-special values for each variable + #... + count each special value for each variable + m.n <- cbind(rowSums(sapply(dsa[, c.n, with=FALSE], function(x){!(x %in% special.v)})), + sapply(special.v, function(x){as.integer(rowSums(dsa[, c.n, with=FALSE]==x, na.rm=TRUE))})) + } + + ##procedure for tip:2 + #only run if there is an at least one tip:2 variable + if(survey.str[, any(tip==2)]){ + #get list of all unique tip:2 question ids + q.2 <- unique(survey.str[tip==2, question.id]) + #get list of all corresponding variables for each q.2 id + c.2 <- lapply(q.2, function(x){colnames(dsa)[which(colnames(dsa) %in% survey.str[question.id==x & tip==2, variable])]}) + + #(do this for each instance in c.2): + #for each set of variables: + # check if any variable contains at least one non-special value + # + (for each special value) check if any variable contains at least special value + m.2 <- lapply(c.2, function(x){ + cbind(apply(dsa[, x, with=FALSE], 1, function(q){any(!(q %in% special.v))}), + sapply(special.v, function(y){ + apply(dsa[, x, with=FALSE], 1, function(q){any(q==y)}) + }) + ) + }) + + # (do this for each instance in c.2) + # if multiple special values per respondent exist, keep only the first one + m.2 <- lapply(m.2, function(x){ + if(any(rowSums(x)>1)){ + p <- x[rowSums(x)>1,] + for(i in 1:nrow(p)){ + a <- p[i,] + f <- TRUE + for(j in 1:length(a)){ + print(j) + if(a[j] & f){ + f <- FALSE + }else if(a[j] & !f){ + a[j] <- FALSE + } + } + p[i,] <- a + } + x[rowSums(x)>1,] <- p + }else{x} + }) + + + #add to m.n + m.n <- m.n + Reduce('+', m.2) + } + + ##procedure for tip:16 + #only run if there is an at least one tip:16 variable + if(survey.str[, any(tip==16)]){ + #get list of all unique tip:16 item ids + q.16 <- unique(survey.str[tip==16, item.id]) + + #get list of all corresponding variables for each q.16 id + c.16 <- lapply(q.16, function(x){colnames(dsa)[which(colnames(dsa) %in% survey.str[item.id==x & tip==16, variable])]}) + #(do this for each special value): + #for each set of variables, check if any variable contains at least one special value + # m.16 <- sapply(special.v, function(x){ + # rowSums(sapply(c.16, function(y){ + # apply(dsa[, y, with=FALSE], 1, function(q){any(q==x)}) + # })) + # }) + + #(do this for each instance in c.16): + #for each set of variables: + # check if any variable contains at least one non-special value + # + (for each special value) check if any variable contains at least special value + m.16 <- lapply(c.16, function(x){ + cbind(apply(dsa[, x, with=FALSE], 1, function(q){any(!(q %in% special.v))}), + sapply(special.v, function(y){ + apply(dsa[, x, with=FALSE], 1, function(q){any(q==y)}) + }) + ) + }) + + # (do this for each instance in c.16) + # if multiple special values per respondent exist, keep only the first one + m.16 <- lapply(m.16, function(x){ + if(any(rowSums(x)>1)){ + p <- x[rowSums(x)>1,] + for(i in 1:nrow(p)){ + a <- p[i,] + f <- TRUE + for(j in 1:length(a)){ + print(j) + if(a[j] & f){ + f <- FALSE + }else if(a[j] & !f){ + a[j] <- FALSE + } + } + p[i,] <- a + } + x[rowSums(x)>1,] <- p + }else{x} + }) + + m.n <- m.n + Reduce('+', m.16) + } + + ##procedure for tip:17 + #only run if there is an at least one tip:17 variable + if(survey.str[, any(tip==17)]){ + #get list of all unique tip:17 question ids + q.17 <- unique(survey.str[tip==17, question.id]) + + #get list of all corresponding variables for each q.17 id + c.17 <- lapply(q.17, function(x){colnames(dsa)[which(colnames(dsa) %in% survey.str[question.id==x & tip==17, variable])]}) + + #similiar procedure as for tip:2 and tip:16.... + m.17 <- lapply(c.17, function(x){ + cbind(apply(dsa[, x, with=FALSE], 1, function(q){any(!(q %in% special.v))}), + sapply(special.v, function(y){ + apply(dsa[, x, with=FALSE], 1, function(q){any(q==y)}) + }) + ) + }) + + #... the only difference is that we are checking for all rowsums > 0, not > 1 + m.17 <- lapply(m.17, function(x){ + if(any(rowSums(x)>1)){ + p <- x[rowSums(x)>0,] + for(i in 1:nrow(p)){ + a <- p[i,] + f <- TRUE + for(j in 1:length(a)){ + if(a[j] & f){ + f <- FALSE + }else if(a[j] & !f){ + a[j] <- FALSE + } + } + p[i,] <- a + } + x[rowSums(x)>0,] <- p + }else{x} + }) + + m.n <- m.n + Reduce('+', m.17) + } + + m.n <- cbind(m.n, rowSums(m.n)) + + if(all(m.n[, ncol(m.n)][1]==m.n[, ncol(m.n)])){ + m.n <- as.data.table(m.n) + m.n[, recnum:=dsa$recnum] + setnames(m.n, colnames(m.n)[-length(colnames(m.n))], c("va", "v1", "v3", "v5", "v96", "v97", "v98", "v99", "v4", "v2", "allqs")) + setcolorder(m.n, c("recnum", colnames(m.n)[-length(colnames(m.n))])) + return(m.n) + }else{ + print("not all rowsums equal!") + } +} \ No newline at end of file diff --git a/admin/survey/modules/mod_kakovost/R/kakovost.R b/admin/survey/modules/mod_kakovost/R/kakovost.R new file mode 100644 index 000000000..cd4f932c9 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/R/kakovost.R @@ -0,0 +1,47 @@ +#uporabnost <- function(params){ + +#setwd("path od mape, kjer se nahaja ta glavna datoteka, npr. C:/mapa") + +# Import data.table & functions ------------------------------------------------------ +require("data.table") +source("modules/mod_uporabnost/R/gen.survey.str.R") +source("modules/mod_uporabnost/R/gen.usability.matrix.R") +source("modules/mod_uporabnost/R/calc.usability.R") + +# Input data ------------------------------------------------------ +params <- commandArgs(trailingOnly = TRUE) +ID <- params[1] + +#get & import dsa: the main survey data file (containing only recnum, status, lurker and all variables relating to answers to survey questions) +dsa.file <- paste0("modules/mod_uporabnost/temp/data_", ID, ".csv") +dsa <- fread(dsa.file, header=T, drop=c(1:5, 7, 8)) + +#get question and item files +questions.file <- paste0("modules/mod_uporabnost/temp/questions_", ID, ".csv") +items.file <- paste0("modules/mod_uporabnost/temp/items_", ID, ".csv") + +# Main & Output ------------------------------------------------------ +#generate survey structure +survey.str <- gen.survey.str(colnames(dsa)[-(1)], questions.file, items.file) + +if(any(!(is.data.table(survey.str)), nrow(survey.str)==0)){ + write(survey.str, paste0("modules/mod_uporabnost/results/usability_", ID, ".csv")) +}else{ + #delete invisible variables and types: 5, 9, 22, 23, 25 + survey.str <- survey.str[visible==1 & !(tip %in% c(5, 9, 22, 23, 25)),] + + #generate usability matrix + m.all <- gen.usability.matrix(dsa, survey.str) + + if(any(!(is.data.table(m.all)), nrow(m.all)==0)){ + write(m.all, paste0("modules/mod_uporabnost/results/usability_", ID, ".csv")) + }else{ + #calculate usability indexes + m.final <- calc.usability(m.all, 3) + + #write to results + write.csv2(m.final, paste0("modules/mod_uporabnost/results/usability_", ID, ".csv"), row.names = FALSE) + } +} + +#} \ No newline at end of file diff --git a/admin/survey/modules/mod_kakovost/class.SurveyKakovost.php b/admin/survey/modules/mod_kakovost/class.SurveyKakovost.php new file mode 100644 index 000000000..8b4620d37 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/class.SurveyKakovost.php @@ -0,0 +1,906 @@ +ul evealvacija + if ((int)$anketa > 0){ + + $this->anketa = $anketa; + + # polovimo vrsto tabel (aktivne / neaktivne) + SurveyInfo :: getInstance()->SurveyInit($this->anketa); + if (SurveyInfo::getInstance()->getSurveyColumn('db_table') == 1) { + $this->db_table = '_active'; + } + + SurveyAnalysisHelper::getInstance()->Init($this->anketa); + + $this->_CURRENT_STATUS_FILTER = STATUS_FIELD.' ~ /6|5/'; + + Common::deletePreviewData($this->anketa); + + // Poskrbimo za datoteko s podatki + $SDF = SurveyDataFile::get_instance(); + $SDF->init($this->anketa); + + if($generateDataFile) + $SDF->prepareFiles(); + + $this->headFileName = $SDF->getHeaderFileName(); + $this->dataFileName = $SDF->getDataFileName(); + $this->dataFileStatus = $SDF->getStatus(); + + if ( $this->dataFileStatus == FILE_STATUS_NO_DATA || $this->dataFileStatus == FILE_STATUS_SRV_DELETED) { + Common::noDataAlert(); + exit(); + } + + # Inicializiramo in polovimo nastavitve missing profila + SurveyStatusProfiles::Init($this->anketa); + SurveyUserSetting::getInstance()->Init($this->anketa, $global_user_id); + SurveyConditionProfiles :: Init($this->anketa, $global_user_id); + SurveyTimeProfiles :: Init($this->anketa, $global_user_id); + SurveyVariablesProfiles :: Init($this->anketa, $global_user_id); + SurveyDataSettingProfiles :: Init($this->anketa); + + + // preberemo nastavitve iz baze (prej v sessionu) + SurveyUserSession::Init($this->anketa); + $this->sessionData = SurveyUserSession::getData(); + + if(isset($_SESSION['sid_'.$this->anketa]['usabilityIcons_settings'])) + $this->displayEditIconsSettings = ($_SESSION['sid_'.$this->anketa]['usabilityIcons_settings']); + + if (file_exists($this->headFileName) && $this->headFileName !== null && $this->headFileName != ''){ + $this->_HEADERS = unserialize(file_get_contents($this->headFileName)); + } + + # nastavimo vse filtre + $this->setUpFilter(); + + # nastavimo filtre uporabnika + $this->setUserFilters(); + + # nastavimo sortiranje + if(isset($_GET['sortField'])) + $this->sortField = $_GET['sortField']; + if(isset($_GET['sortType'])) + $this->sortType = $_GET['sortType']; + } + } + + + // Prikažemo stran + public function displayKakovost(){ + global $lang; + + // Prikaz nastavitev + $this->displayKakovostSettings(); + + // Izvedemo pripravo datoteke + $this->prepareData(); + + // Napolnimo podatke v array + $this->fillData(); + + // Izrisemo tabelo + $this->displayKakovostTable(); + + // Na koncu pobrisemo zacasne datoteke + //$this->deleteTemp(); + } + + // Prikazemo tabelo + private function displayKakovostTable(){ + global $site_path; + global $lang; + global $admin_type; + + echo '
    '; + + echo ''; + + + // NASLOVNE VRSTICE + if($this->sortType == 1){ + $sortType = 0; + $arrow = ' '; + } + else{ + $sortType = 1; + $arrow = ' '; + } + + if($admin_type == '0' || $admin_type == '1') + echo ''; + + echo ''; + echo ''; + + echo ''; + + echo ''; + + echo ''; + + // ali odstranimo vse stolpce s podrobnimi vrednostmi (-1, -2...) + if ($this->show_details == true) { + foreach ($this->_missings AS $value => $text){ + $cnt_miss++; + echo ""; + } + foreach ($this->_unsets AS $value => $text){ + $cnt_undefined++; + echo ""; + } + } + + // ali prikazemo podrobne izracune + if ($this->show_calculations == true) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + + + // VRSTICE S PODATKI + foreach($this->usability['data'] as $user){ + + // Prva vrstica z vrednostmi + echo ''; + + if($admin_type == '0' || $admin_type == '1'){ + + $sql = sisplet_query("SELECT id FROM srv_user WHERE ank_id='".$this->anketa."' AND recnum='".$user['recnum']."'"); + $row = mysqli_fetch_array($sql); + + echo ''; + } + + + echo ''; + + // Vsi + echo ''; + + // Ustrezni + echo ''; + + // Non-substantive + echo ''; + + // Non-response + echo ''; + + // Skupaj + echo ''; + + // Breakoffs + echo ''; + + // Uporabni + echo ''; + echo ''; + + // ali odstranimo vse stolpce s podrobnimi vrednostmi (-1, -2...) + if ($this->show_details == true) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + // ali prikazemo podrobne izracune + if ($this->show_calculations == true) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + + + // Druga vrstica s procenti + echo ''; + + // Ustrezni + echo ''; + + // Non-substantive + echo ''; + + // Non-response + echo ''; + + // Skupaj + echo ''; + + // Breakoffs + echo ''; + + // Uporabni + echo ''; + + // ali odstranimo vse stolpce s podrobnimi vrednostmi (-1, -2...) + if ($this->show_details == true) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + } + + + echo '
    User IDRecnum'./*$lang['recnum'].*/($this->sortField=='recnum' ? $arrow : '').''.$lang['srv_usableResp_qcount'].''.$lang['srv_usableResp_exposed'].''.$lang['srv_usableResp_breakoff'].($this->sortField=='breakoff' ? $arrow : '').''.$lang['srv_usableResp_usability'].'{$value}
    (".$lang['srv_usableResp_'.$text].")
    {$value}
    (".$lang['srv_usableResp_'.$text].")
    UNLUMLUCLUILUAQ
    '.$lang['srv_anl_valid'].($this->sortField=='valid' ? $arrow : '').''.$lang['srv_usableResp_nonsubstantive'].($this->sortField=='nonsubstantive' ? $arrow : '').''.$lang['srv_usableResp_nonresponse'].($this->sortField=='nonresponse' ? $arrow : '').''.$lang['srv_anl_suma1'].'%'.($this->sortField=='usable' ? $arrow : '').'Status'.($this->sortField=='status' ? $arrow : '').'
    '.$row['id'].''.$user['recnum'].''.$user['all'].''.$user['valid'].''.$user['nonsubstantive'].''.$user['nonresponse'].''.($user['valid']+$user['nonsubstantive']+$user['nonresponse']+$user['breakoff']).''.$user['breakoff'].''.$user['usable'].''.$user['status'].''.$user['-1'].''.$user['-2'].''.$user['-3'].''.$user['-4'].''.$user['-5'].''.$user['-97'].''.$user['-98'].''.$user['-99'].''.$user['UNL'].''.$user['UML'].''.$user['UCL'].''.$user['UIL'].''.$user['UAQ'].'
    '.$user['validPercent'].''.$user['nonsubstantivePercent'].''.$user['nonresponsePercent'].'100%'.$user['breakoffPercent'].''.$user['usablePercent'].''.$user['-1_percent'].''.$user['-2_percent'].''.$user['-3_percent'].''.$user['-4_percent'].''.$user['-5_percent'].''.$user['-97_percent'].''.$user['-98_percent'].''.$user['-99_percent'].'
    '; + + if($this->usability['all'] > 0){ + echo '
    '; + + //echo ''.$lang['srv_usableResp_usability'].': '; + echo ''.$lang['srv_usableResp_usable_unit'].' - Status 2 ('.$this->top_usable_limit.'%-100%): '.$this->usability['usable'].' ('.common::formatNumber($this->usability['usable']/$this->usability['all']*100, 0, null, '%').')'; + echo ''.$lang['srv_usableResp_partusable_unit'].' - Status 1 ('.$this->bottom_usable_limit.'%-'.$this->top_usable_limit.'%): '.$this->usability['partusable'].' ('.common::formatNumber($this->usability['partusable']/$this->usability['all']*100, 0, null, '%').')'; + echo ''.$lang['srv_usableResp_unusable_unit'].' - Status 0 (0%-'.$this->bottom_usable_limit.'%): '.$this->usability['unusable'].' ('.common::formatNumber($this->usability['unusable']/$this->usability['all']*100, 0, null, '%').')'; + + echo '
    '; + } + + echo '
    '; + } + + private function displayKakovostSettings(){ + global $lang; + + // Div z nastavitvami ki se razpre + echo '
    displayEditIconsSettings ? '' : ' style="display:none;"').'>'; + echo '
    '.$lang['srv_data_settings_checkboxes2'].'
    '; + + + echo '
    '; + + echo $lang['srv_usableResp_limit'].': '; + + echo ''.$lang['srv_usableResp_bottom_limit'].': %'; + echo ''.$lang['srv_usableResp_top_limit'].': %'; + + echo '
    '; + + echo '
    '; + echo $lang['srv_usableResp_show'].': '; + + // Prikaz neničelnih stolpcev + /*echo '';*/ + + // Prikaz podrobnosti + echo ''; + + // Prikaz podrobnih izracunov + echo ''; + echo '
    '; + + echo '
    '; + + + echo '
    '; + } + + + // Zgeneriramo pdf analizo + private function prepareData(){ + global $site_path; + global $lang; + global $admin_type; + + // Zgeneriramo zacasne csv datoteke + $this->prepareDataCSV(); + $this->prepareQuestionCSV(); + $this->prepareItemCSV(); + + // Poklicemo R skripto in zgeneriramo pdf + $script = $site_path . SCRIPT_FOLDER . '/kakovost.R'; + $out = exec('Rscript '.$script.' '.$this->anketa.' 2>&1', $output, $return_var); + + // Testiranje - izpis errorjev + if($admin_type == 0){ + echo '
    '; + echo 'Rscript '.$script; + //echo '
    '.$out.'
    '; + var_dump($output); + echo '
    '; + } + } + + // Napolnimo podatke v array + private function fillData(){ + global $site_path; + global $lang; + + $result_folder = $site_path . RESULTS_FOLDER.'/'; + + if (($handle = fopen($result_folder."usability_".$this->anketa.".csv", "r")) !== FALSE) { + + // Loop po vrsticah + $cnt = 0; + while (($row = fgetcsv($handle, 1000, ';')) !== FALSE) { + + if($cnt == 0) + $row = fgetcsv($handle, 1000, ';'); + + // Preberemo se drugo vrstico, ker so v parih + $row2 = fgetcsv($handle, 1000, ';'); + + + // Obarvamo vrstico glede na status (belo, rumeno, rdece) + if($row2[7] < (int)$this->bottom_usable_limit){ + $css_usable = 'unusable'; + $status = 0; + $this->usability['unusable']++; + } + elseif($row2[7] >= (int)$this->bottom_usable_limit && $row2[7] < (int)$this->top_usable_limit){ + $css_usable = 'partusable'; + $status = 1; + $this->usability['partusable']++; + } + else{ + $css_usable = 'usable'; + $status = 2; + $this->usability['usable']++; + } + $this->usability['all']++; + + + // Nastavimo izracunane podatke za respondenta + $this->usability['data'][$cnt]['recnum'] = $row[0]; + //$this->usability['data'][$cnt]['usr_id'] = $row['usr_id']; + $this->usability['data'][$cnt]['css'] = $css_usable; + $this->usability['data'][$cnt]['status'] = $status; + + $this->usability['data'][$cnt]['all'] = $row[1]; + + $this->usability['data'][$cnt]['valid'] = $row[2]; + $this->usability['data'][$cnt]['nonsubstantive'] = $row[3]; + $this->usability['data'][$cnt]['nonresponse'] = $row[4]; + + $this->usability['data'][$cnt]['validPercent'] = $row2[2]; + $this->usability['data'][$cnt]['nonsubstantivePercent'] = $row2[3]; + $this->usability['data'][$cnt]['nonresponsePercent'] = $row2[4]; + + $this->usability['data'][$cnt]['breakoff'] = $row[6]; + $this->usability['data'][$cnt]['breakoffPercent'] = $row2[6]; + + $this->usability['data'][$cnt]['usable'] = $row[7]; + $this->usability['data'][$cnt]['usablePercent'] = $row2[7]; + + $this->usability['data'][$cnt]['UNL'] = $row2[17]; + $this->usability['data'][$cnt]['UML'] = $row2[18]; + $this->usability['data'][$cnt]['UCL'] = $row2[19]; + $this->usability['data'][$cnt]['UIL'] = $row2[20]; + $this->usability['data'][$cnt]['UAQ'] = $row2[21]; + + $this->usability['data'][$cnt]['-1'] = $row[9]; + $this->usability['data'][$cnt]['-1_percent'] = $row2[9]; + $this->usability['data'][$cnt]['-2'] = $row[10]; + $this->usability['data'][$cnt]['-2_percent'] = $row2[10]; + $this->usability['data'][$cnt]['-3'] = $row[11]; + $this->usability['data'][$cnt]['-3_percent'] = $row2[11]; + $this->usability['data'][$cnt]['-4'] = $row[12]; + $this->usability['data'][$cnt]['-4_percent'] = $row2[12]; + $this->usability['data'][$cnt]['-5'] = $row[13]; + $this->usability['data'][$cnt]['-5_percent'] = $row2[13]; + $this->usability['data'][$cnt]['-97'] = $row[14]; + $this->usability['data'][$cnt]['-97_percent'] = $row2[14]; + $this->usability['data'][$cnt]['-98'] = $row[15]; + $this->usability['data'][$cnt]['-98_percent'] = $row2[15]; + $this->usability['data'][$cnt]['-99'] = $row[16]; + $this->usability['data'][$cnt]['-99_percent'] = $row2[16]; + + $cnt++; + } + } + + // Sortiramo podatke + foreach ($this->usability['data'] as $key => $row) { + $mid[$key] = $row[$this->sortField]; + } + if($this->sortType == 0) + array_multisort($mid, SORT_ASC, $this->usability['data']); + else + array_multisort($mid, SORT_DESC, $this->usability['data']); + + + # ali odstranimo stolpce kateri imajo same 0 + /*if ($this->show_with_zero == false) { + # odstranimo missinge brez vrednosti + foreach ($this->_missings AS $_key => $_missing) { + if (!isset($this->cols_with_value[$_key]) || $this->cols_with_value[$_key] == false) { + unset($this->_missings[$_key]); + } + } + # odstranimo neveljavne brez vrednosti + foreach ($this->_unsets AS $_key => $_unset) { + if (!isset($this->cols_with_value[$_key]) || $this->cols_with_value[$_key] == false) { + unset($this->_unsets[$_key]); + } + } + }*/ + } + + + // Pripravi csv s podatki + private function prepareDataCSV(){ + global $site_path; + global $lang; + global $admin_type; + + $temp_folder = $site_path . TEMP_FOLDER.'/'; + + $SDF = SurveyDataFile::get_instance(); + $SDF->init($this->anketa); + $_headFileName = $SDF->getHeaderFileName(); + $_dataFileName = $SDF->getDataFileName(); + $_fileStatus = $SDF->getStatus(); + + if ($_headFileName != null && $_headFileName != '') { + $_HEADERS = unserialize(file_get_contents($_headFileName)); + } + else { + echo 'Error! Empty file name!'; + } + + // Zaenkrat dopuscamo samo status 6 in brez lurkerjev + if($admin_type == '0') + $status_filter = '('.STATUS_FIELD.' ~ /6|5/)&&('.LURKER_FIELD.'==0)'; + else + $status_filter = '('.STATUS_FIELD.'==6)&&('.LURKER_FIELD.'==0)'; + + //$start_sequence = $_HEADERS['_settings']['dataSequence']; + $start_sequence = 2; + $end_sequence = $_HEADERS['_settings']['metaSequence'] + $_HEADERS['meta']['cnt_all']; + + $field_delimit = ';'; + + // Filtriramo podatke po statusu in jih zapisemo v temp folder + if (IS_WINDOWS) { + //$command = 'awk -F"|" "BEGIN {{OFS=\",\"} {ORS=\"\n\"}} '.$status_filter.' { print $0}" '.$_dataFileName.' >> '.$temp_folder.'/temp_data_'.$this->anketa.'.dat'; + $out = shell_exec('awk -F"|" "BEGIN {{OFS=\",\"} {ORS=\"\n\"}} '.$status_filter.'" '.$_dataFileName.' | cut -d "|" -f '.$start_sequence.'-'.$end_sequence.' >> '.$temp_folder.'/temp_data_'.$this->anketa.'.dat'); + } + else { + //$command = 'awk -F"|" \'BEGIN {{OFS=","} {ORS="\n"}} '.$status_filter.' { print $0; }\' '.$_dataFileName.' >> '.$temp_folder.'/temp_data_'.$this->anketa.'.dat'; + $out = shell_exec('awk -F"|" \'BEGIN {{OFS=","} {ORS="\n"}} '.$status_filter.'\' '.$_dataFileName.' | cut -d \'|\' -f '.$start_sequence.'-'.$end_sequence.' >> '.$temp_folder.'/temp_data_'.$this->anketa.'.dat'); + } + + + // Ustvarimo koncni CSV + if ($fd = fopen($temp_folder.'/temp_data_'.$this->anketa.'.dat', "r")) { + + $fd2 = fopen($temp_folder.'/data_'.$this->anketa.'.csv', "w"); + + # naredimo header row + foreach ($_HEADERS AS $spid => $spremenljivka) { + if (isset($spremenljivka['grids']) && count($spremenljivka['grids']) > 0) { + foreach ($spremenljivka['grids'] AS $gid => $grid) { + foreach ($grid['variables'] AS $vid => $variable ){ + if (!($variable['variable'] == 'uid' && $variable['naslov'] == 'User ID')){ + $output1 .= strip_tags($variable['variable']).$field_delimit; + //$output2 .= '"'.strip_tags($variable['naslov']).'"'.$field_delimit; + } + } + } + } + } + + // Pobrisemo zadnji ; ce obstaja + $output1 = rtrim($output1, ";"); + + // Zapisemo header row + fwrite($fd2, $output1."\r\n"); + //fwrite($fd2, $output2."\r\n"); + + + while ($line = fgets($fd)) { + + //fwrite($fd2, '="'); + //$line = str_replace(array("\r","\n","|"), array("","",'";="'), $line); + $line = '"' . str_replace(array("\r","\n","\"","|"), array("","","",'";"'), $line) . '"'; + + // Spremenimo encoding v windows-1250 + $line = iconv("UTF-8","Windows-1250//TRANSLIT", $line); + //$line = str_replace(array("č","š","ž","Č","Š","Ž"), array("\v{c}","\v{s}","\v{z}","\v{C}","\v{S}","\v{Z}"), $line); + + fwrite($fd2, $line); + //fwrite($fd2, '"'); + fwrite($fd2, "\r\n"); + } + + fclose($fd2); + } + fclose($fd); + + + // Na koncu pobrisemo temp datoteke + if (file_exists($temp_folder.'/temp_data_'.$this->anketa.'.dat')) { + unlink($temp_folder.'/temp_data_'.$this->anketa.'.dat'); + } + } + + // Pripravi csv z vprasanji + private function prepareQuestionCSV(){ + global $site_path; + global $lang; + global $admin_type; + + define('delimiter', ';'); + + $temp_folder = $site_path . TEMP_FOLDER.'/'; + + $fd = fopen($temp_folder.'/questions_'.$this->anketa.'.csv', "w"); + + + // Prva vrstica + $output = 'ID SURVEY'.delimiter; + $output .= 'ID QUESTION'.delimiter; + $output .= 'ID PAGE'.delimiter; + $output .= 'QUESTION NUMBER'.delimiter; + + $output .= 'variable'.delimiter; + $output .= 'tip'.delimiter; + $output .= 'vrstni_red'.delimiter; + $output .= 'size'.delimiter; + $output .= 'visible'.delimiter; + $output .= 'params'.delimiter; + + $output .= 'char_count'.delimiter; + + fwrite($fd, $output."\r\n"); + + + // Vrstice s podatki + $sql = sisplet_query("SELECT s.id, s.gru_id, s.variable, s.tip, s.vrstni_red, s.size, s.visible, s.params, s.naslov + FROM srv_spremenljivka s, srv_grupa g + WHERE s.gru_id=g.id AND g.ank_id='".$this->anketa."' + ORDER BY g.vrstni_red, s.vrstni_red"); + if (!$sql) echo mysqli_error($GLOBALS['connect_db']); + if (mysqli_num_rows($sql) > 0) { + + $i = 0; + + while ($row = mysqli_fetch_array($sql)) { + + $i++; + + $line = ''; + + $line .= $this->anketa.delimiter; + $line .= $row['id'].delimiter; + $line .= $row['gru_id'].delimiter; + $line .= $i.delimiter; + + $line .= $row['variable'].delimiter; + $line .= $row['tip'].delimiter; + $line .= $row['vrstni_red'].delimiter; + $line .= $row['size'].delimiter; + $line .= $row['visible'].delimiter; + + $line .= str_replace("\n", '', str_replace(delimiter, '', $row['params']) ).delimiter; + + $naslov_clean = iconv("UTF-8","Windows-1250//TRANSLIT", $row['naslov']); + $naslov_clean = trim(strip_tags($naslov_clean)); + $line .= strlen($naslov_clean).delimiter; + + fwrite($fd, $line."\r\n"); + } + } + + + fclose($fd); + } + + // Pripravi csv z itemi + private function prepareItemCSV(){ + global $site_path; + global $lang; + global $admin_type; + + define('delimiter', ';'); + + $temp_folder = $site_path . TEMP_FOLDER.'/'; + + $fd = fopen($temp_folder.'/items_'.$this->anketa.'.csv', "w"); + + + // Prva vrstica + $output = ''; + $output .= 'ID SURVEY'.delimiter; + $output .= 'ID QUESTION'.delimiter; + $output .= 'ID ITEM'.delimiter; + + $output .= 'variable'.delimiter; + $output .= 'variable_custom'.delimiter; + $output .= 'vrstni_red'.delimiter; + + $output .= 'char_count'.delimiter; + + fwrite($fd, $output."\r\n"); + + // Vrstice s podatki + $sql = sisplet_query("SELECT v.id, v.spr_id, v.variable, v.variable_custom, v.vrstni_red, v.naslov + FROM srv_vrednost v, srv_spremenljivka s, srv_grupa g + WHERE v.spr_id=s.id AND s.gru_id=g.id AND g.ank_id='".$this->anketa."' + ORDER BY g.vrstni_red, s.vrstni_red"); + if (!$sql) echo mysqli_error($GLOBALS['connect_db']); + if (mysqli_num_rows($sql) > 0) { + + while ($row = mysqli_fetch_array($sql)) { + + $line = ''; + + $line .= $this->anketa.delimiter; + $line .= $row['spr_id'].delimiter; + $line .= $row['id'].delimiter; + + $line .= str_replace("\n", '', str_replace(delimiter, '', $row['variable']) ).delimiter; + $line .= $row['variable_custom'].delimiter; + $line .= $row['vrstni_red'].delimiter; + + $naslov_clean = iconv("UTF-8","Windows-1250//TRANSLIT", $row['naslov']); + $naslov_clean = trim(strip_tags($naslov_clean)); + $line .= strlen($naslov_clean).delimiter; + + fwrite($fd, $line."\r\n"); + } + } + + + fclose($fd); + } + + + // Pobrisemo zacasne datoteke + private function deleteTemp(){ + global $site_path; + + $temp_folder = $site_path . TEMP_FOLDER.'/'; + $result_folder = $site_path . RESULTS_FOLDER.'/'; + + // Pobrisemo zacasno CSV datoteko s podatki + if (file_exists($temp_folder.'/data_'.$this->anketa.'.csv')) { + unlink($temp_folder.'/data_'.$this->anketa.'.csv'); + } + + // Pobrisemo zacasno CSV datoteko z vprasanji + if (file_exists($temp_folder.'/questions_'.$this->anketa.'.csv')) { + unlink($temp_folder.'/questions_'.$this->anketa.'.csv'); + } + + // Pobrisemo zacasno CSV datoteko z itemi + if (file_exists($temp_folder.'/items_'.$this->anketa.'.csv')) { + unlink($temp_folder.'/items_'.$this->anketa.'.csv'); + } + + // Pobrisemo CSV datoteko z rezultati + if (file_exists($result_folder.'/usability_'.$this->anketa.'.csv')) { + unlink($result_folder.'/usability_'.$this->anketa.'.csv'); + } + } + + + private function parentIf($anketa, $element) { + $sql = sisplet_query("SELECT tip FROM srv_if WHERE id = '$element'"); + $row = mysqli_fetch_array($sql); + + if ($row['tip'] == 0) return $element; + + $sql1 = sisplet_query("SELECT parent FROM srv_branching WHERE ank_id='$anketa' AND element_if = '$element'"); + $row1 = mysqli_fetch_array($sql1); + + return parentIf($anketa, $row1['parent']); + } + + + /** Funkcija ki nastavi vse filtre + * + */ + private function setUpFilter(){ + /*if ($this->dataFileStatus == FILE_STATUS_NO_DATA + || $this->dataFileStatus == FILE_STATUS_NO_FILE + || $this->dataFileStatus == FILE_STATUS_SRV_DELETED) + { + return false; + }*/ + + # poiščemo kater profil uporablja uporabnik + $_currentMissingProfile = SurveyUserSetting :: getInstance()->getSettings('default_missing_profile'); + $this->currentMissingProfile = (isset($_currentMissingProfile) ? $_currentMissingProfile : 1); + + # filtriranje po statusih + $this->_CURRENT_STATUS_FILTER = SurveyStatusProfiles :: getStatusAsAWKString(); + + # filtriranje po časih + $_time_profile_awk = SurveyTimeProfiles :: getFilterForAWK($this->_HEADERS['unx_ins_date']['grids']['0']['variables']['0']['sequence']); + + # dodamo še ife + + SurveyConditionProfiles :: setHeader($this->_HEADERS); + $_condition_profile_AWK = SurveyConditionProfiles:: getAwkConditionString(); + + if (($_condition_profile_AWK != "" && $_condition_profile_AWK != null ) + || ($_time_profile_awk != "" && $_time_profile_awk != null)) + { + $this->_CURRENT_STATUS_FILTER = '('.$this->_CURRENT_STATUS_FILTER; + if ($_condition_profile_AWK != "" && $_condition_profile_AWK != null ) + { + $this->_CURRENT_STATUS_FILTER .= ' && '.$_condition_profile_AWK; + } + if ($_time_profile_awk != "" && $_time_profile_awk != null) + { + $this->_CURRENT_STATUS_FILTER .= ' && '.$_time_profile_awk; + } + $this->_CURRENT_STATUS_FILTER .= ')'; + } + $status_filter = $this->_CURRENT_STATUS_FILTER; + + if ($this->dataFileStatus == FILE_STATUS_OK || $this->dataFileStatus == FILE_STATUS_OLD) + { + if (isset($this->_HEADERS['testdata'])) + { + $this->_HAS_TEST_DATA = true; + } + } + + $smv = new SurveyMissingValues($this->anketa); + $smv -> Init(); + + $smv_array = $smv->GetSurveyMissingValues($this->anketa); + if (!empty($smv_array[1])){ + foreach ($smv_array[1] AS $_survey_missings) + { + $this->_missings[$_survey_missings['value']] = $_survey_missings['text']; + + } + } + if (!empty($smv_array[2])){ + foreach ($smv_array[2] AS $_survey_unsets) + { + $this->_unsets[$_survey_unsets['value']] = $_survey_unsets['text']; + } + } + } + + private function setUserFilters(){ + # Nastavimo filtre variabel + $dvp = SurveyUserSetting :: getInstance()->getSettings('default_variable_profile'); + $_currentVariableProfile = SurveyVariablesProfiles :: checkDefaultProfile($dvp); + if ($dvp != $_currentVariableProfile) { + SurveyUserSetting :: getInstance()->saveSettings('default_variable_profile', $_currentVariableProfile); + } + $this->_PROFILE_ID_VARIABLE = $_currentVariableProfile; + + # ali prikazujemo tudi stolpce z 0 vrednostmi + if (isset($this->sessionData['usable_resp']['show_with_zero'])) { + $this->show_with_zero = $this->sessionData['usable_resp']['show_with_zero']; + } + + # ali prikazujemo tudi stolpce z 0 vrednostmi + if (isset($this->sessionData['usable_resp']['show_details'])) { + $this->show_details = $this->sessionData['usable_resp']['show_details']; + } + + # ali prikazujemo tudi stolpce z izracuni + if (isset($this->sessionData['usable_resp']['show_calculations'])) { + $this->show_calculations = $this->sessionData['usable_resp']['show_calculations']; + } + + # ali prikazujemo vrstice "Drugo" + $this->show_with_other = true; + if (isset($this->sessionData['usable_resp']['show_with_other'])) { + $this->show_with_other = $this->sessionData['usable_resp']['show_with_other']; + } + + # ali prikazujemo vrstice tipa "besedilo" + $this->show_with_text = true; + if (isset($this->sessionData['usable_resp']['show_with_text'])) { + $this->show_with_text = $this->sessionData['usable_resp']['show_with_text']; + } + + # Spodnja in zgornja meja za usable + if (isset($this->sessionData['usable_resp']['bottom_usable_limit'])) { + $this->bottom_usable_limit = $this->sessionData['usable_resp']['bottom_usable_limit']; + } + # ali prikazujemo tudi stolpce z 0 vrednostmi + if (isset($this->sessionData['usable_resp']['top_usable_limit'])) { + $this->top_usable_limit = $this->sessionData['usable_resp']['top_usable_limit']; + } + } + + // Ali imamo zgenerirano datoteko ali ne + private function hasDataFile(){ + if ($this->dataFileStatus == FILE_STATUS_NO_DATA || $this->dataFileStatus == FILE_STATUS_NO_FILE + || $this->dataFileStatus == FILE_STATUS_SRV_DELETED) + return false; + else + return true; + } + + private function setStatusFilter($status=''){ + + $this->_CURRENT_STATUS_FILTER = $status; + } + + + +} \ No newline at end of file diff --git a/admin/survey/modules/mod_kakovost/results/.gitignore b/admin/survey/modules/mod_kakovost/results/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/results/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/admin/survey/modules/mod_kakovost/temp/.gitignore b/admin/survey/modules/mod_kakovost/temp/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/admin/survey/modules/mod_kakovost/temp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/admin/survey/modules/mod_squalo/api_test.php b/admin/survey/modules/mod_squalo/api_test.php deleted file mode 100644 index 281b00aae..000000000 --- a/admin/survey/modules/mod_squalo/api_test.php +++ /dev/null @@ -1,46 +0,0 @@ -createList($list_name='Testni seznam Xyz'); - -/*$result = $squalo->createNewsletter( - $list_id = '5', - $subject = 'Testek', - $body = 'Testno sporočilo...', - $body_alt = 'Testno sporočilo ALT...', - $from_email = 'peter.hrvatin@gmail.com', - $from_name = 'Peter', - $reply_to_email = 'peter.hrvatin@gmail.com', - $language = 'sl' -);*/ - -//$result = $squalo->addRecipient($email='peter.h1203@gmail.com', $list_id='5'); - -//$result = $squalo->sendEmails($newsletter_id='24'); - -//$result = $squalo->deleteRecipient($recipient_id='2'); - - - - -echo '
    '; print_r($result); echo '
    '; - - - \ No newline at end of file diff --git a/admin/survey/modules/mod_voting/class.SurveyVoting.php b/admin/survey/modules/mod_voting/class.SurveyVoting.php new file mode 100644 index 000000000..f551dcbdc --- /dev/null +++ b/admin/survey/modules/mod_voting/class.SurveyVoting.php @@ -0,0 +1,71 @@ + 0){ + $this->anketa = $anketa; + } + } + + + // Izvedemo vse potrebno pri vklopu (vklopimo obvescanje, ugasnemo belezenje parapodatkov...) + public function turnOnVoting(){ + global $lang; + + SurveySetting::getInstance()->Init($this->anketa); + + // Ugasnimo belezenje vseh parapodatkov + SurveySetting::getInstance()->setSurveyMiscSetting('survey_ip', '1'); + SurveySetting::getInstance()->setSurveyMiscSetting('survey_show_ip', '0'); + SurveySetting::getInstance()->setSurveyMiscSetting('survey_browser', '1'); + SurveySetting::getInstance()->setSurveyMiscSetting('survey_referal', '1'); + SurveySetting::getInstance()->setSurveyMiscSetting('survey_date', '1'); + + // Vklopimo email vabila + sisplet_query("UPDATE srv_anketa SET user_base='1', show_email='0' WHERE id='".$this->anketa."'"); + sisplet_query("INSERT INTO srv_anketa_module (ank_id, modul) VALUES ('".$this->anketa."', 'email')"); + + // Ugasnemo obvescanje respondenta + sisplet_query("UPDATE srv_alert SET finish_respondent='0', finish_respondent_cms='0' WHERE ank_id='".$this->anketa."'"); + } + + // Nastavitve volitev + public function displaySettings(){ + global $lang; + + echo '
    '.$lang['settings'].''; + + echo '
    '; + + echo $lang['srv_voting_edit1'].' '.$lang['srv_voting_edit2'].'.'; + + echo '

    '; + + echo $lang['srv_voting_invitations1'].' '.$lang['srv_voting_invitations2'].'.'; + + echo '

    '; + + echo '
    '; + } + + // Pridobimo trenutne nastavitve volitev za anketo + private function getSettings(){ + + $settings = array(); + + return $settings; + } + +} \ No newline at end of file diff --git a/admin/survey/script/branching.js b/admin/survey/script/branching.js index 1dd4f39c9..02703def1 100644 --- a/admin/survey/script/branching.js +++ b/admin/survey/script/branching.js @@ -2035,13 +2035,16 @@ function dodaj_blok_interpretacije() { // --------------------------------------- // refresha levo stran z branchingom -function refreshLeft(spremenljivka, removeID) { +function refreshLeft(spremenljivka, callback) { $.post('ajax.php?t=branching&a=refresh_left', { spremenljivka: spremenljivka, anketa: srv_meta_anketa_id }, function (data) { $('#branching').html(data); + + if (typeof callback == "function") + callback(); }); } diff --git a/admin/survey/script/invitations.js b/admin/survey/script/invitations.js index 289b330d8..a0de2e7b3 100644 --- a/admin/survey/script/invitations.js +++ b/admin/survey/script/invitations.js @@ -1745,3 +1745,19 @@ function smtpAAIAccept(){ else $('#aai_smtp_button').hide(); } + + +// SQUALO +function squaloSwitch(){ + + if($('#squalo_mode').prop('checked')){ + $('#send_mail_mode0, #send_mail_mode1, #send_mail_mode2, .mail_mode_switch, #send_mail_mode_test').hide(); + $('#success_save').hide(); + } + else{ + $('.squalo_settings').hide(); + $('#send_mail_mode2, .mail_mode_switch, #send_mail_mode_test').show(); + $('#success_save').hide(); + } +} + diff --git a/admin/survey/script/js-lang.php b/admin/survey/script/js-lang.php index 7b45bfa71..e67585c77 100644 --- a/admin/survey/script/js-lang.php +++ b/admin/survey/script/js-lang.php @@ -192,6 +192,7 @@ lang('srv_alert_upload_size'); lang('srv_alert_upload_ext'); lang('srv_trans_lang'); lang('srv_manager_remove_alert'); +lang('srv_resevanje_foto_pre_result'); //LOKACIJA diff --git a/admin/survey/script/mobile.js b/admin/survey/script/mobile.js new file mode 100644 index 000000000..432fd9e2d --- /dev/null +++ b/admin/survey/script/mobile.js @@ -0,0 +1,75 @@ +function mobile_init(){ + + // init zeynepjs side menu + var zeynep = $('.mobile_menu').zeynep({ + opened: function () { + + }, + closed: function () { + + } + }) + + // dynamically bind 'closing' event + zeynep.on('closing', function () { + + }) + + // handle zeynepjs overlay click + $('.mobile_menu_close').on('click', function () { + zeynep.close(); + $('#fade').fadeOut(); + + $('.mobile_menu_close').fadeOut('fast', function(){ + $('.mobile_menu_open').fadeIn('fast'); + }); + }) + + // open zeynepjs side menu + $('.mobile_menu_open').on('click', function () { + zeynep.open(); + $('#fade').fadeIn(); + + $('.mobile_menu_open').fadeOut('fast', function(){ + $('.mobile_menu_close').fadeIn('fast'); + }); + }) +} + +// Popup za dodajanje vprasanja na mobile +function mobile_add_question_popup(){ + $('.mobile_add_question_popup').fadeIn(); +} + +// Popup za dodajanje vprasanja na mobile +function mobile_add_question_popup_close(){ + $('.mobile_add_question_popup').fadeOut(); +} + +// Popup za dodajanje vprasanja na mobile +function mobile_add_question(tip){ + + mobile_add_question_popup_close(); + + $.post('ajax.php?t=branching&a=spremenljivka_new', { + spremenljivka: 0, + 'if': 0, + endif: 1, + copy: 0, + tip: tip, + podtip: 0, + anketa: srv_meta_anketa_id + }, function (data) { + if (!data) return; + + refreshLeft(data.nova_spremenljivka_id, function(){ + + // Scroll do novega vprasanja + $("html, body").animate({ + scrollTop: $('#spremenljivka_content_'+data.nova_spremenljivka_id).offset().top - 100 + }, 1000); + }); + + }, 'json'); +} + diff --git a/admin/survey/script/mobileMenu/zeynep.min.js b/admin/survey/script/mobileMenu/zeynep.min.js new file mode 100644 index 000000000..be3e129fb --- /dev/null +++ b/admin/survey/script/mobileMenu/zeynep.min.js @@ -0,0 +1,9 @@ +/*! +* zeynepjs v2.1.2 +* A light-weight multi-level jQuery side menu plugin. +* It's fully customizable and is compatible with modern browsers such as Google Chrome, Mozilla Firefox, Safari, Edge and Internet Explorer +* MIT License +* by Huseyin ELMAS +*/ +!function(l,s){var n={htmlClass:!0};function i(e,t){this.element=e,this.eventController=o,this.options=l.extend({},n,t),this.options.initialized=!1,this.init()}i.prototype.init=function(){var s=this.element,e=this.options,i=this.eventController.bind(this);!0!==e.initialized&&(i("loading"),s.find("[data-submenu]").on("click",function(e){e.preventDefault();var t,n=l(this).attr("data-submenu"),o=l("#"+n);o.length&&(i("opening",t={subMenu:!0,menuId:n}),s.find(".submenu.current").removeClass("current"),o.addClass("opened current"),s.hasClass("submenu-opened")||s.addClass("submenu-opened"),s.scrollTop(0),i("opened",t))}),s.find("[data-submenu-close]").on("click",function(e){e.preventDefault();var t,n=l(this).attr("data-submenu-close"),o=l("#"+n);o.length&&(i("closing",t={subMenu:!0,menuId:n}),o.removeClass("opened current"),s.find(".submenu.opened:last").addClass("current"),s.find(".submenu.opened").length||s.removeClass("submenu-opened"),o.scrollTop(0),i("closed",t))}),i("load"),this.options.htmlClass&&!l("html").hasClass("zeynep-initialized")&&l("html").addClass("zeynep-initialized"),e.initialized=!0)},i.prototype.open=function(){this.eventController("opening",{subMenu:!1}),this.element.addClass("opened"),this.options.htmlClass&&l("html").addClass("zeynep-opened"),this.eventController("opened",{subMenu:!1})},i.prototype.close=function(e){e||this.eventController("closing",{subMenu:!1}),this.element.removeClass("opened"),this.options.htmlClass&&l("html").removeClass("zeynep-opened"),e||this.eventController("closed",{subMenu:!1})},i.prototype.destroy=function(){this.eventController("destroying"),this.close(!0),this.element.find(".submenu.opened").removeClass("opened"),this.element.removeData(s),this.eventController("destroyed"),this.options=n,this.options.htmlClass&&l("html").removeClass("zeynep-initialized"),delete this.element,delete this.options,delete this.eventController},i.prototype.on=function(e,t){r.call(this,e,t)};var o=function(e,t){if(this.options[e]){if("function"!=typeof this.options[e])throw Error("event handler must be a function: "+e);this.options[e].call(this,this.element,this.options,t)}},r=function(e,t){if("string"!=typeof e)throw Error("event name is expected to be a string but got: "+typeof e);if("function"!=typeof t)throw Error("event handler is not a function for: "+e);this.options[e]=t};l.fn[s]=function(e){var t,n,o;return t=l(this[0]),n=e,o=null,t.data(s)?o=t.data(s):(o=new i(t,n||{}),t.data(s,o)),o}}(window.jQuery,"zeynep"); +//# sourceMappingURL=zeynep.min.js.map diff --git a/admin/survey/script/narocila.js b/admin/survey/script/narocila.js index 103c8c253..115f077d7 100644 --- a/admin/survey/script/narocila.js +++ b/admin/survey/script/narocila.js @@ -54,7 +54,7 @@ function popupUserAccess_close() { function prepareNarocilaTableAdmin(){ $("#user_narocila").DataTable({ - order: [[ 4, "desc" ]], + order: [[ 5, "desc" ]], lengthMenu: [[50, 500, 1000], [50, 500, 1000]], select: false, lengthChange: true, @@ -63,8 +63,9 @@ function prepareNarocilaTableAdmin(){ responsive: true, columnDefs: [ {responsivePriority: 1, targets: 0}, - {responsivePriority: 2, targets: 7}, - {responsivePriority: 3, targets: 4} + {responsivePriority: 2, targets: 1}, + {responsivePriority: 3, targets: 8}, + {responsivePriority: 4, targets: 5} ], language: { "url": siteUrl+"admin/survey/script/datatables/Slovenian.json" diff --git a/admin/survey/script/onload.js b/admin/survey/script/onload.js index 78967b9c0..4cdd407d0 100644 --- a/admin/survey/script/onload.js +++ b/admin/survey/script/onload.js @@ -9,13 +9,11 @@ $(function() { - //console.time('onload'); - load_meta_variables(); // script.js ajax_start_stop(); // script.js onload_init(); // script.js IE7_select_disabled_fix(); // script.js - inline_jezik_hover(); // script.js + inline_jezik_hover(); // script.js onload_init_branching(); // branching.js onload_init_inline(); // vprasanjeInline.js @@ -25,9 +23,6 @@ $(function() { analiza_init(); // script_analiza.js statistika_init(); // statistika.js - //console.timeEnd('onload'); - - //browser_alert(); missingProfiles_init(); // missingProfiles.js missingValues_init(); // missingValues.js @@ -39,17 +34,12 @@ $(function() { multiCrosstabs_init(); // multicrosstabi (analiza) slideshow_init(); // prezendatcije (slideshow) dataSetingProfile_init(); // nastavitve v analizah in podatkov - invitations_init(); // nastavitve v analizah in podatkov - //themes_init(); // nastavitve v temah + invitations_init(); // nastavitve v analizah in podatkov inspect_init(); // nastavitve v inspect means_init(); // nastavitve v meansih ttest_init(); // nastavitve v ttest simpleMailInvitation_init();// nastavitve v simpleMailInvitation charts_init(); // nastavitve v charts creport_init(); // nastavitve v creport - onload_init_recode(); // nastavitve v recodiranju(function($) { - //onload_init_language_technology(); // nastavitve v language technology{ - - // prestejemo stevilo DOM elementov - //alert(document.getElementsByTagName("*").length); + onload_init_recode(); // nastavitve v recodiranju(function($) { }); diff --git a/admin/survey/script/script.js b/admin/survey/script/script.js index 1aea2fd86..a97393e80 100644 --- a/admin/survey/script/script.js +++ b/admin/survey/script/script.js @@ -5468,3 +5468,10 @@ function consultingPopupClose(){ $('#popup_note').fadeOut('slow').html(''); $('#fade').fadeOut('slow'); } + + +// Brisanje datoteke iz podatkov +function removeUploadFromData(usr_id, spr_id, code){ + + $("#fullscreen").load('ajax.php?t=postprocess&a=edit_data_question_upload_delete', {anketa: srv_meta_anketa_id, usr_id: usr_id, spr_id: spr_id, code: code}); +} diff --git a/admin/survey/script/surveyList.js b/admin/survey/script/surveyList.js index f7509364f..5114042ff 100644 --- a/admin/survey/script/surveyList.js +++ b/admin/survey/script/surveyList.js @@ -70,8 +70,12 @@ function delete_folder (folder) { // Ustvarimo folder function create_folder (parent) { - $.post('ajax.php?t=surveyList&a=folder_create', {parent: parent}, function(){ - window.location.reload(); + + $('#survey_list').load('ajax.php?t=surveyList&a=folder_create', {parent: parent}, function(){ + + var added_folder_id = $('#new_added_folder').val(); + + edit_title_folder(added_folder_id); }); } diff --git a/admin/survey/script/uporabniki_custom.js b/admin/survey/script/uporabniki_custom.js index 787740bea..742809e36 100644 --- a/admin/survey/script/uporabniki_custom.js +++ b/admin/survey/script/uporabniki_custom.js @@ -1,10 +1,15 @@ //Uporabimo custom funkcijo pri uporabnikih, dokler se ne posodobi jQuery $(document).ready(function () { - $('#xtradiv strong').on("click", function (event) { - $('#xtradivSettings').toggle(); - }); + + onload_init(); + + $('#xtradiv strong').on("click", function (event) { + $('#xtradivSettings').toggle(); + }); + }); + function language_change (lang) { $.post('ajax.php?t=surveyList&a=language_change', {lang: lang}, function () { /*window.location.reload();*/ diff --git a/admin/survey/script/vprasanje.js b/admin/survey/script/vprasanje.js index 69b44c93d..414e49cc8 100644 --- a/admin/survey/script/vprasanje.js +++ b/admin/survey/script/vprasanje.js @@ -154,7 +154,7 @@ function vprasanje_tab (spremenljivka, tab) { } // shrani nastavitve vprasanja -function vprasanje_save (silentsave, spr) { +function vprasanje_save (silentsave, spr, callback) { var spremenljivka = $('input[name=spremenljivka]').val() || spr; // spr se prenese, ce je urejanje na desni zaprto @@ -213,6 +213,9 @@ function vprasanje_save (silentsave, spr) { inline_nova_vrednost(last); } + if (typeof callback == 'function') { + callback(); + } }); } @@ -270,6 +273,17 @@ function vrednost_new (spremenljivka, other, tip, mv) { } +// doda novo vrednost na mobitelu +function vrednost_new_mobile (spremenljivka, tip) { + + $.post('ajax.php?t=vprasanje&a=vrednost_new', {spremenljivka: spremenljivka, other: 0, anketa: srv_meta_anketa_id, mv:0}, function (data) { + + vprasanje_save(true, spremenljivka, function(){ + $('#spremenljivka_contentdiv'+spremenljivka).find('#variable_holder div:last-child').find('.vrednost_inline').focus(); + }); + }); +} + //preverimo ce ze obstaja function vrednost_new_dodatne (spremenljivka, mv, tip, checked){ var vrednost; @@ -281,18 +295,8 @@ function vrednost_new_dodatne (spremenljivka, mv, tip, checked){ var vre_id = $('#spremenljivka_content_'+spremenljivka+' #variable_holder [other|="'+mv+'"]').attr('id'); vre_id = vre_id.replace('variabla_', ''); inline_vrednost_delete(spremenljivka, vre_id, '0'); + return false; - /* - temp = document.getElementById(mv).name; - var arr = new Array(); - arr = temp.split('_'); - vrednost = arr[2]; - - $('#vrednost_'+vrednost).remove(); - $.post('ajax.php?t=vprasanje&a=vrednost_delete', {vrednost: vrednost, spremenljivka: spremenljivka, anketa: srv_meta_anketa_id}, function () { - vprasanje_save(true); - }) - */ } } diff --git a/composer.lock b/composer.lock index 0a856a85c..6d41f275d 100644 --- a/composer.lock +++ b/composer.lock @@ -1,9 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], + "hash": "c9fc655cdb58743c38114c03e68c1cc7", "content-hash": "a150435d7f20bef4fdda8cb6c59eb1a6", "packages": [ { @@ -61,20 +62,20 @@ "ssl", "tls" ], - "time": "2021-01-12T12:10:35+00:00" + "time": "2021-01-12 12:10:35" }, { "name": "fgrosse/phpasn1", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "d1978f7abd580f3fc33561e7f71d4c12c7531fad" + "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/d1978f7abd580f3fc33561e7f71d4c12c7531fad", - "reference": "d1978f7abd580f3fc33561e7f71d4c12c7531fad", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/20299033c35f4300eb656e7e8e88cf52d1d6694e", + "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e", "shasum": "" }, "require": { @@ -132,31 +133,31 @@ "x509", "x690" ], - "time": "2020-10-11T16:28:18+00:00" + "time": "2021-04-24 19:01:55" }, { "name": "geoip2/geoip2", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/maxmind/GeoIP2-php.git", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a" + "reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/d01be5894a5c1a3381c58c9b1795cd07f96c30f7", + "reference": "d01be5894a5c1a3381c58c9b1795cd07f96c30f7", "shasum": "" }, "require": { "ext-json": "*", - "maxmind-db/reader": "~1.5", - "maxmind/web-service-common": "~0.6", - "php": ">=5.6" + "maxmind-db/reader": "~1.8", + "maxmind/web-service-common": "~0.8", + "php": ">=7.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "5.*", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "3.*" }, "type": "library", @@ -185,7 +186,7 @@ "geolocation", "maxmind" ], - "time": "2019-12-12T18:48:39+00:00" + "time": "2020-10-01 18:48:34" }, { "name": "guzzlehttp/guzzle", @@ -252,7 +253,7 @@ "rest", "web service" ], - "time": "2020-06-16T21:01:06+00:00" + "time": "2020-06-16 21:01:06" }, { "name": "guzzlehttp/promises", @@ -303,7 +304,7 @@ "keywords": [ "promise" ], - "time": "2021-03-07T09:25:29+00:00" + "time": "2021-03-07 09:25:29" }, { "name": "guzzlehttp/psr7", @@ -374,33 +375,34 @@ "uri", "url" ], - "time": "2021-04-26T09:17:50+00:00" + "time": "2021-04-26 09:17:50" }, { "name": "maxmind-db/reader", - "version": "v1.6.0", + "version": "v1.10.1", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4" + "reference": "569bd44d97d30a4ec12c7793a33004a76d4caf18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4", - "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/569bd44d97d30a4ec12c7793a33004a76d4caf18", + "reference": "569bd44d97d30a4ec12c7793a33004a76d4caf18", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.6.0,>=2.0.0" + "ext-maxminddb": "<1.10.1,>=2.0.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "2.*", + "friendsofphp/php-cs-fixer": "*", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpcov": "^3.0", - "phpunit/phpunit": "5.*", + "phpstan/phpstan": "*", + "phpunit/phpcov": ">=6.0.0", + "phpunit/phpunit": ">=8.0.0,<10.0.0", "squizlabs/php_codesniffer": "3.*" }, "suggest": { @@ -434,31 +436,31 @@ "geolocation", "maxmind" ], - "time": "2019-12-19T22:59:03+00:00" + "time": "2021-04-14 17:49:35" }, { "name": "maxmind/web-service-common", - "version": "v0.7.0", + "version": "v0.8.1", "source": { "type": "git", "url": "https://github.com/maxmind/web-service-common-php.git", - "reference": "74c996c218ada5c639c8c2f076756e059f5552fc" + "reference": "32f274051c543fc865e5a84d3a2c703913641ea8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/74c996c218ada5c639c8c2f076756e059f5552fc", - "reference": "74c996c218ada5c639c8c2f076756e059f5552fc", + "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/32f274051c543fc865e5a84d3a2c703913641ea8", + "reference": "32f274051c543fc865e5a84d3a2c703913641ea8", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0.3", "ext-curl": "*", "ext-json": "*", - "php": ">=5.6" + "php": ">=7.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "3.*" }, "type": "library", @@ -480,7 +482,7 @@ ], "description": "Internal MaxMind Web Service API", "homepage": "https://github.com/maxmind/web-service-common-php", - "time": "2020-05-06T14:07:26+00:00" + "time": "2020-11-02 17:00:53" }, { "name": "minishlink/web-push", @@ -536,7 +538,7 @@ "push", "web" ], - "time": "2020-08-02T08:58:01+00:00" + "time": "2020-08-02 08:58:01" }, { "name": "paragonie/random_compat", @@ -581,7 +583,7 @@ "pseudorandom", "random" ], - "time": "2020-10-15T08:29:30+00:00" + "time": "2020-10-15 08:29:30" }, { "name": "paragonie/sodium_compat", @@ -663,7 +665,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2021-04-17T09:00:05+00:00" + "time": "2021-04-17 09:00:05" }, { "name": "paypal/paypal-checkout-sdk", @@ -712,7 +714,7 @@ "rest", "sdk" ], - "time": "2019-11-07T23:16:44+00:00" + "time": "2019-11-07 23:16:44" }, { "name": "paypal/paypalhttp", @@ -751,7 +753,7 @@ "homepage": "https://github.com/paypal/paypalhttp_php/contributors" } ], - "time": "2019-11-06T21:27:12+00:00" + "time": "2019-11-06 21:27:12" }, { "name": "phpmailer/phpmailer", @@ -817,13 +819,7 @@ } ], "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ], - "time": "2021-04-29T12:25:04+00:00" + "time": "2021-04-29 12:25:04" }, { "name": "psr/http-message", @@ -873,7 +869,7 @@ "request", "response" ], - "time": "2016-08-06T14:39:51+00:00" + "time": "2016-08-06 14:39:51" }, { "name": "ralouphie/getallheaders", @@ -913,7 +909,7 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" + "time": "2019-03-08 08:55:37" }, { "name": "sonata-project/google-authenticator", @@ -970,7 +966,7 @@ "keywords": [ "google authenticator" ], - "time": "2018-07-18T22:08:02+00:00" + "time": "2018-07-18 22:08:02" }, { "name": "spomky-labs/base64url", @@ -1021,7 +1017,7 @@ "safe", "url" ], - "time": "2020-11-03T09:10:25+00:00" + "time": "2020-11-03 09:10:25" }, { "name": "stripe/stripe-php", @@ -1078,7 +1074,7 @@ "payment processing", "stripe" ], - "time": "2021-04-12T17:19:16+00:00" + "time": "2021-04-12 17:19:16" }, { "name": "symfony/polyfill-intl-idn", @@ -1148,7 +1144,7 @@ "portable", "shim" ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-01-22 09:19:47" }, { "name": "symfony/polyfill-intl-normalizer", @@ -1215,7 +1211,7 @@ "portable", "shim" ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-01-22 09:19:47" }, { "name": "symfony/polyfill-php72", @@ -1274,7 +1270,7 @@ "portable", "shim" ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-07 16:49:33" }, { "name": "web-token/jwt-core", @@ -1349,7 +1345,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-key-mgmt", @@ -1426,7 +1422,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature", @@ -1506,7 +1502,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature-algorithm-ecdsa", @@ -1573,7 +1569,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature-algorithm-eddsa", @@ -1640,7 +1636,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature-algorithm-hmac", @@ -1707,7 +1703,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature-algorithm-none", @@ -1774,7 +1770,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-signature-algorithm-rsa", @@ -1841,7 +1837,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" }, { "name": "web-token/jwt-util-ecc", @@ -1910,7 +1906,7 @@ "jwt", "symfony" ], - "time": "2020-03-20T13:29:04+00:00" + "time": "2020-03-20 13:29:04" } ], "packages-dev": [ @@ -1973,13 +1969,7 @@ "throwable", "whoops" ], - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2021-04-25T12:00:00+00:00" + "time": "2021-04-25 12:00:00" }, { "name": "kint-php/kint", @@ -2030,7 +2020,7 @@ "kint", "php" ], - "time": "2017-01-15T14:23:43+00:00" + "time": "2017-01-15 14:23:43" }, { "name": "maximebf/debugbar", @@ -2091,20 +2081,20 @@ "debug", "debugbar" ], - "time": "2020-12-07T11:07:24+00:00" + "time": "2020-12-07 11:07:24" }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -2128,7 +2118,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -2138,7 +2128,7 @@ "psr", "psr-3" ], - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03 11:20:27" }, { "name": "symfony/polyfill-mbstring", @@ -2201,21 +2191,7 @@ "portable", "shim" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-01-22 09:19:47" }, { "name": "symfony/polyfill-php80", @@ -2281,21 +2257,7 @@ "portable", "shim" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-07 16:49:33" }, { "name": "symfony/var-dumper", @@ -2367,21 +2329,7 @@ "debug", "dump" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-04-19T13:36:17+00:00" + "time": "2021-04-19 13:36:17" } ], "aliases": [], @@ -2392,6 +2340,5 @@ "platform": { "php": "^5.5|^7.0" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": [] } diff --git a/editors/ckeditor_4_4/uploader/EnkaUploader.php b/editors/ckeditor_4_4/uploader/EnkaUploader.php index 685096081..da7caebd6 100644 --- a/editors/ckeditor_4_4/uploader/EnkaUploader.php +++ b/editors/ckeditor_4_4/uploader/EnkaUploader.php @@ -7,7 +7,7 @@ + action="editors/ckeditor_4_4/uploader/EnkaUploader.php" style="height: 15px;"> @@ -41,7 +41,7 @@ if (!isset ($_POST['posted']) && (isset ($_GET['image']) && $_GET['image'] == 1) ?> + action="editors/ckeditor_4_4/uploader/EnkaUploader.php" style="height: 15px;"> @@ -64,7 +64,7 @@ if (!isset ($_POST['posted']) && (isset ($_GET['image']) && $_GET['image'] == 1) $final = $nakljucno .$ime; if (move_uploaded_file($_FILES['editorDatoteka']['tmp_name'], $site_path .'uploadi/editor/doc/' .$final)) { ?> - uploadi/editor/doc/'; window.location.href='/editors/ckeditor_4_4/uploader/EnkaUploader.php'; "> + uploadi/editor/doc/'; window.location.href='editors/ckeditor_4_4/uploader/EnkaUploader.php'; "> - + File = $_FILES['eitorSlika']; - $UF_obj->SavePath = $_SERVER['DOCUMENT_ROOT'] . '/uploadi/editor'; + $UF_obj->SavePath = $site_path . '/uploadi/editor'; $UF_obj->NewName = $_FILES['eitorSlika']['name']; //Širina in višina slike nastavimo @@ -113,7 +113,7 @@ if (!isset ($_POST['posted']) && (isset ($_GET['image']) && $_GET['image'] == 1) $Error = $UF_obj->UploadFile(); if (empty($Error)) { ?> - + = 70300) { + setcookie($name, $value, $options); + } + else { + setcookie($name, $value, $options['expires'], $options['path'], $options['domain'], $options['secure'], $options['httponly']); + } +} + +/** + * Process the params for cookies. This emulates support for the SameSite + * attribute in earlier versions of PHP, and allows the value of that attribute + * to be overridden. + * + * @param $options + * An associative array which may have any of the keys expires, path, domain, + * secure, httponly, samesite. + * + * @return + * An associative array which may have any of the keys expires, path, domain, + * secure, httponly, and samesite. + */ +function _drupal_cookie_params($options) { + $options['samesite'] = _drupal_samesite_cookie($options); + if (\PHP_VERSION_ID < 70300) { + // Emulate SameSite support in older PHP versions. + if (!empty($options['samesite'])) { + // Ensure the SameSite attribute is only added once. + if (!preg_match('/SameSite=/i', $options['path'])) { + $options['path'] .= '; SameSite=' . $options['samesite']; + } + } + } + return $options; +} + +/** + * Determine the value for the samesite cookie attribute, in the following order + * of precedence: + * + * 1) A value explicitly passed to drupal_setcookie() + * 2) A value set in $conf['samesite_cookie_value'] + * 3) The setting from php ini + * 4) The default of None, or FALSE (no attribute) if the cookie is not Secure + * + * @param $options + * An associative array as passed to drupal_setcookie(). + * @return + * The value for the samesite cookie attribute. + */ +function _drupal_samesite_cookie($options) { + if (isset($options['samesite'])) { + return $options['samesite']; + } + $override = variable_get('samesite_cookie_value', NULL); + if ($override !== NULL) { + return $override; + } + $ini_options = session_get_cookie_params(); + if (isset($ini_options['samesite'])) { + return $ini_options['samesite']; + } + return empty($options['secure']) ? FALSE : 'None'; +} diff --git a/frontend/drupal/includes/common.inc b/frontend/drupal/includes/common.inc index 7b7955855..690c00478 100644 --- a/frontend/drupal/includes/common.inc +++ b/frontend/drupal/includes/common.inc @@ -1559,7 +1559,7 @@ function _filter_xss_split($m, $store = FALSE) { return '<'; } - if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|()$%', $string, $matches)) { + if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)\s*([^>]*)>?|()$%', $string, $matches)) { // Seriously malformed. return ''; } @@ -1618,7 +1618,13 @@ function _filter_xss_attributes($attr) { // Attribute name, href for instance. if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { $attrname = strtolower($match[1]); - $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); + $skip = ( + $attrname == 'style' || + substr($attrname, 0, 2) == 'on' || + substr($attrname, 0, 1) == '-' || + // Ignore long attributes to avoid unnecessary processing overhead. + strlen($attrname) > 96 + ); $working = $mode = 1; $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); } @@ -2329,6 +2335,7 @@ function url($path = NULL, array $options = array()) { } elseif (!empty($path) && !$options['alias']) { $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; + require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc'); $alias = drupal_get_path_alias($original_path, $language); if ($alias != $original_path) { // Strip leading slashes from internal path aliases to prevent them @@ -5166,6 +5173,8 @@ function drupal_build_js_cache($files) { $contents .= file_get_contents($path) . ";\n"; } } + // Remove JS source and source mapping urls or these may cause 404 errors. + $contents = preg_replace('/\/\/(#|@)\s(sourceURL|sourceMappingURL)=\s*(\S*?)\s*$/m', '', $contents); // Prefix filename to prevent blocking by firewalls which reject files // starting with "ad*". $filename = 'js_' . drupal_hash_base64($contents) . '.js'; diff --git a/frontend/drupal/includes/database/database.inc b/frontend/drupal/includes/database/database.inc index d4d2d8f02..61ac44f78 100644 --- a/frontend/drupal/includes/database/database.inc +++ b/frontend/drupal/includes/database/database.inc @@ -184,7 +184,7 @@ * * @see http://php.net/manual/book.pdo.php */ -abstract class DatabaseConnection extends PDO { +abstract class DatabaseConnection { /** * The database target this connection is for. @@ -261,6 +261,13 @@ abstract class DatabaseConnection extends PDO { */ protected $temporaryNameIndex = 0; + /** + * The actual PDO connection. + * + * @var \PDO + */ + protected $connection; + /** * The connection information for this connection object. * @@ -325,14 +332,27 @@ abstract class DatabaseConnection extends PDO { $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; // Call PDO::__construct and PDO::setAttribute. - parent::__construct($dsn, $username, $password, $driver_options); + $this->connection = new PDO($dsn, $username, $password, $driver_options); // Set a Statement class, unless the driver opted out. if (!empty($this->statementClass)) { - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); + $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); } } + /** + * Proxy possible direct calls to the \PDO methods. + * + * Since PHP8.0 the signature of the the \PDO::query() method has changed, + * and this class can't extending \PDO any more. + * + * However, for the BC, proxy any calls to the \PDO methods to the actual + * PDO connection object. + */ + public function __call($name, $arguments) { + return call_user_func_array(array($this->connection, $name), $arguments); + } + /** * Destroys this Connection object. * @@ -346,7 +366,7 @@ abstract class DatabaseConnection extends PDO { // The Statement class attribute only accepts a new value that presents a // proper callable, so we reset it to PDOStatement. if (!empty($this->statementClass)) { - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); } $this->schema = NULL; } @@ -521,7 +541,7 @@ abstract class DatabaseConnection extends PDO { $query = $this->prefixTables($query); // Call PDO::prepare. - return parent::prepare($query); + return $this->connection->prepare($query); } /** @@ -733,7 +753,7 @@ abstract class DatabaseConnection extends PDO { case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: - return $this->lastInsertId(); + return $this->connection->lastInsertId(); case Database::RETURN_NULL: return; default: @@ -1116,7 +1136,7 @@ abstract class DatabaseConnection extends PDO { $rolled_back_other_active_savepoints = TRUE; } } - parent::rollBack(); + $this->connection->rollBack(); if ($rolled_back_other_active_savepoints) { throw new DatabaseTransactionOutOfOrderException(); } @@ -1144,7 +1164,7 @@ abstract class DatabaseConnection extends PDO { $this->query('SAVEPOINT ' . $name); } else { - parent::beginTransaction(); + $this->connection->beginTransaction(); } $this->transactionLayers[$name] = $name; } @@ -1195,7 +1215,7 @@ abstract class DatabaseConnection extends PDO { // If there are no more layers left then we should commit. unset($this->transactionLayers[$name]); if (empty($this->transactionLayers)) { - if (!parent::commit()) { + if (!$this->connection->commit()) { throw new DatabaseTransactionCommitFailedException(); } } @@ -1279,7 +1299,7 @@ abstract class DatabaseConnection extends PDO { * Returns the version of the database server. */ public function version() { - return $this->getAttribute(PDO::ATTR_SERVER_VERSION); + return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** @@ -1724,12 +1744,16 @@ abstract class Database { * * @param $key * The connection key. + * @param $close + * Whether to close the connection. * @return * TRUE in case of success, FALSE otherwise. */ - final public static function removeConnection($key) { + final public static function removeConnection($key, $close = TRUE) { if (isset(self::$databaseInfo[$key])) { - self::closeConnection(NULL, $key); + if ($close) { + self::closeConnection(NULL, $key); + } unset(self::$databaseInfo[$key]); return TRUE; } diff --git a/frontend/drupal/includes/database/mysql/database.inc b/frontend/drupal/includes/database/mysql/database.inc index 00df3c13e..b83611198 100644 --- a/frontend/drupal/includes/database/mysql/database.inc +++ b/frontend/drupal/includes/database/mysql/database.inc @@ -345,10 +345,10 @@ class DatabaseConnection_mysql extends DatabaseConnection { // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' // for UTF-8. if (!empty($connection_options['collation'])) { - $this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']); + $this->connection->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']); } else { - $this->exec('SET NAMES ' . $charset); + $this->connection->exec('SET NAMES ' . $charset); } // Set MySQL init_commands if not already defined. Default Drupal's MySQL @@ -366,7 +366,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { $sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO'; // NO_AUTO_CREATE_USER was removed in MySQL 8.0.11 // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal - if (version_compare($this->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '<')) { + if (version_compare($this->connection->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '<')) { $sql_mode .= ',NO_AUTO_CREATE_USER'; } $connection_options['init_commands'] += array( @@ -375,7 +375,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { // Execute initial commands. foreach ($connection_options['init_commands'] as $sql) { - $this->exec($sql); + $this->connection->exec($sql); } } @@ -536,7 +536,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { // If there are no more layers left then we should commit. unset($this->transactionLayers[$name]); if (empty($this->transactionLayers)) { - if (!PDO::commit()) { + if (!$this->doCommit()) { throw new DatabaseTransactionCommitFailedException(); } } @@ -559,7 +559,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->transactionLayers = array(); // We also have to explain to PDO that the transaction stack has // been cleaned-up. - PDO::commit(); + $this->doCommit(); } else { throw $e; @@ -569,6 +569,53 @@ class DatabaseConnection_mysql extends DatabaseConnection { } } + /** + * Do the actual commit, including a workaround for PHP 8 behaviour changes. + * + * @return bool + * Success or otherwise of the commit. + */ + protected function doCommit() { + if ($this->connection->inTransaction()) { + return $this->connection->commit(); + } + else { + // In PHP 8.0 a PDOException is thrown when a commit is attempted with no + // transaction active. In previous PHP versions this failed silently. + return TRUE; + } + } + + /** + * {@inheritdoc} + */ + public function rollback($savepoint_name = 'drupal_transaction') { + // MySQL will automatically commit transactions when tables are altered or + // created (DDL transactions are not supported). Prevent triggering an + // exception to ensure that the error that has caused the rollback is + // properly reported. + if (!$this->connection->inTransaction()) { + // Before PHP 8 $this->connection->inTransaction() will return TRUE and + // $this->connection->rollback() does not throw an exception; the + // following code is unreachable. + + // If \DatabaseConnection::rollback() would throw an + // exception then continue to throw an exception. + if (!$this->inTransaction()) { + throw new DatabaseTransactionNoActiveException(); + } + // A previous rollback to an earlier savepoint may mean that the savepoint + // in question has already been accidentally committed. + if (!isset($this->transactionLayers[$savepoint_name])) { + throw new DatabaseTransactionNoActiveException(); + } + + trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING); + return; + } + return parent::rollback($savepoint_name); + } + public function utf8mb4IsConfigurable() { return TRUE; } @@ -579,7 +626,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { public function utf8mb4IsSupported() { // Ensure that the MySQL driver supports utf8mb4 encoding. - $version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION); + $version = $this->connection->getAttribute(PDO::ATTR_CLIENT_VERSION); if (strpos($version, 'mysqlnd') !== FALSE) { // The mysqlnd driver supports utf8mb4 starting at version 5.0.9. $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version); diff --git a/frontend/drupal/includes/database/pgsql/database.inc b/frontend/drupal/includes/database/pgsql/database.inc index fb3d0ab51..96ffc1d3e 100644 --- a/frontend/drupal/includes/database/pgsql/database.inc +++ b/frontend/drupal/includes/database/pgsql/database.inc @@ -66,11 +66,11 @@ class DatabaseConnection_pgsql extends DatabaseConnection { parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); // Force PostgreSQL to use the UTF-8 character set by default. - $this->exec("SET NAMES 'UTF8'"); + $this->connection->exec("SET NAMES 'UTF8'"); // Execute PostgreSQL init_commands. if (isset($connection_options['init_commands'])) { - $this->exec(implode('; ', $connection_options['init_commands'])); + $this->connection->exec(implode('; ', $connection_options['init_commands'])); } } @@ -117,7 +117,7 @@ class DatabaseConnection_pgsql extends DatabaseConnection { case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: - return $this->lastInsertId($options['sequence_name']); + return $this->connection->lastInsertId($options['sequence_name']); case Database::RETURN_NULL: return; default: diff --git a/frontend/drupal/includes/database/select.inc b/frontend/drupal/includes/database/select.inc index 84098bdf7..674c6b53a 100644 --- a/frontend/drupal/includes/database/select.inc +++ b/frontend/drupal/includes/database/select.inc @@ -964,7 +964,7 @@ class SelectQuery extends Query implements SelectQueryInterface { */ protected $forUpdate = FALSE; - public function __construct($table, $alias = NULL, DatabaseConnection $connection, $options = array()) { + public function __construct($table, $alias, DatabaseConnection $connection, $options = array()) { $options['return'] = Database::RETURN_STATEMENT; parent::__construct($connection, $options); $this->where = new DatabaseCondition('AND'); diff --git a/frontend/drupal/includes/database/sqlite/database.inc b/frontend/drupal/includes/database/sqlite/database.inc index c50f08ec5..2cf83ccb5 100644 --- a/frontend/drupal/includes/database/sqlite/database.inc +++ b/frontend/drupal/includes/database/sqlite/database.inc @@ -121,7 +121,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { // Execute sqlite init_commands. if (isset($connection_options['init_commands'])) { - $this->exec(implode('; ', $connection_options['init_commands'])); + $this->connection->exec(implode('; ', $connection_options['init_commands'])); } } @@ -259,7 +259,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { * expose this function to the world. */ public function PDOPrepare($query, array $options = array()) { - return parent::prepare($query, $options); + return $this->connection->prepare($query, $options); } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { @@ -350,7 +350,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { } } if ($this->supportsTransactions()) { - PDO::rollBack(); + $this->connection->rollBack(); } } @@ -365,7 +365,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { throw new DatabaseTransactionNameNonUniqueException($name . " is already in use."); } if (!$this->inTransaction()) { - PDO::beginTransaction(); + $this->connection->beginTransaction(); } $this->transactionLayers[$name] = $name; } @@ -390,9 +390,9 @@ class DatabaseConnection_sqlite extends DatabaseConnection { // If there was any rollback() we should roll back whole transaction. if ($this->willRollback) { $this->willRollback = FALSE; - PDO::rollBack(); + $this->connection->rollBack(); } - elseif (!PDO::commit()) { + elseif (!$this->connection->commit()) { throw new DatabaseTransactionCommitFailedException(); } } diff --git a/frontend/drupal/includes/errors.inc b/frontend/drupal/includes/errors.inc index 3548d1fd8..4401ebe87 100644 --- a/frontend/drupal/includes/errors.inc +++ b/frontend/drupal/includes/errors.inc @@ -48,11 +48,8 @@ function drupal_error_levels() { * The filename that the error was raised in. * @param $line * The line number the error was raised at. - * @param $context - * An array that points to the active symbol table at the point the error - * occurred. */ -function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { +function _drupal_error_handler_real($error_level, $message, $filename, $line) { if ($error_level & error_reporting()) { $types = drupal_error_levels(); list($severity_msg, $severity_level) = $types[$error_level]; diff --git a/frontend/drupal/includes/menu.inc b/frontend/drupal/includes/menu.inc index 22e6dba97..0f38d6f50 100644 --- a/frontend/drupal/includes/menu.inc +++ b/frontend/drupal/includes/menu.inc @@ -317,7 +317,7 @@ define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91'); * actually exists. This list of 'masks' is built in menu_rebuild(). * * @param $parts - * An array of path parts; for the above example, + * An array of path parts; for the above example, * array('node', '12345', 'edit'). * * @return @@ -2595,7 +2595,7 @@ function menu_get_active_breadcrumb() { // Don't show a link to the current page in the breadcrumb trail. $end = end($active_trail); - if ($item['href'] == $end['href']) { + if (is_array($end) && $item['href'] == $end['href']) { array_pop($active_trail); } diff --git a/frontend/drupal/includes/session.inc b/frontend/drupal/includes/session.inc index 11c77c4c1..a4ce54b7d 100644 --- a/frontend/drupal/includes/session.inc +++ b/frontend/drupal/includes/session.inc @@ -284,6 +284,20 @@ function drupal_session_start() { // Save current session data before starting it, as PHP will destroy it. $session_data = isset($_SESSION) ? $_SESSION : NULL; + // Apply any overrides to the session cookie params. + $params = $original_params = session_get_cookie_params(); + // PHP settings for samesite will be handled by _drupal_cookie_params(). + unset($params['samesite']); + $params = _drupal_cookie_params($params); + if ($params !== $original_params) { + if (\PHP_VERSION_ID >= 70300) { + session_set_cookie_params($params); + } + else { + session_set_cookie_params($params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']); + } + } + session_start(); drupal_session_started(TRUE); @@ -323,7 +337,14 @@ function drupal_session_commit() { $insecure_session_name = substr(session_name(), 1); $params = session_get_cookie_params(); $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie($insecure_session_name, $_COOKIE[$insecure_session_name], $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); + $options = array( + 'expires' => $expire, + 'path' => $params['path'], + 'domain' => $params['domain'], + 'secure' => FALSE, + 'httponly' => $params['httponly'], + ); + drupal_setcookie($insecure_session_name, $_COOKIE[$insecure_session_name], $options); } } // Write the session data. @@ -365,7 +386,14 @@ function drupal_session_regenerate() { // $params['lifetime'] seconds from the current request. If it is not set, // it will expire when the browser is closed. $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); + $options = array( + 'expires' => $expire, + 'path' => $params['path'], + 'domain' => $params['domain'], + 'secure' => FALSE, + 'httponly' => $params['httponly'], + ); + drupal_setcookie($insecure_session_name, $session_id, $options); $_COOKIE[$insecure_session_name] = $session_id; } @@ -380,7 +408,14 @@ function drupal_session_regenerate() { if (isset($old_session_id)) { $params = session_get_cookie_params(); $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + $options = array( + 'expires' => $expire, + 'path' => $params['path'], + 'domain' => $params['domain'], + 'secure' => $params['secure'], + 'httponly' => $params['httponly'], + ); + drupal_setcookie(session_name(), session_id(), $options); $fields = array('sid' => session_id()); if ($is_https) { $fields['ssid'] = session_id(); @@ -488,7 +523,14 @@ function _drupal_session_delete_cookie($name, $secure = NULL) { if ($secure !== NULL) { $params['secure'] = $secure; } - setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + $options = array( + 'expires' => REQUEST_TIME - 3600, + 'path' => $params['path'], + 'domain' => $params['domain'], + 'secure' => $params['secure'], + 'httponly' => $params['httponly'], + ); + drupal_setcookie($name, '', $options); unset($_COOKIE[$name]); } } diff --git a/frontend/drupal/misc/ajax.js b/frontend/drupal/misc/ajax.js index 79a4e9eb6..a809f5738 100644 --- a/frontend/drupal/misc/ajax.js +++ b/frontend/drupal/misc/ajax.js @@ -408,7 +408,7 @@ Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) { // Insert progressbar or throbber. if (this.progress.type == 'bar') { - var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); + var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop); if (this.progress.message) { progressBar.setProgress(-1, this.progress.message); } diff --git a/frontend/drupal/modules/aggregator/aggregator.info b/frontend/drupal/modules/aggregator/aggregator.info index 9fe93e0c4..dac556160 100644 --- a/frontend/drupal/modules/aggregator/aggregator.info +++ b/frontend/drupal/modules/aggregator/aggregator.info @@ -7,7 +7,7 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/aggregator/tests/aggregator_test.info b/frontend/drupal/modules/aggregator/tests/aggregator_test.info index 1fc2daa31..840f68f53 100644 --- a/frontend/drupal/modules/aggregator/tests/aggregator_test.info +++ b/frontend/drupal/modules/aggregator/tests/aggregator_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/block/block.info b/frontend/drupal/modules/block/block.info index 484a4a0f2..95907c03b 100644 --- a/frontend/drupal/modules/block/block.info +++ b/frontend/drupal/modules/block/block.info @@ -6,7 +6,7 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/block/tests/block_test.info b/frontend/drupal/modules/block/tests/block_test.info index 5b1b0d2d6..1a0b512fb 100644 --- a/frontend/drupal/modules/block/tests/block_test.info +++ b/frontend/drupal/modules/block/tests/block_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/block/tests/themes/block_test_theme/block_test_theme.info b/frontend/drupal/modules/block/tests/themes/block_test_theme/block_test_theme.info index a10950ad4..87a8d6262 100644 --- a/frontend/drupal/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/frontend/drupal/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,7 +13,7 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/blog/blog.info b/frontend/drupal/modules/blog/blog.info index 9f626f82a..80ca6e702 100644 --- a/frontend/drupal/modules/blog/blog.info +++ b/frontend/drupal/modules/blog/blog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/book/book.info b/frontend/drupal/modules/book/book.info index 3a611bc4f..5f9d340aa 100644 --- a/frontend/drupal/modules/book/book.info +++ b/frontend/drupal/modules/book/book.info @@ -7,7 +7,7 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/book/book.test b/frontend/drupal/modules/book/book.test index 81f4524ac..448dc23a9 100644 --- a/frontend/drupal/modules/book/book.test +++ b/frontend/drupal/modules/book/book.test @@ -101,7 +101,7 @@ class BookTestCase extends DrupalWebTestCase { // Check that book pages display along with the correct outlines and // previous/next links. - $this->checkBookNode($book, array($nodes[0], $nodes[3], $nodes[4]), FALSE, FALSE, $nodes[0], array()); + $this->checkBookNode($book, array($nodes[0], $nodes[3], $nodes[4]), FALSE, FALSE, $nodes[0]); $this->checkBookNode($nodes[0], array($nodes[1], $nodes[2]), $book, $book, $nodes[1], array($book)); $this->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2], array($book, $nodes[0])); $this->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3], array($book, $nodes[0])); @@ -124,7 +124,7 @@ class BookTestCase extends DrupalWebTestCase { // First we must set $this->book to the second book, so that the // correct regex will be generated for testing the outline. $this->book = $other_book; - $this->checkBookNode($other_book, array($node), FALSE, FALSE, $node, array()); + $this->checkBookNode($other_book, array($node), FALSE, FALSE, $node); $this->checkBookNode($node, NULL, $other_book, $other_book, FALSE, array($other_book)); } @@ -144,9 +144,9 @@ class BookTestCase extends DrupalWebTestCase { * @param $next * (optional) Next link node. Defaults to FALSE. * @param $breadcrumb - * The nodes that should be displayed in the breadcrumb. + * (optional) The nodes that should be displayed in the breadcrumb. */ - function checkBookNode($node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) { + function checkBookNode($node, $nodes = NULL, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb = array()) { // $number does not use drupal_static as it should not be reset // since it uniquely identifies each call to checkBookNode(). static $number = 0; diff --git a/frontend/drupal/modules/color/color.info b/frontend/drupal/modules/color/color.info index 23be4ace9..3906b758f 100644 --- a/frontend/drupal/modules/color/color.info +++ b/frontend/drupal/modules/color/color.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/comment/comment.info b/frontend/drupal/modules/comment/comment.info index 68c6c2393..e7fcca1dc 100644 --- a/frontend/drupal/modules/comment/comment.info +++ b/frontend/drupal/modules/comment/comment.info @@ -9,7 +9,7 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/contact/contact.info b/frontend/drupal/modules/contact/contact.info index b6d67dd50..d12f98cb1 100644 --- a/frontend/drupal/modules/contact/contact.info +++ b/frontend/drupal/modules/contact/contact.info @@ -6,7 +6,7 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/contextual/contextual.info b/frontend/drupal/modules/contextual/contextual.info index c881710cf..165551cba 100644 --- a/frontend/drupal/modules/contextual/contextual.info +++ b/frontend/drupal/modules/contextual/contextual.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/dashboard/dashboard.info b/frontend/drupal/modules/dashboard/dashboard.info index f4c2db68a..fd30f8401 100644 --- a/frontend/drupal/modules/dashboard/dashboard.info +++ b/frontend/drupal/modules/dashboard/dashboard.info @@ -7,7 +7,7 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/dblog/dblog.info b/frontend/drupal/modules/dblog/dblog.info index da068eb78..5ca79fca0 100644 --- a/frontend/drupal/modules/dblog/dblog.info +++ b/frontend/drupal/modules/dblog/dblog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/field.info b/frontend/drupal/modules/field/field.info index c8c4585e7..48bd97047 100644 --- a/frontend/drupal/modules/field/field.info +++ b/frontend/drupal/modules/field/field.info @@ -11,7 +11,7 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.info b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.info index ed407fe88..9ce8bf4e9 100644 --- a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module index 842893ad7..deb08d0da 100644 --- a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -434,6 +434,81 @@ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fi } } +/** + * Callback for array_filter(). + */ +function _field_sql_storage_write_compare_filter_callback($value) { + return NULL !== $value && '' !== $value; +} + +/** + * Cleanup field values for later values comparison. + * + * @param array $field + * Field info as returned by field_info_field_by_id(). + * + * @param array $array + * Field values to cleanup. + * + * @return array + * Filtered values. + */ +function _field_sql_storage_write_compare_filter($field, $array) { + foreach ($array as $language => $items) { + if (empty($items)) { + unset($array[$language]); + } + else { + foreach ($items as $delta => $item) { + // This should not happen but some modules provide invalid data to the + // field API. + if (!is_array($item)) { + continue; + } + // Let's start by pruning empty values and non storable values. + $array[$language][$delta] = array_filter(array_intersect_key($item, $field['columns']), '_field_sql_storage_write_compare_filter_callback'); + // Ordering is important because for widget elements and loaded columns + // from database order might differ and give false positives on field + // value change, especially with complex fields such as image fields. + ksort($array[$language][$delta]); + } + } + } + return $array; +} + +/** + * Compare a single field value for both entities and tell us if it changed. + * + * @param array $field + * Loaded field structure. + * @param object $entity1 + * First entity to compare. + * @param object $entity2 + * Second entity to compare. + * + * @return bool + * True if field value changed, false otherwise. + */ +function _field_sql_storage_write_compare($field, $entity1, $entity2) { + $field_name = $field['field_name']; + if (empty($entity1->$field_name) && empty($entity2->$field_name)) { + // Both are empty we can safely assume that it did not change. + return FALSE; + } + if (!isset($entity1->$field_name) || !isset($entity2->$field_name)) { + // One of them is missing but not the other the value changed. + return TRUE; + } + // We need to proceed to deep array comparison, but we cannot do it naively: + // in most cases the field values come from the edit form, and some Form API + // widget values that are not field columns may be present. We need to clean + // up both original and new field values before comparison. + $items1 = _field_sql_storage_write_compare_filter($field, (array) $entity1->$field_name); + $items2 = _field_sql_storage_write_compare_filter($field, (array) $entity2->$field_name); + return $items1 != $items2; +} + /** * Implements hook_field_storage_write(). */ @@ -443,8 +518,29 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel $vid = $id; } + // Check if the given entity is a new revision or not. In case of a new + // revision creation, we cannot skip any field. + if (!empty($vid) && !empty($entity->original)) { + list(, $original_vid) = entity_extract_ids($entity_type, $entity->original); + if (NULL === $original_vid) { + $original_vid = $id; + } + $is_new_revision = $original_vid != $vid; + } + else { + $is_new_revision = FALSE; + } + + // Allow this optimization to be optional. + $skip_unchanged_fields = variable_get('field_sql_storage_skip_writing_unchanged_fields', FALSE); + foreach ($fields as $field_id) { $field = field_info_field_by_id($field_id); + + if ($skip_unchanged_fields && !$is_new_revision && !empty($entity->original) && !_field_sql_storage_write_compare($field, $entity, $entity->original)) { + continue; + } + $field_name = $field['field_name']; $table_name = _field_sql_storage_tablename($field); $revision_name = _field_sql_storage_revision_tablename($field); diff --git a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test index b2eb50652..e46677be9 100644 --- a/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/frontend/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -281,6 +281,69 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { $this->assertEqual($count, 1, 'NULL field translation is wiped.'); } + /** + * Tests the expected return values of _field_sql_storage_write_compare(). + */ + public function testFieldCompareDataModification() { + $langcode = LANGUAGE_NONE; + $field_info = field_info_field($this->field_name); + + // Make sure we have 2 sample field values that are unique. + $value1 = 0; + $value2 = 0; + while ($value1 == $value2) { + $value1 = mt_rand(); + $value2 = (string) mt_rand(); + } + + // Create the 2 entities to compare. + $entity = field_test_create_stub_entity(); + $entity->{$this->field_name}[$langcode][]['value'] = $value1; + $entity1 = clone $entity; + $entity2 = clone $entity; + + // Make sure that it correctly compares identical entities. + $this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The entities are identical.'); + + // Compare to an empty object. + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, new stdClass()), 'The entity is not the same as an empty object.'); + + // Change one of the values. + $entity2->{$this->field_name}[$langcode][0]['value'] = $value2; + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The values are not the same.'); + + // Reset $entity2. + $entity2 = clone $entity; + + // Duplicate the value on one of the entities. + $entity1->{$this->field_name}[$langcode][]['value'] = $value1; + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The fields do not have the same number of values.'); + + // Add a second value to both entities. + $entity2->{$this->field_name}[$langcode][]['value'] = $value2; + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The values are not the same.'); + + // Replace the array containing the value with the actual value. + $entity2->{$this->field_name}[$langcode] = $entity2->{$this->field_name}[$langcode][0]; + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'The array to hold field values is replaced by the value.'); + + // Null one value. + $entity2->{$this->field_name}[$langcode] = NULL; + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'One field is NULL and the other is not.'); + + // Null both values. + $entity1->{$this->field_name}[$langcode] = NULL; + $this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'Both fields are NULL.'); + + // Unset one of the fields. + unset($entity2->{$this->field_name}); + $this->assert(_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'One field structure is unset.'); + + // Unset both of the fields. + unset($entity1->{$this->field_name}); + $this->assert(!_field_sql_storage_write_compare($field_info, $entity1, $entity2), 'Both field structures are unset.'); + } + /** * Test trying to update a field with data. */ diff --git a/frontend/drupal/modules/field/modules/list/list.info b/frontend/drupal/modules/field/modules/list/list.info index bc867c0ba..92bbf7233 100644 --- a/frontend/drupal/modules/field/modules/list/list.info +++ b/frontend/drupal/modules/field/modules/list/list.info @@ -7,7 +7,7 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/list/tests/list_test.info b/frontend/drupal/modules/field/modules/list/tests/list_test.info index f344a39f9..5854cb3a1 100644 --- a/frontend/drupal/modules/field/modules/list/tests/list_test.info +++ b/frontend/drupal/modules/field/modules/list/tests/list_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/number/number.info b/frontend/drupal/modules/field/modules/number/number.info index 545ccb105..54ea521ec 100644 --- a/frontend/drupal/modules/field/modules/number/number.info +++ b/frontend/drupal/modules/field/modules/number/number.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/options/options.info b/frontend/drupal/modules/field/modules/options/options.info index 8e42a49a4..d3e47aa9e 100644 --- a/frontend/drupal/modules/field/modules/options/options.info +++ b/frontend/drupal/modules/field/modules/options/options.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/modules/text/text.info b/frontend/drupal/modules/field/modules/text/text.info index a1145184b..3b7a36f0e 100644 --- a/frontend/drupal/modules/field/modules/text/text.info +++ b/frontend/drupal/modules/field/modules/text/text.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/tests/field_test.info b/frontend/drupal/modules/field/tests/field_test.info index 242fe55a5..324e12093 100644 --- a/frontend/drupal/modules/field/tests/field_test.info +++ b/frontend/drupal/modules/field/tests/field_test.info @@ -6,7 +6,7 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/field/tests/field_test.storage.inc b/frontend/drupal/modules/field/tests/field_test.storage.inc index 03eae4a6f..7a6201d31 100644 --- a/frontend/drupal/modules/field/tests/field_test.storage.inc +++ b/frontend/drupal/modules/field/tests/field_test.storage.inc @@ -240,111 +240,6 @@ function field_test_field_storage_delete_revision($entity_type, $entity, $fields _field_test_storage_data($data); } -/** - * Implements hook_field_storage_query(). - */ -function field_test_field_storage_query($field_id, $conditions, $count, &$cursor = NULL, $age) { - $data = _field_test_storage_data(); - - $load_current = $age == FIELD_LOAD_CURRENT; - - $field = field_info_field_by_id($field_id); - $field_columns = array_keys($field['columns']); - - $field_data = $data[$field['id']]; - $sub_table = $load_current ? 'current' : 'revisions'; - // We need to sort records by entity type and entity id. - usort($field_data[$sub_table], '_field_test_field_storage_query_sort_helper'); - - // Initialize results array. - $return = array(); - $entity_count = 0; - $rows_count = 0; - $rows_total = count($field_data[$sub_table]); - $skip = $cursor; - $skipped = 0; - - foreach ($field_data[$sub_table] as $row) { - if ($count != FIELD_QUERY_NO_LIMIT && $entity_count >= $count) { - break; - } - - if ($row->field_id == $field['id']) { - $match = TRUE; - $condition_deleted = FALSE; - // Add conditions. - foreach ($conditions as $condition) { - @list($column, $value, $operator) = $condition; - if (empty($operator)) { - $operator = is_array($value) ? 'IN' : '='; - } - switch ($operator) { - case '=': - $match = $match && $row->{$column} == $value; - break; - case '<>': - case '<': - case '<=': - case '>': - case '>=': - eval('$match = $match && ' . $row->{$column} . ' ' . $operator . ' '. $value); - break; - case 'IN': - $match = $match && in_array($row->{$column}, $value); - break; - case 'NOT IN': - $match = $match && !in_array($row->{$column}, $value); - break; - case 'BETWEEN': - $match = $match && $row->{$column} >= $value[0] && $row->{$column} <= $value[1]; - break; - case 'STARTS_WITH': - case 'ENDS_WITH': - case 'CONTAINS': - // Not supported. - $match = FALSE; - break; - } - // Track condition on 'deleted'. - if ($column == 'deleted') { - $condition_deleted = TRUE; - } - } - - // Exclude deleted data unless we have a condition on it. - if (!$condition_deleted && $row->deleted) { - $match = FALSE; - } - - if ($match) { - if (!isset($skip) || $skipped >= $skip) { - $cursor++; - // If querying all revisions and the entity type has revisions, we need - // to key the results by revision_ids. - $entity_type = entity_get_info($row->type); - $id = ($load_current || empty($entity_type['entity keys']['revision'])) ? $row->entity_id : $row->revision_id; - - if (!isset($return[$row->type][$id])) { - $return[$row->type][$id] = entity_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); - $entity_count++; - } - } - else { - $skipped++; - } - } - } - $rows_count++; - - // The query is complete if we walked the whole array. - if ($count != FIELD_QUERY_NO_LIMIT && $rows_count >= $rows_total) { - $cursor = FIELD_QUERY_COMPLETE; - } - } - - return $return; -} - /** * Sort helper for field_test_field_storage_query(). * diff --git a/frontend/drupal/modules/field_ui/field_ui.info b/frontend/drupal/modules/field_ui/field_ui.info index e66bd97a7..555d69283 100644 --- a/frontend/drupal/modules/field_ui/field_ui.info +++ b/frontend/drupal/modules/field_ui/field_ui.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = field_ui.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/file/file.field.inc b/frontend/drupal/modules/file/file.field.inc index fc1a1df20..ddb4f841f 100644 --- a/frontend/drupal/modules/file/file.field.inc +++ b/frontend/drupal/modules/file/file.field.inc @@ -593,7 +593,7 @@ function file_field_widget_uri($field, $instance, $data = array()) { /** * The #value_callback for the file_generic field element. */ -function file_field_widget_value($element, $input = FALSE, $form_state) { +function file_field_widget_value($element, $input = FALSE, $form_state = array()) { if ($input) { // Checkboxes lose their value when empty. // If the display field is present make sure its unchecked value is saved. @@ -955,17 +955,14 @@ function theme_file_upload_help($variables) { if (isset($upload_validators['file_validate_image_resolution'])) { $max = $upload_validators['file_validate_image_resolution'][0]; $min = $upload_validators['file_validate_image_resolution'][1]; - if ($min && $max && $min == $max) { - $descriptions[] = t('Images must be exactly !size pixels.', array('!size' => '' . $max . '')); - } - elseif ($min && $max) { - $descriptions[] = t('Images must be between !min and !max pixels.', array('!min' => '' . $min . '', '!max' => '' . $max . '')); + if ($min && $max) { + $descriptions[] = t('Images must be at least !min pixels. Images larger than !max pixels will be resized.', array('!min' => '' . $min . '', '!max' => '' . $max . '')); } elseif ($min) { - $descriptions[] = t('Images must be larger than !min pixels.', array('!min' => '' . $min . '')); + $descriptions[] = t('Images must be at least !min pixels.', array('!min' => '' . $min . '')); } elseif ($max) { - $descriptions[] = t('Images must be smaller than !max pixels.', array('!max' => '' . $max . '')); + $descriptions[] = t('Images larger than !max pixels will be resized.', array('!max' => '' . $max . '')); } } diff --git a/frontend/drupal/modules/file/file.info b/frontend/drupal/modules/file/file.info index fe0ae6dd2..92deebac9 100644 --- a/frontend/drupal/modules/file/file.info +++ b/frontend/drupal/modules/file/file.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = tests/file.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/file/tests/file_module_test.info b/frontend/drupal/modules/file/tests/file_module_test.info index 040561e7d..b134008d4 100644 --- a/frontend/drupal/modules/file/tests/file_module_test.info +++ b/frontend/drupal/modules/file/tests/file_module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/filter/filter.info b/frontend/drupal/modules/filter/filter.info index bcd85882c..2a71c6c3c 100644 --- a/frontend/drupal/modules/filter/filter.info +++ b/frontend/drupal/modules/filter/filter.info @@ -7,7 +7,7 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/forum/forum.info b/frontend/drupal/modules/forum/forum.info index 99253aa64..514e2557a 100644 --- a/frontend/drupal/modules/forum/forum.info +++ b/frontend/drupal/modules/forum/forum.info @@ -9,7 +9,7 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/help/help.info b/frontend/drupal/modules/help/help.info index c410c63ea..62d626758 100644 --- a/frontend/drupal/modules/help/help.info +++ b/frontend/drupal/modules/help/help.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/image/image.info b/frontend/drupal/modules/image/image.info index f427a74b8..6a9abc05e 100644 --- a/frontend/drupal/modules/image/image.info +++ b/frontend/drupal/modules/image/image.info @@ -7,7 +7,7 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/image/image.test b/frontend/drupal/modules/image/image.test index 0c26ffa84..22edcaa06 100644 --- a/frontend/drupal/modules/image/image.test +++ b/frontend/drupal/modules/image/image.test @@ -1022,7 +1022,7 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { $this->drupalGet('node/add/article'); $this->assertText(t('Files must be less than 50 KB.'), 'Image widget max file size is displayed on article form.'); $this->assertText(t('Allowed file types: ' . $test_image_extension . '.'), 'Image widget allowed file types displayed on article form.'); - $this->assertText(t('Images must be between 10x10 and 100x100 pixels.'), 'Image widget allowed resolution displayed on article form.'); + $this->assertText(t('Images must be at least 10x10 pixels. Images larger than 100x100 pixels will be resized.'), 'Image widget allowed resolution displayed on article form.'); // We have to create the article first and then edit it because the alt // and title fields do not display until the image has been attached. diff --git a/frontend/drupal/modules/image/tests/image_module_test.info b/frontend/drupal/modules/image/tests/image_module_test.info index 300bfec19..8c4c7cd91 100644 --- a/frontend/drupal/modules/image/tests/image_module_test.info +++ b/frontend/drupal/modules/image/tests/image_module_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/locale/locale.info b/frontend/drupal/modules/locale/locale.info index aeec19f6d..a48d54d2d 100644 --- a/frontend/drupal/modules/locale/locale.info +++ b/frontend/drupal/modules/locale/locale.info @@ -6,7 +6,7 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/locale/tests/locale_test.info b/frontend/drupal/modules/locale/tests/locale_test.info index 1337147f6..7a682fc2d 100644 --- a/frontend/drupal/modules/locale/tests/locale_test.info +++ b/frontend/drupal/modules/locale/tests/locale_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/menu/menu.info b/frontend/drupal/modules/menu/menu.info index af1c1cdb0..db77373d1 100644 --- a/frontend/drupal/modules/menu/menu.info +++ b/frontend/drupal/modules/menu/menu.info @@ -6,7 +6,7 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/node/node.info b/frontend/drupal/modules/node/node.info index 58746503a..9e166f964 100644 --- a/frontend/drupal/modules/node/node.info +++ b/frontend/drupal/modules/node/node.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/node/tests/node_access_test.info b/frontend/drupal/modules/node/tests/node_access_test.info index 06ecbee92..e023592cf 100644 --- a/frontend/drupal/modules/node/tests/node_access_test.info +++ b/frontend/drupal/modules/node/tests/node_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/node/tests/node_test.info b/frontend/drupal/modules/node/tests/node_test.info index ef4f350e2..b2917e831 100644 --- a/frontend/drupal/modules/node/tests/node_test.info +++ b/frontend/drupal/modules/node/tests/node_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/node/tests/node_test_exception.info b/frontend/drupal/modules/node/tests/node_test_exception.info index be07e9821..8bf4c8a14 100644 --- a/frontend/drupal/modules/node/tests/node_test_exception.info +++ b/frontend/drupal/modules/node/tests/node_test_exception.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/openid/openid.inc b/frontend/drupal/modules/openid/openid.inc index a1da1d0b5..4ca747164 100644 --- a/frontend/drupal/modules/openid/openid.inc +++ b/frontend/drupal/modules/openid/openid.inc @@ -142,7 +142,8 @@ function _openid_xrds_parse($raw_xml) { // For PHP version >= 5.2.11, we can use this function to protect against // malicious doctype declarations and other unexpected entity loading. // However, we will not rely on it, and reject any XML with a DOCTYPE. - $disable_entity_loader = function_exists('libxml_disable_entity_loader'); + // libxml_disable_entity_loader() is deprecated in PHP >= 8.0. + $disable_entity_loader = function_exists('libxml_disable_entity_loader') && PHP_VERSION_ID < 80000; if ($disable_entity_loader) { $load_entities = libxml_disable_entity_loader(TRUE); } diff --git a/frontend/drupal/modules/openid/openid.info b/frontend/drupal/modules/openid/openid.info index ec4003c5c..c5cb0c3c4 100644 --- a/frontend/drupal/modules/openid/openid.info +++ b/frontend/drupal/modules/openid/openid.info @@ -5,7 +5,7 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/openid/openid.module b/frontend/drupal/modules/openid/openid.module index a52dbc3de..f2c1b8d54 100644 --- a/frontend/drupal/modules/openid/openid.module +++ b/frontend/drupal/modules/openid/openid.module @@ -743,7 +743,7 @@ function openid_association_request($public) { return $request; } -function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $service) { +function openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $service) { global $base_url; module_load_include('inc', 'openid'); diff --git a/frontend/drupal/modules/openid/tests/openid_test.info b/frontend/drupal/modules/openid/tests/openid_test.info index f45d60dee..8bd7fd891 100644 --- a/frontend/drupal/modules/openid/tests/openid_test.info +++ b/frontend/drupal/modules/openid/tests/openid_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/overlay/overlay.info b/frontend/drupal/modules/overlay/overlay.info index fe783baa6..f45f8daed 100644 --- a/frontend/drupal/modules/overlay/overlay.info +++ b/frontend/drupal/modules/overlay/overlay.info @@ -4,7 +4,7 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/path/path.info b/frontend/drupal/modules/path/path.info index 3a3458fda..6d4d607d3 100644 --- a/frontend/drupal/modules/path/path.info +++ b/frontend/drupal/modules/path/path.info @@ -6,7 +6,7 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/php/php.info b/frontend/drupal/modules/php/php.info index 5ae31e002..5fd7ce9b8 100644 --- a/frontend/drupal/modules/php/php.info +++ b/frontend/drupal/modules/php/php.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/poll/poll.info b/frontend/drupal/modules/poll/poll.info index 9fc8f3d08..bb82be47d 100644 --- a/frontend/drupal/modules/poll/poll.info +++ b/frontend/drupal/modules/poll/poll.info @@ -6,7 +6,7 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/profile/profile.info b/frontend/drupal/modules/profile/profile.info index a08fa71e7..dcc03f38d 100644 --- a/frontend/drupal/modules/profile/profile.info +++ b/frontend/drupal/modules/profile/profile.info @@ -11,7 +11,7 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/rdf/rdf.info b/frontend/drupal/modules/rdf/rdf.info index 0c7c621df..50a5b0171 100644 --- a/frontend/drupal/modules/rdf/rdf.info +++ b/frontend/drupal/modules/rdf/rdf.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/rdf/tests/rdf_test.info b/frontend/drupal/modules/rdf/tests/rdf_test.info index 62a772930..2593a3d94 100644 --- a/frontend/drupal/modules/rdf/tests/rdf_test.info +++ b/frontend/drupal/modules/rdf/tests/rdf_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = blog -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/search/search.info b/frontend/drupal/modules/search/search.info index 609c8f952..e91d74c7e 100644 --- a/frontend/drupal/modules/search/search.info +++ b/frontend/drupal/modules/search/search.info @@ -8,7 +8,7 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/search/tests/search_embedded_form.info b/frontend/drupal/modules/search/tests/search_embedded_form.info index 759a04d7b..c9696727a 100644 --- a/frontend/drupal/modules/search/tests/search_embedded_form.info +++ b/frontend/drupal/modules/search/tests/search_embedded_form.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/search/tests/search_extra_type.info b/frontend/drupal/modules/search/tests/search_extra_type.info index 79f463f50..b0091f8eb 100644 --- a/frontend/drupal/modules/search/tests/search_extra_type.info +++ b/frontend/drupal/modules/search/tests/search_extra_type.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/search/tests/search_node_tags.info b/frontend/drupal/modules/search/tests/search_node_tags.info index 3b46a9a24..861bca5c5 100644 --- a/frontend/drupal/modules/search/tests/search_node_tags.info +++ b/frontend/drupal/modules/search/tests/search_node_tags.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/shortcut/shortcut.info b/frontend/drupal/modules/shortcut/shortcut.info index 80ee46040..467bf751c 100644 --- a/frontend/drupal/modules/shortcut/shortcut.info +++ b/frontend/drupal/modules/shortcut/shortcut.info @@ -6,7 +6,7 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/drupal_web_test_case.php b/frontend/drupal/modules/simpletest/drupal_web_test_case.php index c426ba53a..f212b0eb5 100644 --- a/frontend/drupal/modules/simpletest/drupal_web_test_case.php +++ b/frontend/drupal/modules/simpletest/drupal_web_test_case.php @@ -1690,8 +1690,14 @@ class DrupalWebTestCase extends DrupalTestCase { $this->fail('Failed to drop all prefixed tables.'); } + // In PHP 8 some tests encounter problems when shutdown code tries to + // access the database connection after it's been explicitly closed, for + // example the destructor of DrupalCacheArray. We avoid this by not fully + // destroying the test database connection. + $close = \PHP_VERSION_ID < 80000; + // Get back to the original connection. - Database::removeConnection('default'); + Database::removeConnection('default', $close); Database::renameConnection('simpletest_original_default', 'default'); // Restore original shutdown callbacks array to prevent original @@ -3084,7 +3090,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertTextHelper($text, $message = '', $group, $not_exists) { + protected function assertTextHelper($text, $message, $group, $not_exists) { if ($this->plainTextContent === FALSE) { $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); } @@ -3150,7 +3156,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertUniqueTextHelper($text, $message = '', $group, $be_unique) { + protected function assertUniqueTextHelper($text, $message, $group, $be_unique) { if ($this->plainTextContent === FALSE) { $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); } @@ -3256,7 +3262,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @param $callback * The name of the theme function to invoke; e.g. 'links' for theme_links(). * @param $variables - * (optional) An array of variables to pass to the theme function. + * An array of variables to pass to the theme function. * @param $expected * The expected themed output string. * @param $message @@ -3272,7 +3278,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '', $group = 'Other') { + protected function assertThemeOutput($callback, array $variables, $expected, $message = '', $group = 'Other') { $output = theme($callback, $variables); $this->verbose('Variables:' . '
    ' .  check_plain(var_export($variables, TRUE)) . '
    ' . '
    ' . 'Result:' . '
    ' .  check_plain(var_export($output, TRUE)) . '
    ' diff --git a/frontend/drupal/modules/simpletest/simpletest.info b/frontend/drupal/modules/simpletest/simpletest.info index b8576f271..474a8fb9b 100644 --- a/frontend/drupal/modules/simpletest/simpletest.info +++ b/frontend/drupal/modules/simpletest/simpletest.info @@ -58,7 +58,7 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/simpletest.test b/frontend/drupal/modules/simpletest/simpletest.test index 5d1c718c1..80b841d58 100644 --- a/frontend/drupal/modules/simpletest/simpletest.test +++ b/frontend/drupal/modules/simpletest/simpletest.test @@ -164,13 +164,16 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { $this->pass(t('Test ID is @id.', array('@id' => $this->testId))); // Generates a warning. - $i = 1 / 0; + $a = ''; + foreach ($a as $b) { + + } // Call an assert function specific to that class. $this->assertNothing(); - // Generates a warning inside a PHP function. - array_key_exists(NULL, NULL); + // Generates 3 warnings inside a PHP function. + simplexml_load_string(''); debug('Foo', 'Debug'); } @@ -195,19 +198,21 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); // Check that a warning is caught by simpletest. - $this->assertAssertion('Division by zero', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); + // The exact error message differs between PHP versions so we check only + // the presense of the 'foreach' statement. + $this->assertAssertion('foreach()', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); // Check that the backtracing code works for specific assert function. $this->assertAssertion('This is nothing.', 'Other', 'Pass', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); // Check that errors that occur inside PHP internal functions are correctly reported. // The exact error message differs between PHP versions so we check only - // the function name 'array_key_exists'. - $this->assertAssertion('array_key_exists', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); + // the function name 'simplexml_load_string'. + $this->assertAssertion('simplexml_load_string', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); $this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()'); - $this->assertEqual('6 passes, 5 fails, 2 exceptions, and 1 debug message', $this->childTestResults['summary'], 'Stub test summary is correct'); + $this->assertEqual('6 passes, 5 fails, 4 exceptions, and 1 debug message', $this->childTestResults['summary'], 'Stub test summary is correct'); $this->test_ids[] = $test_id = $this->getTestIdFromResults(); $this->assertTrue($test_id, 'Found test ID in results.'); diff --git a/frontend/drupal/modules/simpletest/tests/actions_loop_test.info b/frontend/drupal/modules/simpletest/tests/actions_loop_test.info index 81783afea..908bed8d6 100644 --- a/frontend/drupal/modules/simpletest/tests/actions_loop_test.info +++ b/frontend/drupal/modules/simpletest/tests/actions_loop_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/ajax_forms_test.info b/frontend/drupal/modules/simpletest/tests/ajax_forms_test.info index 944f31c1e..01aaa0404 100644 --- a/frontend/drupal/modules/simpletest/tests/ajax_forms_test.info +++ b/frontend/drupal/modules/simpletest/tests/ajax_forms_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/ajax_test.info b/frontend/drupal/modules/simpletest/tests/ajax_test.info index 860f681b3..a6a924674 100644 --- a/frontend/drupal/modules/simpletest/tests/ajax_test.info +++ b/frontend/drupal/modules/simpletest/tests/ajax_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/batch_test.info b/frontend/drupal/modules/simpletest/tests/batch_test.info index a8e87a5ce..24f8bf779 100644 --- a/frontend/drupal/modules/simpletest/tests/batch_test.info +++ b/frontend/drupal/modules/simpletest/tests/batch_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/boot_test_1.info b/frontend/drupal/modules/simpletest/tests/boot_test_1.info index 41c56572d..d09ef328c 100644 --- a/frontend/drupal/modules/simpletest/tests/boot_test_1.info +++ b/frontend/drupal/modules/simpletest/tests/boot_test_1.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/boot_test_2.info b/frontend/drupal/modules/simpletest/tests/boot_test_2.info index 10b332413..ab2ca587f 100644 --- a/frontend/drupal/modules/simpletest/tests/boot_test_2.info +++ b/frontend/drupal/modules/simpletest/tests/boot_test_2.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/bootstrap.test b/frontend/drupal/modules/simpletest/tests/bootstrap.test index effd04bf2..61caf53ca 100644 --- a/frontend/drupal/modules/simpletest/tests/bootstrap.test +++ b/frontend/drupal/modules/simpletest/tests/bootstrap.test @@ -453,13 +453,13 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { /** * Skips handling of "file not found" errors. */ - public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line) { // Skip error handling if this is a "file not found" error. if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { $this->getFilenameTestTriggeredError = $message; return; } - _drupal_error_handler($error_level, $message, $filename, $line, $context); + _drupal_error_handler($error_level, $message, $filename, $line); } } @@ -569,13 +569,13 @@ class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase { /** * Skips handling of "file not found" errors. */ - public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) { + public function fileNotFoundErrorHandler($error_level, $message, $filename, $line) { // Skip error handling if this is a "file not found" error. if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) { $this->getFilenameTestTriggeredError = $message; return; } - _drupal_error_handler($error_level, $message, $filename, $line, $context); + _drupal_error_handler($error_level, $message, $filename, $line); } /** diff --git a/frontend/drupal/modules/simpletest/tests/common.test b/frontend/drupal/modules/simpletest/tests/common.test index 6145546f7..ff3996763 100644 --- a/frontend/drupal/modules/simpletest/tests/common.test +++ b/frontend/drupal/modules/simpletest/tests/common.test @@ -2633,8 +2633,8 @@ class DrupalErrorCollectionUnitTest extends DrupalWebTestCase { $this->assertEqual(count($this->collectedErrors), 3, 'Three errors were collected'); if (count($this->collectedErrors) == 3) { - $this->assertError($this->collectedErrors[0], 'Notice', 'error_test_generate_warnings()', 'error_test.module', 'Undefined variable: bananas'); - $this->assertError($this->collectedErrors[1], 'Warning', 'error_test_generate_warnings()', 'error_test.module', 'Division by zero'); + $this->assertError($this->collectedErrors[0], 'Notice', 'error_test_generate_warnings()', 'error_test.module', 'Object of class stdClass could not be converted to int'); + $this->assertError($this->collectedErrors[1], 'Warning', 'error_test_generate_warnings()', 'error_test.module', \PHP_VERSION_ID < 80000 ? 'Invalid argument supplied for foreach()' : 'foreach() argument must be of type array|object, string given'); $this->assertError($this->collectedErrors[2], 'User warning', 'error_test_generate_warnings()', 'error_test.module', 'Drupal is awesome'); } else { diff --git a/frontend/drupal/modules/simpletest/tests/common_test.info b/frontend/drupal/modules/simpletest/tests/common_test.info index b22b08328..44d709b81 100644 --- a/frontend/drupal/modules/simpletest/tests/common_test.info +++ b/frontend/drupal/modules/simpletest/tests/common_test.info @@ -7,7 +7,7 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/common_test_cron_helper.info b/frontend/drupal/modules/simpletest/tests/common_test_cron_helper.info index 79cb6dbeb..a0f668b80 100644 --- a/frontend/drupal/modules/simpletest/tests/common_test_cron_helper.info +++ b/frontend/drupal/modules/simpletest/tests/common_test_cron_helper.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/database_test.info b/frontend/drupal/modules/simpletest/tests/database_test.info index 758aeb72e..87cd43f9d 100644 --- a/frontend/drupal/modules/simpletest/tests/database_test.info +++ b/frontend/drupal/modules/simpletest/tests/database_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/database_test.test b/frontend/drupal/modules/simpletest/tests/database_test.test index 04be5c85b..0f0d2c320 100644 --- a/frontend/drupal/modules/simpletest/tests/database_test.test +++ b/frontend/drupal/modules/simpletest/tests/database_test.test @@ -3755,21 +3755,36 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $transaction = db_transaction(); $this->insertRow('row'); $this->executeDDLStatement(); - // Rollback the outer transaction. + + set_error_handler(array($this, 'rollBackWithoutTransactionErrorHandler')); try { + // Rollback the outer transaction. $transaction->rollback(); - unset($transaction); - // @TODO: an exception should be triggered here, but is not, because - // "ROLLBACK" fails silently in MySQL if there is no transaction active. - // $this->fail(t('Rolling back a transaction containing DDL should fail.')); + // @see \DatabaseConnection_mysql::rollback() + if (PHP_VERSION_ID >= 80000) { + $this->fail('Rolling back a transaction containing DDL should produce a warning.'); + } } - catch (DatabaseTransactionNoActiveException $e) { - $this->pass('Rolling back a transaction containing DDL should fail.'); + catch (Exception $e) { + $this->assertEqual('Rollback attempted when there is no active transaction.', $e->getMessage()); } + restore_error_handler(); + unset($transaction); $this->assertRowPresent('row'); } } + /** + * Special handling of "rollback without transaction" errors. + */ + public function rollBackWithoutTransactionErrorHandler($error_level, $message, $filename, $line) { + // Throw an exception if this is a "rollback without transaction" error. + if (strpos($message, 'Rollback attempted when there is no active transaction.') !== FALSE ) { + throw new Exception('Rollback attempted when there is no active transaction.'); + } + _drupal_error_handler($error_level, $message, $filename, $line); + } + /** * Insert a single row into the testing table. */ diff --git a/frontend/drupal/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/frontend/drupal/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info index 6820a3517..37f8b035a 100644 --- a/frontend/drupal/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info +++ b/frontend/drupal/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/frontend/drupal/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 0fb3d57b1..d88417ed3 100644 --- a/frontend/drupal/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/frontend/drupal/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/frontend/drupal/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index d015d3fb0..d966bd21b 100644 --- a/frontend/drupal/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/frontend/drupal/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/entity_cache_test.info b/frontend/drupal/modules/simpletest/tests/entity_cache_test.info index a95eaf5f2..a15be7285 100644 --- a/frontend/drupal/modules/simpletest/tests/entity_cache_test.info +++ b/frontend/drupal/modules/simpletest/tests/entity_cache_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/entity_cache_test_dependency.info b/frontend/drupal/modules/simpletest/tests/entity_cache_test_dependency.info index 35ea7039d..72522da34 100644 --- a/frontend/drupal/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/frontend/drupal/modules/simpletest/tests/entity_cache_test_dependency.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/entity_crud_hook_test.info b/frontend/drupal/modules/simpletest/tests/entity_crud_hook_test.info index 17198cd0c..f381ce040 100644 --- a/frontend/drupal/modules/simpletest/tests/entity_crud_hook_test.info +++ b/frontend/drupal/modules/simpletest/tests/entity_crud_hook_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/entity_query_access_test.info b/frontend/drupal/modules/simpletest/tests/entity_query_access_test.info index 9a5d7249a..62e5fee13 100644 --- a/frontend/drupal/modules/simpletest/tests/entity_query_access_test.info +++ b/frontend/drupal/modules/simpletest/tests/entity_query_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/error.test b/frontend/drupal/modules/simpletest/tests/error.test index f946e82f1..5b56d52d9 100644 --- a/frontend/drupal/modules/simpletest/tests/error.test +++ b/frontend/drupal/modules/simpletest/tests/error.test @@ -22,13 +22,13 @@ class DrupalErrorHandlerTestCase extends DrupalWebTestCase { function testErrorHandler() { $error_notice = array( '%type' => 'Notice', - '!message' => 'Undefined variable: bananas', + '!message' => 'Object of class stdClass could not be converted to int', '%function' => 'error_test_generate_warnings()', '%file' => drupal_realpath('modules/simpletest/tests/error_test.module'), ); $error_warning = array( '%type' => 'Warning', - '!message' => 'Division by zero', + '!message' => \PHP_VERSION_ID < 80000 ? 'Invalid argument supplied for foreach()' : 'foreach() argument must be of type array|object, string given', '%function' => 'error_test_generate_warnings()', '%file' => drupal_realpath('modules/simpletest/tests/error_test.module'), ); @@ -113,4 +113,3 @@ class DrupalErrorHandlerTestCase extends DrupalWebTestCase { $this->assertNoRaw($message, format_string('Did not find error message: !message.', array('!message' => $message))); } } - diff --git a/frontend/drupal/modules/simpletest/tests/error_test.info b/frontend/drupal/modules/simpletest/tests/error_test.info index 37425c3c8..58c54e331 100644 --- a/frontend/drupal/modules/simpletest/tests/error_test.info +++ b/frontend/drupal/modules/simpletest/tests/error_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/error_test.module b/frontend/drupal/modules/simpletest/tests/error_test.module index d062cb067..43d0d0c61 100644 --- a/frontend/drupal/modules/simpletest/tests/error_test.module +++ b/frontend/drupal/modules/simpletest/tests/error_test.module @@ -40,9 +40,13 @@ function error_test_generate_warnings($collect_errors = FALSE) { // Tell Drupal error reporter to send errors to Simpletest or not. define('SIMPLETEST_COLLECT_ERRORS', $collect_errors); // This will generate a notice. - $monkey_love = $bananas; + $notice = new \stdClass(); + $notice == 1 ? 1 : 0; // This will generate a warning. - $awesomely_big = 1/0; + $a = ''; + foreach ($a as $b) { + + } // This will generate a user error. trigger_error("Drupal is awesome", E_USER_WARNING); return ""; diff --git a/frontend/drupal/modules/simpletest/tests/file_test.info b/frontend/drupal/modules/simpletest/tests/file_test.info index 0bb6d1db6..f1f287751 100644 --- a/frontend/drupal/modules/simpletest/tests/file_test.info +++ b/frontend/drupal/modules/simpletest/tests/file_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/filter_test.info b/frontend/drupal/modules/simpletest/tests/filter_test.info index 96d316805..cf51e69ec 100644 --- a/frontend/drupal/modules/simpletest/tests/filter_test.info +++ b/frontend/drupal/modules/simpletest/tests/filter_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/form.test b/frontend/drupal/modules/simpletest/tests/form.test index 49b561a63..23af5f4c7 100644 --- a/frontend/drupal/modules/simpletest/tests/form.test +++ b/frontend/drupal/modules/simpletest/tests/form.test @@ -240,14 +240,14 @@ class FormsTestCase extends DrupalWebTestCase { $values = drupal_json_decode($this->drupalPost(NULL, array('required_checkbox' => 1), t('Submit'))); $expected_values = array( 'disabled_checkbox_on' => 'disabled_checkbox_on', - 'disabled_checkbox_off' => '', + 'disabled_checkbox_off' => 0, 'checkbox_on' => 'checkbox_on', - 'checkbox_off' => '', + 'checkbox_off' => 0, 'zero_checkbox_on' => '0', - 'zero_checkbox_off' => '', + 'zero_checkbox_off' => 0, ); foreach ($expected_values as $widget => $expected_value) { - $this->assertEqual($values[$widget], $expected_value, format_string('Checkbox %widget returns expected value (expected: %expected, got: %value)', array( + $this->assertIdentical($values[$widget], $expected_value, format_string('Checkbox %widget returns expected value (expected: %expected, got: %value)', array( '%widget' => var_export($widget, TRUE), '%expected' => var_export($expected_value, TRUE), '%value' => var_export($values[$widget], TRUE), diff --git a/frontend/drupal/modules/simpletest/tests/form_test.info b/frontend/drupal/modules/simpletest/tests/form_test.info index 346acca29..c3f8ba901 100644 --- a/frontend/drupal/modules/simpletest/tests/form_test.info +++ b/frontend/drupal/modules/simpletest/tests/form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/image_test.info b/frontend/drupal/modules/simpletest/tests/image_test.info index b3e168036..009cffe85 100644 --- a/frontend/drupal/modules/simpletest/tests/image_test.info +++ b/frontend/drupal/modules/simpletest/tests/image_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/menu_test.info b/frontend/drupal/modules/simpletest/tests/menu_test.info index 4973c75b7..80fa7d0fc 100644 --- a/frontend/drupal/modules/simpletest/tests/menu_test.info +++ b/frontend/drupal/modules/simpletest/tests/menu_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/module_test.info b/frontend/drupal/modules/simpletest/tests/module_test.info index 8c1eb098d..8fb3bf27d 100644 --- a/frontend/drupal/modules/simpletest/tests/module_test.info +++ b/frontend/drupal/modules/simpletest/tests/module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/path_test.info b/frontend/drupal/modules/simpletest/tests/path_test.info index ab1a36b56..0de0cf6e1 100644 --- a/frontend/drupal/modules/simpletest/tests/path_test.info +++ b/frontend/drupal/modules/simpletest/tests/path_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/psr_0_test/psr_0_test.info b/frontend/drupal/modules/simpletest/tests/psr_0_test/psr_0_test.info index 2e2e8e508..efc8711bd 100644 --- a/frontend/drupal/modules/simpletest/tests/psr_0_test/psr_0_test.info +++ b/frontend/drupal/modules/simpletest/tests/psr_0_test/psr_0_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/psr_4_test/psr_4_test.info b/frontend/drupal/modules/simpletest/tests/psr_4_test/psr_4_test.info index a5270a7f7..ab7ec3eea 100644 --- a/frontend/drupal/modules/simpletest/tests/psr_4_test/psr_4_test.info +++ b/frontend/drupal/modules/simpletest/tests/psr_4_test/psr_4_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/requirements1_test.info b/frontend/drupal/modules/simpletest/tests/requirements1_test.info index bc85ee845..31be1944b 100644 --- a/frontend/drupal/modules/simpletest/tests/requirements1_test.info +++ b/frontend/drupal/modules/simpletest/tests/requirements1_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/requirements2_test.info b/frontend/drupal/modules/simpletest/tests/requirements2_test.info index b023c44e9..792456984 100644 --- a/frontend/drupal/modules/simpletest/tests/requirements2_test.info +++ b/frontend/drupal/modules/simpletest/tests/requirements2_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/session.test b/frontend/drupal/modules/simpletest/tests/session.test index 893d03e9f..5632d44e4 100644 --- a/frontend/drupal/modules/simpletest/tests/session.test +++ b/frontend/drupal/modules/simpletest/tests/session.test @@ -244,6 +244,187 @@ class SessionTestCase extends DrupalWebTestCase { $this->assertResponse(403, 'An empty session ID is not allowed.'); } + /** + * Test absence of SameSite attribute on session cookies by default. + */ + function testNoSameSiteCookieAttributeDefault() { + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + if (\PHP_VERSION_ID < 70300) { + $this->drupalLogin($user); + } + else { + // PHP often defaults to an empty value for session.cookie_samesite but + // that may vary, so we set an explicit empty value. + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: *EMPTY*'; + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in'), array(), $headers); + } + $this->assertFalse(preg_match('/SameSite=/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie has no SameSite attribute (default).'); + } + + /** + * Test SameSite attribute = None by default on Secure session cookies. + */ + function testSameSiteCookieAttributeNoneSecure() { + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + $headers = array(); + if (\PHP_VERSION_ID >= 70300) { + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: None'; + } + // Test HTTPS session handling by altering the form action to submit the + // login form through https.php, which creates a mock HTTPS request. + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpsUrl('user'); + $edit = array('name' => $user->name, 'pass' => $user->pass_raw); + $this->drupalPost(NULL, $edit, t('Log in'), array(), $headers); + $this->assertTrue(preg_match('/SameSite=None/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=None.'); + } + + /** + * Test SameSite attribute = None on session cookies. + */ + function testSameSiteCookieAttributeNone() { + variable_set('samesite_cookie_value', 'None'); + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + $this->drupalLogin($user); + $this->assertTrue(preg_match('/SameSite=None/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=None.'); + } + + /** + * Test SameSite attribute = Lax on session cookies. + */ + function testSameSiteCookieAttributeLax() { + variable_set('samesite_cookie_value', 'Lax'); + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + $this->drupalLogin($user); + $this->assertTrue(preg_match('/SameSite=Lax/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=Lax.'); + } + + /** + * Test SameSite attribute = Strict on session cookies. + */ + function testSameSiteCookieAttributeStrict() { + variable_set('samesite_cookie_value', 'Strict'); + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + $this->drupalLogin($user); + $this->assertTrue(preg_match('/SameSite=Strict/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=Strict.'); + } + + /** + * Test disabling the samesite attribute on session cookies via $conf + */ + function testSameSiteCookieAttributeDisabledViaConf() { + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + variable_set('samesite_cookie_value', FALSE); + if (\PHP_VERSION_ID < 70300) { + // There is no session.cookie_samesite in earlier PHP versions. + $this->drupalLogin($user); + } + else { + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: Lax'; + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in'), array(), $headers); + } + $this->assertFalse(preg_match('/SameSite=/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie has no SameSite attribute (conf).'); + } + + /** + * Test disabling the samesite attribute on session cookies via php ini + */ + function testSameSiteCookieAttributeDisabledViaPhpIni() { + if (\PHP_VERSION_ID < 70300) { + // There is no session.cookie_samesite in earlier PHP versions. + $this->pass('This test is only for PHP 7.3 and later.'); + return; + } + $user = $this->drupalCreateUser(array('access content')); + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: *EMPTY*'; + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in'), array(), $headers); + $this->assertFalse(preg_match('/SameSite=/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie has no SameSite attribute (ini).'); + } + + /** + * Test that a PHP setting for session.cookie_samesite is not overridden by + * the default value in Drupal, without a samesite_cookie_value variable. + */ + function testSamesiteCookiePhpSettingLax() { + if (\PHP_VERSION_ID < 70300) { + // There is no session.cookie_samesite in earlier PHP versions. + $this->pass('This test is only for PHP 7.3 and later.'); + return; + } + $user = $this->drupalCreateUser(array('access content')); + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: Lax'; + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in'), array(), $headers); + $this->assertTrue(preg_match('/SameSite=Lax/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=Lax.'); + } + + /** + * Test overriding the PHP setting for session.cookie_samesite with the + * samesite_cookie_value variable. + */ + function testSamesiteCookieOverrideLaxToStrict() { + if (\PHP_VERSION_ID < 70300) { + // There is no session.cookie_samesite in earlier PHP versions. + $this->pass('This test is only for PHP 7.3 and later.'); + return; + } + variable_set('samesite_cookie_value', 'Strict'); + $user = $this->drupalCreateUser(array('access content')); + // Send our own login POST so that we can pass a custom header to trigger + // session_test.module to call ini_set('session.cookie_samesite', $value) + $headers[] = 'X-Session-Cookie-Ini-Set: Lax'; + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in'), array(), $headers); + $this->assertTrue(preg_match('/SameSite=Strict/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as SameSite=Strict.'); + } + + /** + * Test SameSite attribute = Lax on set-cookie header on logout. + */ + function testSamesiteCookieLogoutLax() { + variable_set('samesite_cookie_value', 'Lax'); + $user = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user->uid); + $this->drupalLogin($user); + $this->drupalGet('user/logout'); + $this->assertTrue(preg_match('/SameSite=Lax/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie deletion includes SameSite=Lax.'); + } + /** * Reset the cookie file so that it refers to the specified user. * @@ -285,6 +466,20 @@ class SessionTestCase extends DrupalWebTestCase { $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', 'Session was not empty.'); } } + + /** + * Builds a URL for submitting a mock HTTPS request to HTTP test environments. + * + * @param $url + * A Drupal path such as 'user'. + * + * @return + * An absolute URL. + */ + protected function httpsUrl($url) { + global $base_url; + return $base_url . '/modules/simpletest/tests/https.php?q=' . $url; + } } /** diff --git a/frontend/drupal/modules/simpletest/tests/session_test.info b/frontend/drupal/modules/simpletest/tests/session_test.info index 4a04c5b8c..7b66b3892 100644 --- a/frontend/drupal/modules/simpletest/tests/session_test.info +++ b/frontend/drupal/modules/simpletest/tests/session_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/session_test.module b/frontend/drupal/modules/simpletest/tests/session_test.module index 689ff099a..b2d31079b 100644 --- a/frontend/drupal/modules/simpletest/tests/session_test.module +++ b/frontend/drupal/modules/simpletest/tests/session_test.module @@ -64,6 +64,19 @@ function session_test_menu() { return $items; } +/** + * It's very unusual to do anything outside of a function / hook, but in this + * case we want to simulate a given session.cookie_samesite setting in php.ini + * or via ini_set() in settings.php. This would almost never be a good idea + * outside of a test scenario. + */ +if (isset($_SERVER['HTTP_X_SESSION_COOKIE_INI_SET'])) { + if (in_array($_SERVER['HTTP_X_SESSION_COOKIE_INI_SET'], array('None', 'Lax', 'Strict', '*EMPTY*'))) { + $value = ($_SERVER['HTTP_X_SESSION_COOKIE_INI_SET'] == '*EMPTY*') ? '' : $_SERVER['HTTP_X_SESSION_COOKIE_INI_SET']; + ini_set('session.cookie_samesite', $value); + } +} + /** * Implements hook_boot(). */ diff --git a/frontend/drupal/modules/simpletest/tests/system_dependencies_test.info b/frontend/drupal/modules/simpletest/tests/system_dependencies_test.info index d7242e282..eeedee4ab 100644 --- a/frontend/drupal/modules/simpletest/tests/system_dependencies_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index 1680c1ade..f1b38e520 100644 --- a/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_test.info b/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_test.info index 1f70929de..ce6cd080b 100644 --- a/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,7 +5,7 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index 94721cbc5..f18384529 100644 --- a/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,7 +7,7 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_test.info b/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_test.info index ce11959b5..51390e80a 100644 --- a/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,7 +5,7 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_project_namespace_test.info b/frontend/drupal/modules/simpletest/tests/system_project_namespace_test.info index 371999d87..111c3fb24 100644 --- a/frontend/drupal/modules/simpletest/tests/system_project_namespace_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_project_namespace_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = drupal:filter -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/system_test.info b/frontend/drupal/modules/simpletest/tests/system_test.info index 3e6f87f82..7221a8d6e 100644 --- a/frontend/drupal/modules/simpletest/tests/system_test.info +++ b/frontend/drupal/modules/simpletest/tests/system_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/taxonomy_test.info b/frontend/drupal/modules/simpletest/tests/taxonomy_test.info index f3a4a4bd8..4b8696bd4 100644 --- a/frontend/drupal/modules/simpletest/tests/taxonomy_test.info +++ b/frontend/drupal/modules/simpletest/tests/taxonomy_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/theme_test.info b/frontend/drupal/modules/simpletest/tests/theme_test.info index 4a929db16..318c76873 100644 --- a/frontend/drupal/modules/simpletest/tests/theme_test.info +++ b/frontend/drupal/modules/simpletest/tests/theme_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/frontend/drupal/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index 62d1a72dc..e4338553e 100644 --- a/frontend/drupal/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/frontend/drupal/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/frontend/drupal/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index d3fd66caa..188521739 100644 --- a/frontend/drupal/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/frontend/drupal/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/themes/test_theme/test_theme.info b/frontend/drupal/modules/simpletest/tests/themes/test_theme/test_theme.info index a09c4f190..470df24fd 100644 --- a/frontend/drupal/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/frontend/drupal/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,7 +17,7 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info b/frontend/drupal/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info index 758c9b553..1c23ececb 100644 --- a/frontend/drupal/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info +++ b/frontend/drupal/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info @@ -4,7 +4,7 @@ core = 7.x hidden = TRUE engine = nyan_cat -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/update_script_test.info b/frontend/drupal/modules/simpletest/tests/update_script_test.info index 6ecea7912..3084effa1 100644 --- a/frontend/drupal/modules/simpletest/tests/update_script_test.info +++ b/frontend/drupal/modules/simpletest/tests/update_script_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/update_test_1.info b/frontend/drupal/modules/simpletest/tests/update_test_1.info index a9cc0c0f9..fa2eb75fe 100644 --- a/frontend/drupal/modules/simpletest/tests/update_test_1.info +++ b/frontend/drupal/modules/simpletest/tests/update_test_1.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/update_test_2.info b/frontend/drupal/modules/simpletest/tests/update_test_2.info index a9cc0c0f9..fa2eb75fe 100644 --- a/frontend/drupal/modules/simpletest/tests/update_test_2.info +++ b/frontend/drupal/modules/simpletest/tests/update_test_2.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/update_test_3.info b/frontend/drupal/modules/simpletest/tests/update_test_3.info index a9cc0c0f9..fa2eb75fe 100644 --- a/frontend/drupal/modules/simpletest/tests/update_test_3.info +++ b/frontend/drupal/modules/simpletest/tests/update_test_3.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/upgrade/upgrade.locale.test b/frontend/drupal/modules/simpletest/tests/upgrade/upgrade.locale.test index 2a3056b06..f7b0038d6 100644 --- a/frontend/drupal/modules/simpletest/tests/upgrade/upgrade.locale.test +++ b/frontend/drupal/modules/simpletest/tests/upgrade/upgrade.locale.test @@ -124,7 +124,7 @@ class LocaleUpgradePathTestCase extends UpgradePathTestCase { /** * Asserts that a page exists and is in the specified language. */ - public function assertPageInLanguage($path = NULL, $langcode) { + public function assertPageInLanguage($path, $langcode) { if (isset($path)) { $this->drupalGet($path); } diff --git a/frontend/drupal/modules/simpletest/tests/url_alter_test.info b/frontend/drupal/modules/simpletest/tests/url_alter_test.info index 3adee0fad..e2775bb4d 100644 --- a/frontend/drupal/modules/simpletest/tests/url_alter_test.info +++ b/frontend/drupal/modules/simpletest/tests/url_alter_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/simpletest/tests/xmlrpc_test.info b/frontend/drupal/modules/simpletest/tests/xmlrpc_test.info index 326b82dd1..c83dae688 100644 --- a/frontend/drupal/modules/simpletest/tests/xmlrpc_test.info +++ b/frontend/drupal/modules/simpletest/tests/xmlrpc_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/statistics/statistics.info b/frontend/drupal/modules/statistics/statistics.info index 0ad418980..f55e71530 100644 --- a/frontend/drupal/modules/statistics/statistics.info +++ b/frontend/drupal/modules/statistics/statistics.info @@ -6,7 +6,7 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/syslog/syslog.info b/frontend/drupal/modules/syslog/syslog.info index 4ee8581dd..45ee371c7 100644 --- a/frontend/drupal/modules/syslog/syslog.info +++ b/frontend/drupal/modules/syslog/syslog.info @@ -6,7 +6,7 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/system/system.info b/frontend/drupal/modules/system/system.info index 8b4ba3a38..6cff0a625 100644 --- a/frontend/drupal/modules/system/system.info +++ b/frontend/drupal/modules/system/system.info @@ -12,7 +12,7 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/system/system.tar.inc b/frontend/drupal/modules/system/system.tar.inc index 0af6275b4..7a0719e58 100644 --- a/frontend/drupal/modules/system/system.tar.inc +++ b/frontend/drupal/modules/system/system.tar.inc @@ -280,7 +280,7 @@ class Archive_Tar { $this->_close(); // ----- Look for a local copy to delete - if ($this->_temp_tarname != '') { + if ($this->_temp_tarname != '' && (bool) preg_match('/^tar[[:alnum:]]*\.tmp$/', $this->_temp_tarname)) { @drupal_unlink($this->_temp_tarname); } } diff --git a/frontend/drupal/modules/system/tests/cron_queue_test.info b/frontend/drupal/modules/system/tests/cron_queue_test.info index 4c33802bc..c2c263643 100644 --- a/frontend/drupal/modules/system/tests/cron_queue_test.info +++ b/frontend/drupal/modules/system/tests/cron_queue_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/system/tests/system_cron_test.info b/frontend/drupal/modules/system/tests/system_cron_test.info index 4a79c5461..36a21de0f 100644 --- a/frontend/drupal/modules/system/tests/system_cron_test.info +++ b/frontend/drupal/modules/system/tests/system_cron_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/taxonomy/taxonomy.info b/frontend/drupal/modules/taxonomy/taxonomy.info index 16b3b361d..d696969d7 100644 --- a/frontend/drupal/modules/taxonomy/taxonomy.info +++ b/frontend/drupal/modules/taxonomy/taxonomy.info @@ -8,7 +8,7 @@ files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/toolbar/toolbar.info b/frontend/drupal/modules/toolbar/toolbar.info index f847f2a0a..0ffdee0ea 100644 --- a/frontend/drupal/modules/toolbar/toolbar.info +++ b/frontend/drupal/modules/toolbar/toolbar.info @@ -4,7 +4,7 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/tracker/tracker.info b/frontend/drupal/modules/tracker/tracker.info index 02fdaab62..eb4f6db49 100644 --- a/frontend/drupal/modules/tracker/tracker.info +++ b/frontend/drupal/modules/tracker/tracker.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/translation/tests/translation_test.info b/frontend/drupal/modules/translation/tests/translation_test.info index af85ecc89..b5e861514 100644 --- a/frontend/drupal/modules/translation/tests/translation_test.info +++ b/frontend/drupal/modules/translation/tests/translation_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/translation/translation.info b/frontend/drupal/modules/translation/translation.info index 8b81011da..4362b7d44 100644 --- a/frontend/drupal/modules/translation/translation.info +++ b/frontend/drupal/modules/translation/translation.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/trigger/tests/trigger_test.info b/frontend/drupal/modules/trigger/tests/trigger_test.info index 4af57113d..1b1099e44 100644 --- a/frontend/drupal/modules/trigger/tests/trigger_test.info +++ b/frontend/drupal/modules/trigger/tests/trigger_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/trigger/trigger.info b/frontend/drupal/modules/trigger/trigger.info index dccfe4a68..7d18ab017 100644 --- a/frontend/drupal/modules/trigger/trigger.info +++ b/frontend/drupal/modules/trigger/trigger.info @@ -6,7 +6,7 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/aaa_update_test.info b/frontend/drupal/modules/update/tests/aaa_update_test.info index 1e591277f..290880308 100644 --- a/frontend/drupal/modules/update/tests/aaa_update_test.info +++ b/frontend/drupal/modules/update/tests/aaa_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/bbb_update_test.info b/frontend/drupal/modules/update/tests/bbb_update_test.info index ca8c12c72..55eb983b4 100644 --- a/frontend/drupal/modules/update/tests/bbb_update_test.info +++ b/frontend/drupal/modules/update/tests/bbb_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/ccc_update_test.info b/frontend/drupal/modules/update/tests/ccc_update_test.info index 30b134133..981b5b381 100644 --- a/frontend/drupal/modules/update/tests/ccc_update_test.info +++ b/frontend/drupal/modules/update/tests/ccc_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/frontend/drupal/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info index db08879db..06efabaa2 100644 --- a/frontend/drupal/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info +++ b/frontend/drupal/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info @@ -3,7 +3,7 @@ description = Test theme which is used as admin theme. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/frontend/drupal/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 409ab59cf..dc984ae9a 100644 --- a/frontend/drupal/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/frontend/drupal/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,7 +3,7 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/frontend/drupal/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index 757210739..afdbc931c 100644 --- a/frontend/drupal/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/frontend/drupal/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,7 +4,7 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/tests/update_test.info b/frontend/drupal/modules/update/tests/update_test.info index bbd89205a..0844cdd68 100644 --- a/frontend/drupal/modules/update/tests/update_test.info +++ b/frontend/drupal/modules/update/tests/update_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/update/update.info b/frontend/drupal/modules/update/update.info index 2799f33a3..95b1c78d2 100644 --- a/frontend/drupal/modules/update/update.info +++ b/frontend/drupal/modules/update/update.info @@ -6,7 +6,7 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/user/tests/user_flood_test.info b/frontend/drupal/modules/user/tests/user_flood_test.info index 4500f0882..845418525 100644 --- a/frontend/drupal/modules/user/tests/user_flood_test.info +++ b/frontend/drupal/modules/user/tests/user_flood_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/user/tests/user_form_test.info b/frontend/drupal/modules/user/tests/user_form_test.info index 3ee56197b..0cfa543cb 100644 --- a/frontend/drupal/modules/user/tests/user_form_test.info +++ b/frontend/drupal/modules/user/tests/user_form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/user/tests/user_form_test.module b/frontend/drupal/modules/user/tests/user_form_test.module index 2af15cb83..4379bd1a5 100644 --- a/frontend/drupal/modules/user/tests/user_form_test.module +++ b/frontend/drupal/modules/user/tests/user_form_test.module @@ -80,3 +80,42 @@ function user_form_test_user_account_submit($form, &$form_state) { // test for bugs that can be triggered in contributed modules. $form_state['rebuild'] = TRUE; } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function user_form_test_form_user_pass_reset_alter(&$form, &$form_state) { + // An unaltered form has no form values; the uid/timestmap/"confirm" are in + // the URL arguments. (If for some reason a form_alter method needs the + // hash, it can be retrieved from $form['#action'].) + if (!is_numeric(arg(2)) || !is_numeric(arg(3)) || !is_string(arg(4)) || arg(4) !== 'confirm') { + throw new Exception("Something unexpected changed in the user_pass_reset form; we are not getting the UID/timestamp/'confirm' passed anymore."); + } + // Use the variable system to communicate to the test code, since we don't + // share a session with it. + variable_set('user_test_pass_reset_form_build_' . arg(2), TRUE); + + $form['#submit'][] = 'user_form_test_form_user_pass_reset_submit'; + // We must cache the form to ensure the form builder (user_pass_reset()) is + // skipped when processing the submitted form, otherwise we get redirected + // already during form build. + $form_state['cache'] = TRUE; +} + +/** + * Submit function for user_pass_reset(). + */ +function user_form_test_form_user_pass_reset_submit($form, &$form_state) { + // On submit, the hash is in arg(4). + if (!is_numeric(arg(2)) || !is_numeric(arg(3)) || !is_string(arg(4)) || strlen(arg(4)) < 32) { + throw new Exception("Something unexpected changed in the user_pass_reset form; we are not getting the UID/timestamp/hash passed anymore."); + } + variable_set('user_test_pass_reset_form_submit_' . arg(2), TRUE); + // Because the form does no further processing and has no redirect set, + // drupal_redirect_form() will redirect back to ourselves + // (user/reset/UID/TIMESTAMP/HASH/login); we will be logged in and redirected + // while the form is built again. FYI: we cannot set $form_state['rebuild'] + // to get around the first redirect without further hacks, because then the + // form won't pass the hash. (Our original $form_state['build_info']['args'] + // contains "confirm" for the 3rd argument.) +} diff --git a/frontend/drupal/modules/user/tests/user_session_test.info b/frontend/drupal/modules/user/tests/user_session_test.info index 4a9152602..ca482bdb5 100644 --- a/frontend/drupal/modules/user/tests/user_session_test.info +++ b/frontend/drupal/modules/user/tests/user_session_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/user/user.admin.inc b/frontend/drupal/modules/user/user.admin.inc index 6ca330b01..0db7efb1a 100644 --- a/frontend/drupal/modules/user/user.admin.inc +++ b/frontend/drupal/modules/user/user.admin.inc @@ -61,7 +61,7 @@ function user_filter_form() { if ($type == 'permission') { // Merge arrays of module permissions into one. // Slice past the first element '[any]' whose value is not an array. - $options = call_user_func_array('array_merge', array_slice($filters[$type]['options'], 1)); + $options = call_user_func_array('array_merge', array_values(array_slice($filters[$type]['options'], 1))); $value = $options[$value]; } else { @@ -1050,4 +1050,3 @@ function user_admin_role_delete_confirm_submit($form, &$form_state) { drupal_set_message(t('The role has been deleted.')); $form_state['redirect'] = 'admin/people/permissions/roles'; } - diff --git a/frontend/drupal/modules/user/user.info b/frontend/drupal/modules/user/user.info index 01b772a10..4bed910d2 100644 --- a/frontend/drupal/modules/user/user.info +++ b/frontend/drupal/modules/user/user.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/modules/user/user.pages.inc b/frontend/drupal/modules/user/user.pages.inc index 6f997a62e..937f7a31e 100644 --- a/frontend/drupal/modules/user/user.pages.inc +++ b/frontend/drupal/modules/user/user.pages.inc @@ -134,10 +134,25 @@ function user_pass_submit($form, &$form_state) { /** * Menu callback; process one time login link and redirects to the user page on success. + * + * In order to never disclose password reset hashes via referrer headers or + * web browser history, this function always issues a redirect when a valid + * password reset hash is in the URL. */ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) { global $user; + // Check for a reset hash in the session. Immediately remove it (to prevent it + // from being reused, for example if this page is visited again via the + // browser history) and store it for later use. + if (isset($_SESSION['pass_reset_hash'])) { + $session_reset_hash = $_SESSION['pass_reset_hash']; + unset($_SESSION['pass_reset_hash']); + } + else { + $session_reset_hash = NULL; + } + // When processing the one-time login link, we have to make sure that a user // isn't already logged in. if ($user->uid) { @@ -182,7 +197,36 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); drupal_goto('user/password'); } - elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { + // Validate the reset hash from the URL or from the session. + $is_valid = FALSE; + if ($account->uid && $timestamp >= $account->login && $timestamp <= $current) { + // If the reset hash in the URL is valid, put it in the session and + // redirect to the same URL but with the hash replaced by an invalid + // one ("confirm"). This prevents disclosing the hash via referrer + // headers or web browser history. + if ($hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { + if ($action === 'login') { + // The 'login' action redirects directly to the user edit form. + $is_valid = TRUE; + } + else { + $_SESSION['pass_reset_hash'] = $hashed_pass; + $args = arg(); + foreach ($args as &$arg) { + if ($arg == $hashed_pass) { + $arg = 'confirm'; + } + } + $path = implode('/', $args); + drupal_goto($path, array('query' => drupal_get_query_parameters())); + } + } + // If the reset hash from the session is valid, use that. + elseif ($session_reset_hash && $session_reset_hash == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { + $is_valid = TRUE; + } + } + if ($is_valid) { // First stage is a confirmation form, then login if ($action == 'login') { // Set the new user. @@ -204,7 +248,11 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a $form['help'] = array('#markup' => '

    ' . t('This login can be used only once.') . '

    '); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in')); - $form['#action'] = url("user/reset/$uid/$timestamp/$hashed_pass/login"); + $form['#action'] = url("user/reset/$uid/$timestamp/$session_reset_hash/login"); + // Prevent the browser from storing this page so that the token will + // not be visible in the form action if the back button is used to + // revisit this page. + drupal_add_http_header('Cache-Control', 'no-store'); return $form; } } diff --git a/frontend/drupal/modules/user/user.test b/frontend/drupal/modules/user/user.test index 4c16b531c..ec2f90d6f 100644 --- a/frontend/drupal/modules/user/user.test +++ b/frontend/drupal/modules/user/user.test @@ -480,6 +480,10 @@ class UserLoginTestCase extends DrupalWebTestCase { class UserPasswordResetTestCase extends DrupalWebTestCase { protected $profile = 'standard'; + function setUp() { + parent::setUp('user_form_test'); + } + public static function getInfo() { return array( 'name' => 'Reset password', @@ -491,20 +495,38 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { /** * Retrieves password reset email and extracts the login link. */ - public function getResetURL() { + public function getResetURL($bypass_form = FALSE) { // Assume the most recent email. $_emails = $this->drupalGetMails(); $email = end($_emails); $urls = array(); preg_match('#.+user/reset/.+#', $email['body'], $urls); - return $urls[0]; + return $urls[0] . ($bypass_form ? '/login' : ''); + } + + /** + * Generates login link. + */ + public function generateResetURL($account, $bypass_form = FALSE) { + return user_pass_reset_url($account) . ($bypass_form ? '/login' : ''); + } + + /** + * Turns a password reset URL into a 'confirm' URL. + */ + public function getConfirmURL($reset_url) { + // Last part is always the hash; replace with "confirm". + $parts = explode('/', $reset_url); + array_pop($parts); + array_push($parts, 'confirm'); + return implode('/', $parts); } /** * Tests password reset functionality. */ - function testUserPasswordReset() { + function testUserPasswordReset($use_direct_login_link = FALSE) { // Create a user. $account = $this->drupalCreateUser(); $this->drupalLogin($account); @@ -540,11 +562,19 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { ); field_create_instance($instance); - $resetURL = $this->getResetURL(); + variable_del("user_test_pass_reset_form_submit_{$account->uid}"); + $resetURL = $this->getResetURL($use_direct_login_link); $this->drupalGet($resetURL); // Check successful login. - $this->drupalPost(NULL, NULL, t('Log in')); + if (!$use_direct_login_link) { + $this->assertUrl($this->getConfirmURL($resetURL), array(), 'The user is redirected to the reset password confirm form.'); + $this->drupalPost(NULL, NULL, t('Log in')); + // The form was fully processed before redirecting. + $form_submit_handled = variable_get("user_test_pass_reset_form_submit_{$account->uid}", FALSE); + $this->assertTrue($form_submit_handled, 'A custom submit handler executed.'); + } + $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'); // Make sure the Ajax request from uploading a file does not invalidate the // reset token. @@ -559,6 +589,16 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); + + // Ensure blocked and deleted accounts can't access the direct login link. + $this->drupalLogout(); + $reset_url = $this->generateResetURL($account, $use_direct_login_link); + user_save($account, array('status' => 0)); + $this->drupalGet($reset_url); + $this->assertResponse(403); + user_delete($account->uid); + $this->drupalGet($reset_url); + $this->assertResponse(403); } /** @@ -642,21 +682,51 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { /** * Test user password reset while logged in. */ - function testUserPasswordResetLoggedIn() { + function testUserPasswordResetLoggedIn($use_direct_login_link = FALSE) { + $another_account = $this->drupalCreateUser(); $account = $this->drupalCreateUser(); $this->drupalLogin($account); // Make sure the test account has a valid password. user_save($account, array('pass' => user_password())); + // Try to use the login link while logged in as a different user. // Generate one time login link. - $reset_url = user_pass_reset_url($account); + $reset_url = $this->generateResetURL($another_account, $use_direct_login_link); $this->drupalGet($reset_url); + $this->assertRaw(t( + 'Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please logout and try using the link again.', + array('%other_user' => $account->name, '%resetting_user' => $another_account->name, '!logout' => url('user/logout')) + )); - $this->assertText('Reset password'); - $this->drupalPost(NULL, NULL, t('Log in')); + // Test the link for a deleted user while logged in. + user_delete($another_account->uid); + $this->drupalGet($reset_url); + $this->assertText('The one-time login link you clicked is invalid.'); + // Generate a one time login link for the logged-in user. + $fapi_action = $use_direct_login_link ? 'build' : 'submit'; + variable_del("user_test_pass_reset_form_{$fapi_action}_{$account->uid}"); + $reset_url = $this->generateResetURL($account, $use_direct_login_link); + $this->drupalGet($reset_url); + if ($use_direct_login_link) { + // The form is never fully built; user is logged out (session destroyed) + // and redirected to the same URL, then logged in again and redirected + // during form build. + $form_built = variable_get("user_test_pass_reset_form_build_{$account->uid}", FALSE); + $this->assertTrue(!$form_built, 'The password reset form was never fully built.'); + } + else { + $this->assertUrl($this->getConfirmURL($reset_url), array(), 'The user is redirected to the reset password confirm form.'); + $this->assertText('Reset password'); + $this->drupalPost(NULL, NULL, t('Log in')); + // The form was fully processed before redirecting. + $form_submit_handled = variable_get("user_test_pass_reset_form_submit_{$account->uid}", FALSE); + $this->assertTrue($form_submit_handled, 'A custom submit handler executed.'); + } $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'); + // The user can change the forgotten password on the page they are + // redirected to. $pass = user_password(); $edit = array( 'pass[pass1]' => $pass, @@ -667,6 +737,14 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->assertText('The changes have been saved.'); } + /** + * Test direct login link that bypasses the password reset form. + */ + function testUserDirectLogin() { + $this->testUserPasswordReset(TRUE); + $this->testUserPasswordResetLoggedIn(TRUE); + } + /** * Attempts login using an expired password reset link. */ @@ -770,7 +848,7 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE)); $this->drupalGet($reset_url); $this->assertText($user1->name, 'The valid password reset page shows the user name.'); - $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.'); + $this->assertUrl($this->getConfirmURL($reset_url), array(), 'The user is redirected to the reset password confirm form.'); $this->assertNoText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'); } diff --git a/frontend/drupal/profiles/minimal/minimal.info b/frontend/drupal/profiles/minimal/minimal.info index c9a70a1db..bd8f3f6f5 100644 --- a/frontend/drupal/profiles/minimal/minimal.info +++ b/frontend/drupal/profiles/minimal/minimal.info @@ -5,7 +5,7 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/profiles/standard/standard.info b/frontend/drupal/profiles/standard/standard.info index bcda8923e..2e10e0d05 100644 --- a/frontend/drupal/profiles/standard/standard.info +++ b/frontend/drupal/profiles/standard/standard.info @@ -24,7 +24,7 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/frontend/drupal/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 6ef5373c4..79d3e5bcf 100644 --- a/frontend/drupal/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/frontend/drupal/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/frontend/drupal/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index fb319ecb0..91e34f6c6 100644 --- a/frontend/drupal/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/frontend/drupal/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,7 +8,7 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/profiles/testing/testing.info b/frontend/drupal/profiles/testing/testing.info index fe1a2e863..bf2a85a18 100644 --- a/frontend/drupal/profiles/testing/testing.info +++ b/frontend/drupal/profiles/testing/testing.info @@ -4,7 +4,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-01-20 -version = "7.78" +; Information added by Drupal.org packaging script on 2021-04-21 +version = "7.80" project = "drupal" -datestamp = "1611162699" +datestamp = "1619021862" diff --git a/frontend/drupal/sites/all/libraries/blazy/LICENSE b/frontend/drupal/sites/all/libraries/blazy/LICENSE new file mode 100755 index 000000000..f3fe393c7 --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-16 Bjørn Klinggaard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/frontend/drupal/sites/all/libraries/blazy/README.md b/frontend/drupal/sites/all/libraries/blazy/README.md new file mode 100755 index 000000000..f6e3bb23c --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/README.md @@ -0,0 +1,187 @@ +#hey, be lazy +[![Downloads](https://img.shields.io/npm/dm/blazy.svg?style=flat)](https://www.npmjs.com/package/blazy) +[![Latest Stable Version](https://img.shields.io/npm/v/blazy.svg?style=flat)](https://www.npmjs.com/package/blazy) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/dinbror/blazy/blob/master/LICENSE) + +bLazy is a lightweight script for lazy loading and multi-serving images, iframes, videos and more (less than 1.4KB minified and gzipped). It’s written in pure JavaScript why it doesn’t depend on 3rd-party libraries such as jQuery. It lets you lazy load and multi-serve your images so you can save bandwidth and server requests. The user will have faster load times and save data usage if he/she doesn't browse the whole page. + +**Table of Contents**
    +1. [Demo](https://github.com/dinbror/blazy#demo)
    +2. [Usage & API](https://github.com/dinbror/blazy#usage--api)
    +3. [Why be lazy?](https://github.com/dinbror/blazy#why-be-lazy)
    +4. [Changelog](https://github.com/dinbror/blazy#changelog)
    +5. [License](https://github.com/dinbror/blazy#license)
    + +## DEMO ## +[http://dinbror.dk/blazy/](http://dinbror.dk/blazy/?ref=github) + +More examples: +[http://dinbror.dk/blazy/examples/](http://dinbror.dk/blazy/examples/?ref=github) + +Codepen playground: +http://codepen.io/dinbror/pen/HzCAJ + +## USAGE & API ## +[http://dinbror.dk/blog/blazy/](http://dinbror.dk/blog/blazy/?ref=github) + +### INSTALL +You can install blazy.js with npm: +``` npm +npm install blazy --save +``` +or bower: +``` bower +bower install blazy --save +``` + +### CDN +If you don't want to host the script yourself you can link to the latest minified file: +`//cdn.jsdelivr.net/blazy/latest/blazy.min.js` on [jsDelivr](http://www.jsdelivr.com/#!blazy). +Exchange `latest` with the specific version number if you want to lock it in. + +## WHY BE LAZY? ## +* bLazy is used on big sites with millions of monthly visitors so it has been tested out in the real world. +* bLazy is written in pure JavaScript why it doesn’t depend on 3rd-party libraries such as jQuery. +* bLazy is lightweight, less than 1.4KB and less than 1.25KB if you don't need IE7- support. +* bLazy is very fast. It has focus on performance why it also auto-destroys when it's done lazy loading. +* bLazy can lazy load all types of images including background images. +* bLazy is future-proof. It supports srcset and the picture element. +* bLazy can serve retina images on retina devices. +* bLazy can lazy load everything with a src like iframes, HTML5 videos, scripts, unity games etc. +* bLazy supports all browsers used today including legacy browsers like IE7 and 8. +* bLazy supports all main module formats like AMD, CommonJS and globals. + + +## WISHLIST/NEW FEATURES REQUESTED BY YOU +* Only preload "first frame" of progressive jpegs. +* Add support for CSS background property; [image-set](https://cloudfour.com/examples/image-set/) ([caniuse](http://caniuse.com/#feat=css-image-set)). +* Add a class when the lazyloading begins. +* Option to keen load once on screen images have loaded. +* Add option to disable success/error classes +* Animate the container that contains the image you lazy load. You can do that today by adding/removing a class in the success callback. + +## CHANGELOG +### v 1.8.2 (2016/10/25) ### +* Added null check in public `load` function. +* Bugfix: Fixed `this` issue defaulting to window when passing `revalidate` or `destroy` in setTimeout as reference [#73](https://github.com/dinbror/blazy/issues/73) and [#112](https://github.com/dinbror/blazy/pull/112). Thanks [PeteDuncanson](https://github.com/PeteDuncanson). +* Bugfix: If parts of container is outside window use the viewport boundaries [#113](https://github.com/dinbror/blazy/issues/113) and [#114](https://github.com/dinbror/blazy/pull/114). Thanks [dbirkbeck](https://github.com/dbirkbeck). + +### v 1.8.1 (2016/10/22) ### +* Bugfix: Created polyfill and check for support of `Element.closest` which was introduced in the container fix in v. 1.8.0. + +### v 1.8.0 (2016/10/16) ### +* Bugfix: Non-visible images being loaded inside container [#23](https://github.com/dinbror/blazy/issues/23) and [#96](https://github.com/dinbror/blazy/issues/96). + +### v 1.7.1 (2016/10/14) ### +* Bugfix: In safari the picture element always loaded the default/fallback image [#92](https://github.com/dinbror/blazy/issues/92). + +### v 1.7.0 (2016/10/10) ### +* Bugfix: When lazyloading picture elements it also loaded the fallback/regular image [#92](https://github.com/dinbror/blazy/issues/92) and [108](https://github.com/dinbror/blazy/pull/108). Thanks [@idoshamun](https://github.com/idoshamun) +* Refactored loadElement function to avoid redundancy. + +### v 1.6.4 (2016/10/08) ### +* Bugfix: When lazyloading srcset images it also loaded the fallback/regular image [#99](https://github.com/dinbror/blazy/pull/99). Thanks [@m0uH](https://github.com/m0uH) + +### v 1.6.3 (2016/09/30) ### +* Changed event listener to passive listener [#106](https://github.com/dinbror/blazy/pull/106). Thanks [@idoshamun](https://github.com/idoshamun) +* Added support for web components (shadow dom) [#107](https://github.com/dinbror/blazy/pull/107). Thanks again [@idoshamun](https://github.com/idoshamun) + +### v 1.6.2 (2016/05/09) ### +* Fixed bug introduced in v.1.6.0, not using retina/breakpoint src [#90](https://github.com/dinbror/blazy/issues/90). + +### v 1.6.1 (2016/05/02) ### +* Implemented a workaround for onload/onerror bug introduced in chrome v50, [LINK](https://productforums.google.com/forum/#!topic/chrome/p51Lk7vnP2o). Fixed [#85](https://github.com/dinbror/blazy/issues/85). + +### v 1.6.0 (2016/04/30) ### +* Added support for srcset and the picture element. Fixed [#69](https://github.com/dinbror/blazy/issues/69), [#75](https://github.com/dinbror/blazy/issues/75), [#77](https://github.com/dinbror/blazy/issues/77) and [#82](https://github.com/dinbror/blazy/issues/82). +* Added support for lazy load of videos with sources. Fixed [#81](https://github.com/dinbror/blazy/issues/81). +* Bugfix. Ensuring that error and success classes won't be added multiple times. Fixed [#84](https://github.com/dinbror/blazy/issues/84). +* Marked `breakpoints` as obsolete. Will be removed in upcoming version. Use srcset and/or the picture element instead. + +### v 1.5.4 (2016/03/06) ### +* Fixed two Safari bugs: [#66](https://github.com/dinbror/blazy/issues/66) and [#78](https://github.com/dinbror/blazy/issues/78). Ensuring "DOM ready". + +### v 1.5.3 (2016/03/01) ### +* Implemented [#30](https://github.com/dinbror/blazy/pull/30). Keeping data source until success. +* Fixed [#47](https://github.com/dinbror/blazy/pull/47). After implementing #30 you can now get the image src and more information in the error/success callbacks. +* Added example page to repo `/example/index.html`. + +### v 1.5.2 (2015/12/01) ### +* Fixed minor bug where the error class was added when calling `revalidate()`. +* Minor refactoring + +### v 1.5.1 (2015/11/14) ### +* Fixed toArray function so it now works in IE7 + 8 again. Bug introduced in 1.4.0. Thanks for reporting [@imcotton](https://github.com/imcotton). +* Fixed [#41](https://github.com/dinbror/blazy/pull/41). Added options for validate and saveViewportOffset delay. + +### v 1.5.0 (2015/10/30) ### +* Added new feature. Now you can lazy load everything with a src attribute like iframes, unity games etc. +* Fixed [#45](https://github.com/dinbror/blazy/issues/45). Now you can pass an option if you always want to load invisible images/elements. +* Fixed [#49](https://github.com/dinbror/blazy/issues/49). Expanded the `load` function so it's now possible to pass a list of elements instead of only one element. Tested with getElementById, getElementsByClassName, querySelectorAll, querySelector and jQuery selector. +* Fixed [#63](https://github.com/dinbror/blazy/issues/63). + +### v 1.4.1 (2015/10/12) ### +* Fixed [#60](https://github.com/dinbror/blazy/issues/60). An "Uncaught TypeError" when options is null introduced in the refactoring in version 1.4.0. + +### v 1.4.0 (2015/09/28) ### +* Fixed [#56](https://github.com/dinbror/blazy/issues/56). Now it's possible to create multiple versions of blazy without overriding options. + +### v 1.3.1 (2015/02/01) ### +* Added support for CommonJS-like environments that support module.exports like [node](http://nodejs.org/). + +### v 1.3.0 (2015/01/23) ### +* Fixed [#34](https://github.com/dinbror/blazy/issues/34). Expanded public `load` function with force attribute, so you can force hidden images to be loaded. +* Fixed [#24](https://github.com/dinbror/blazy/issues/24), [#32](https://github.com/dinbror/blazy/issues/32) and [#35](https://github.com/dinbror/blazy/issues/35). Updated "elementInView" function with intersection check. Thanks @teohhanhui. + +### v 1.2.2 (2014/05/04) ### +* Fixed [#15](https://github.com/dinbror/blazy/issues/15), when you resize the browser window in another tab bLazy didn't trigger new images in view. Thanks joshribakoff. + +### v 1.2.1 (2014/03/23) ### +* When lazy loading background images it now only updates the background-image css attribute. Thanks Saku. + +### v 1.2.0 (2014/02/15) ### +* Important note: renamed option multi to `breakpoints` because it's much more descriptive. +* Added [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) support. +* Minor refactoring. + +### v 1.1.3 (2014/01/21) ### +* Fixed hardcoded retina check (isRetina = true). +* Fixed "Uncaught TypeError" when data-src is null. Instead it'll trigger the `error` callback. + +### v 1.1.2 (2014/01/03) ### +* New feature: After many requests I added the possibility to handle retina images (if you’re not doing retina-first). +* New feature: Now you can also lazy load background images. +* Added new option, `separator`. Used if you want to pass retina images, default separator is ‘|’. (data-src=“image.jpg|image@2x.jpg”). + +### v 1.1.1 (2013/12/27) ### +* Fixed #1, resize/scroll events may be detached when adding elements by ajax. +* Added new option, `errorClass`. Classname an image will get if something goes wrong, default is ‘b-error’. +* Renamed option loadedClass to `successClass` so naming is aligned. Default is still ‘b-loaded’. + +### v 1.1.0 (2013/11/22) ### +* Renamed success callback from onLoaded to `success`. +* Added onerror callback; `error`. +* Added the possibility to pass multiple containers instead of one. + +### v 1.0.5 (2013/10/7) ### +* Fixed "Uncaught TypeError" when container isn't default (window). + +### v 1.0.4 (2013/8/29) ### +* Added null check so we won't try to load an image if it's missing a data source. + +### v 1.0.3 (2013/8/27) ### +* Added new option, `loadedClass`. Classname an image will get when loaded. +* Added support for horizontal lazy loading. +* Reduced throttle time for validate. + +### v 1.0.2 (2013/8/7) ### +* Fixed typo in unbindEvent function. +* Added support for IE7 as promised (fallback for querySelectorAll). + +### v 1.0.1 (2013/8/6) ### +* Performance improvements. +* Added throttle function to ensure that we don't call resize/scroll functions too often. +* Cleaning image markup when image has loaded. + +##LICENSE: +Copyright (c) 2013-16 Bjørn Klinggaard. Licensed under the [The MIT License (MIT)](http://opensource.org/licenses/MIT). diff --git a/frontend/drupal/sites/all/libraries/blazy/blazy.js b/frontend/drupal/sites/all/libraries/blazy/blazy.js new file mode 100755 index 000000000..00af78dcc --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/blazy.js @@ -0,0 +1,370 @@ +/*! + hey, [be]Lazy.js - v1.8.2 - 2016.10.25 + A fast, small and dependency free lazy load script (https://github.com/dinbror/blazy) + (c) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy +*/ +; +(function(root, blazy) { + if (typeof define === 'function' && define.amd) { + // AMD. Register bLazy as an anonymous module + define(blazy); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = blazy(); + } else { + // Browser globals. Register bLazy on window + root.Blazy = blazy(); + } +})(this, function() { + 'use strict'; + + //private vars + var _source, _viewport, _isRetina, _supportClosest, _attrSrc = 'src', _attrSrcset = 'srcset'; + + // constructor + return function Blazy(options) { + //IE7- fallback for missing querySelectorAll support + if (!document.querySelectorAll) { + var s = document.createStyleSheet(); + document.querySelectorAll = function(r, c, i, j, a) { + a = document.all, c = [], r = r.replace(/\[for\b/gi, '[htmlFor').split(','); + for (i = r.length; i--;) { + s.addRule(r[i], 'k:v'); + for (j = a.length; j--;) a[j].currentStyle.k && c.push(a[j]); + s.removeRule(0); + } + return c; + }; + } + + //options and helper vars + var scope = this; + var util = scope._util = {}; + util.elements = []; + util.destroyed = true; + scope.options = options || {}; + scope.options.error = scope.options.error || false; + scope.options.offset = scope.options.offset || 100; + scope.options.root = scope.options.root || document; + scope.options.success = scope.options.success || false; + scope.options.selector = scope.options.selector || '.b-lazy'; + scope.options.separator = scope.options.separator || '|'; + scope.options.containerClass = scope.options.container; + scope.options.container = scope.options.containerClass ? document.querySelectorAll(scope.options.containerClass) : false; + scope.options.errorClass = scope.options.errorClass || 'b-error'; + scope.options.breakpoints = scope.options.breakpoints || false; + scope.options.loadInvisible = scope.options.loadInvisible || false; + scope.options.successClass = scope.options.successClass || 'b-loaded'; + scope.options.validateDelay = scope.options.validateDelay || 25; + scope.options.saveViewportOffsetDelay = scope.options.saveViewportOffsetDelay || 50; + scope.options.srcset = scope.options.srcset || 'data-srcset'; + scope.options.src = _source = scope.options.src || 'data-src'; + _supportClosest = Element.prototype.closest; + _isRetina = window.devicePixelRatio > 1; + _viewport = {}; + _viewport.top = 0 - scope.options.offset; + _viewport.left = 0 - scope.options.offset; + + + /* public functions + ************************************/ + scope.revalidate = function() { + initialize(scope); + }; + scope.load = function(elements, force) { + var opt = this.options; + if (elements && elements.length === undefined) { + loadElement(elements, force, opt); + } else { + each(elements, function(element) { + loadElement(element, force, opt); + }); + } + }; + scope.destroy = function() { + var util = scope._util; + if (scope.options.container) { + each(scope.options.container, function(object) { + unbindEvent(object, 'scroll', util.validateT); + }); + } + unbindEvent(window, 'scroll', util.validateT); + unbindEvent(window, 'resize', util.validateT); + unbindEvent(window, 'resize', util.saveViewportOffsetT); + util.count = 0; + util.elements.length = 0; + util.destroyed = true; + }; + + //throttle, ensures that we don't call the functions too often + util.validateT = throttle(function() { + validate(scope); + }, scope.options.validateDelay, scope); + util.saveViewportOffsetT = throttle(function() { + saveViewportOffset(scope.options.offset); + }, scope.options.saveViewportOffsetDelay, scope); + saveViewportOffset(scope.options.offset); + + //handle multi-served image src (obsolete) + each(scope.options.breakpoints, function(object) { + if (object.width >= window.screen.width) { + _source = object.src; + return false; + } + }); + + // start lazy load + setTimeout(function() { + initialize(scope); + }); // "dom ready" fix + + }; + + + /* Private helper functions + ************************************/ + function initialize(self) { + var util = self._util; + // First we create an array of elements to lazy load + util.elements = toArray(self.options); + util.count = util.elements.length; + // Then we bind resize and scroll events if not already binded + if (util.destroyed) { + util.destroyed = false; + if (self.options.container) { + each(self.options.container, function(object) { + bindEvent(object, 'scroll', util.validateT); + }); + } + bindEvent(window, 'resize', util.saveViewportOffsetT); + bindEvent(window, 'resize', util.validateT); + bindEvent(window, 'scroll', util.validateT); + } + // And finally, we start to lazy load. + validate(self); + } + + function validate(self) { + var util = self._util; + for (var i = 0; i < util.count; i++) { + var element = util.elements[i]; + if (elementInView(element, self.options) || hasClass(element, self.options.successClass)) { + self.load(element); + util.elements.splice(i, 1); + util.count--; + i--; + } + } + if (util.count === 0) { + self.destroy(); + } + } + + function elementInView(ele, options) { + var rect = ele.getBoundingClientRect(); + + if(options.container && _supportClosest){ + // Is element inside a container? + var elementContainer = ele.closest(options.containerClass); + if(elementContainer){ + var containerRect = elementContainer.getBoundingClientRect(); + // Is container in view? + if(inView(containerRect, _viewport)){ + var top = containerRect.top - options.offset; + var right = containerRect.right + options.offset; + var bottom = containerRect.bottom + options.offset; + var left = containerRect.left - options.offset; + var containerRectWithOffset = { + top: top > _viewport.top ? top : _viewport.top, + right: right < _viewport.right ? right : _viewport.right, + bottom: bottom < _viewport.bottom ? bottom : _viewport.bottom, + left: left > _viewport.left ? left : _viewport.left + }; + // Is element in view of container? + return inView(rect, containerRectWithOffset); + } else { + return false; + } + } + } + return inView(rect, _viewport); + } + + function inView(rect, viewport){ + // Intersection + return rect.right >= viewport.left && + rect.bottom >= viewport.top && + rect.left <= viewport.right && + rect.top <= viewport.bottom; + } + + function loadElement(ele, force, options) { + // if element is visible, not loaded or forced + if (!hasClass(ele, options.successClass) && (force || options.loadInvisible || (ele.offsetWidth > 0 && ele.offsetHeight > 0))) { + var dataSrc = getAttr(ele, _source) || getAttr(ele, options.src); // fallback to default 'data-src' + if (dataSrc) { + var dataSrcSplitted = dataSrc.split(options.separator); + var src = dataSrcSplitted[_isRetina && dataSrcSplitted.length > 1 ? 1 : 0]; + var srcset = getAttr(ele, options.srcset); + var isImage = equal(ele, 'img'); + var parent = ele.parentNode; + var isPicture = parent && equal(parent, 'picture'); + // Image or background image + if (isImage || ele.src === undefined) { + var img = new Image(); + // using EventListener instead of onerror and onload + // due to bug introduced in chrome v50 + // (https://productforums.google.com/forum/#!topic/chrome/p51Lk7vnP2o) + var onErrorHandler = function() { + if (options.error) options.error(ele, "invalid"); + addClass(ele, options.errorClass); + unbindEvent(img, 'error', onErrorHandler); + unbindEvent(img, 'load', onLoadHandler); + }; + var onLoadHandler = function() { + // Is element an image + if (isImage) { + if(!isPicture) { + handleSources(ele, src, srcset); + } + // or background-image + } else { + ele.style.backgroundImage = 'url("' + src + '")'; + } + itemLoaded(ele, options); + unbindEvent(img, 'load', onLoadHandler); + unbindEvent(img, 'error', onErrorHandler); + }; + + // Picture element + if (isPicture) { + img = ele; // Image tag inside picture element wont get preloaded + each(parent.getElementsByTagName('source'), function(source) { + handleSource(source, _attrSrcset, options.srcset); + }); + } + bindEvent(img, 'error', onErrorHandler); + bindEvent(img, 'load', onLoadHandler); + handleSources(img, src, srcset); // Preload + + } else { // An item with src like iframe, unity games, simpel video etc + ele.src = src; + itemLoaded(ele, options); + } + } else { + // video with child source + if (equal(ele, 'video')) { + each(ele.getElementsByTagName('source'), function(source) { + handleSource(source, _attrSrc, options.src); + }); + ele.load(); + itemLoaded(ele, options); + } else { + if (options.error) options.error(ele, "missing"); + addClass(ele, options.errorClass); + } + } + } + } + + function itemLoaded(ele, options) { + addClass(ele, options.successClass); + if (options.success) options.success(ele); + // cleanup markup, remove data source attributes + removeAttr(ele, options.src); + removeAttr(ele, options.srcset); + each(options.breakpoints, function(object) { + removeAttr(ele, object.src); + }); + } + + function handleSource(ele, attr, dataAttr) { + var dataSrc = getAttr(ele, dataAttr); + if (dataSrc) { + setAttr(ele, attr, dataSrc); + removeAttr(ele, dataAttr); + } + } + + function handleSources(ele, src, srcset){ + if(srcset) { + setAttr(ele, _attrSrcset, srcset); //srcset + } + ele.src = src; //src + } + + function setAttr(ele, attr, value){ + ele.setAttribute(attr, value); + } + + function getAttr(ele, attr) { + return ele.getAttribute(attr); + } + + function removeAttr(ele, attr){ + ele.removeAttribute(attr); + } + + function equal(ele, str) { + return ele.nodeName.toLowerCase() === str; + } + + function hasClass(ele, className) { + return (' ' + ele.className + ' ').indexOf(' ' + className + ' ') !== -1; + } + + function addClass(ele, className) { + if (!hasClass(ele, className)) { + ele.className += ' ' + className; + } + } + + function toArray(options) { + var array = []; + var nodelist = (options.root).querySelectorAll(options.selector); + for (var i = nodelist.length; i--; array.unshift(nodelist[i])) {} + return array; + } + + function saveViewportOffset(offset) { + _viewport.bottom = (window.innerHeight || document.documentElement.clientHeight) + offset; + _viewport.right = (window.innerWidth || document.documentElement.clientWidth) + offset; + } + + function bindEvent(ele, type, fn) { + if (ele.attachEvent) { + ele.attachEvent && ele.attachEvent('on' + type, fn); + } else { + ele.addEventListener(type, fn, { capture: false, passive: true }); + } + } + + function unbindEvent(ele, type, fn) { + if (ele.detachEvent) { + ele.detachEvent && ele.detachEvent('on' + type, fn); + } else { + ele.removeEventListener(type, fn, { capture: false, passive: true }); + } + } + + function each(object, fn) { + if (object && fn) { + var l = object.length; + for (var i = 0; i < l && fn(object[i], i) !== false; i++) {} + } + } + + function throttle(fn, minDelay, scope) { + var lastCall = 0; + return function() { + var now = +new Date(); + if (now - lastCall < minDelay) { + return; + } + lastCall = now; + fn.apply(scope, arguments); + }; + } +}); diff --git a/frontend/drupal/sites/all/libraries/blazy/blazy.min.js b/frontend/drupal/sites/all/libraries/blazy/blazy.min.js new file mode 100755 index 000000000..649abfd84 --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/blazy.min.js @@ -0,0 +1,6 @@ +/*! + hey, [be]Lazy.js - v1.8.2 - 2016.10.25 + A fast, small and dependency free lazy load script (https://github.com/dinbror/blazy) + (c) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy +*/ + (function(q,m){"function"===typeof define&&define.amd?define(m):"object"===typeof exports?module.exports=m():q.Blazy=m()})(this,function(){function q(b){var c=b._util;c.elements=E(b.options);c.count=c.elements.length;c.destroyed&&(c.destroyed=!1,b.options.container&&l(b.options.container,function(a){n(a,"scroll",c.validateT)}),n(window,"resize",c.saveViewportOffsetT),n(window,"resize",c.validateT),n(window,"scroll",c.validateT));m(b)}function m(b){for(var c=b._util,a=0;a=c.left&&b.bottom>=c.top&&b.left<=c.right&&b.top<=c.bottom}function z(b,c,a){if(!t(b,a.successClass)&&(c||a.loadInvisible||0=window.screen.width)return u=a.src,!1});setTimeout(function(){q(a)})}}); \ No newline at end of file diff --git a/frontend/drupal/sites/all/libraries/blazy/bower.json b/frontend/drupal/sites/all/libraries/blazy/bower.json new file mode 100755 index 000000000..7add7080d --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/bower.json @@ -0,0 +1,34 @@ +{ + "name": "bLazy", + "main": "blazy.js", + "version": "1.8.2", + "description": "A fast lightweight pure JavaScript script for lazy loading and multi-serving images, iframes, videos and more.", + "homepage": "https://github.com/dinbror/blazy", + "keywords": [ + "blazy", + "lazy", + "lazyload", + "lazyloading", + "retina", + "responsive", + "image", + "images", + "picture", + "srcset", + "javascript", + "performance" + ], + "authors": [ + { + "name": "Bjørn Klinggaard", + "url": "http://dinbror.dk", + "email": "hello@dinbror.dk" + } + ], + "license": "MIT", + "moduleType": [ + "amd", + "CommonJS" + ], + "ignore": [] +} diff --git a/frontend/drupal/sites/all/libraries/blazy/package.json b/frontend/drupal/sites/all/libraries/blazy/package.json new file mode 100755 index 000000000..d42a4d307 --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/package.json @@ -0,0 +1,40 @@ +{ + "name": "blazy", + "version": "1.8.2", + "description": "A fast lightweight pure JavaScript script for lazy loading and multi-serving images, iframes, videos and more.", + "main": "blazy.js", + "keywords": [ + "blazy", + "blazyjs", + "blazy.js", + "lazy", + "lazyload", + "lazyloading", + "image", + "images", + "picture", + "srcset", + "iframe", + "video", + "unity", + "retina", + "responsive", + "performance" + ], + "author": "Bjørn Klinggaard (http://dinbror.dk/blazy)", + "repository": { + "type": "git", + "url": "git://github.com/dinbror/blazy.git" + }, + "bugs": { + "url": "https://github.com/dinbror/blazy/issues" + }, + "license": "MIT", + "homepage": "https://github.com/dinbror/blazy", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "directories": { + "example": "example" + } +} diff --git a/frontend/drupal/sites/all/libraries/blazy/polyfills/closest.js b/frontend/drupal/sites/all/libraries/blazy/polyfills/closest.js new file mode 100755 index 000000000..83c8f5612 --- /dev/null +++ b/frontend/drupal/sites/all/libraries/blazy/polyfills/closest.js @@ -0,0 +1,29 @@ +// Polyfill for Element.closest that falls back to Element.matches that falls back to querySelectorAll +// Created for blazy.js 1.8.1 - https://github.com/dinbror/blazy to ensure IE7+ support + + +(function () { + if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function(s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s), + i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1; + }; + } + + if (!Element.prototype.closest) { + Element.prototype.closest = Element.prototype.closest || + function(selector) { + var element = this; + while (element.matches && !element.matches(selector)) element = element.parentNode; + return element.matches ? element : null; + }; + } +})(); \ No newline at end of file diff --git a/frontend/drupal/sites/all/libraries/modernizr/modernizr-custom.js b/frontend/drupal/sites/all/libraries/modernizr/modernizr-custom.js old mode 100644 new mode 100755 diff --git a/frontend/drupal/sites/all/modules/advagg/LICENSE.txt b/frontend/drupal/sites/all/modules/advagg/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/frontend/drupal/sites/all/modules/advagg/README.md b/frontend/drupal/sites/all/modules/advagg/README.md new file mode 100644 index 000000000..8740c676c --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/README.md @@ -0,0 +1,806 @@ + +ADVANCED CSS/JS AGGREGATION MODULE +================================== + + +CONTENTS OF THIS FILE +--------------------- + + - Introduction + - Requirements + - Recommended modules + - Installation + - How to get a high PageSpeed score + - JSMin PHP Extension + - Brotli PHP Extension + - Zopfli PHP Extension + - nginx Configuration + - JavaScript Bookmarklet + - Troubleshooting + - Features & benefits + - Configuration + - Additional options for `drupal_add_css/js` functions + - Technical Details & Hooks + + +INTRODUCTION +------------ + +The Advanced CSS/JS Aggregation allows you to improve the frontend performance +of your site. Be sure to do a before and after comparison by using Google's +PageSpeed Insights and WebPagetest.org. +https://developers.google.com/speed/pagespeed/insights/ +http://www.webpagetest.org/easy + + +REQUIREMENTS +------------ + +No special requirements. + + +RECOMMENDED MODULES +------------------- + + - Libraries (https://www.drupal.org/project/libraries) + Allows for 3rd party code for minification to be used by AdvAgg. + + +INSTALLATION +------------ + + - Install as you would normally install a contributed Drupal module. Visit: + https://drupal.org/documentation/install/modules-themes/modules-7 + for further information. + + +HOW TO GET A HIGH PAGESPEED SCORE +--------------------------------- + +Be sure to check the site after every section to make sure the change didn't +mess up your site. The changes under AdvAgg Modifier are usually the most +problematic but they offer the biggest improvements. + +#### Advanced CSS/JS Aggregation #### +Go to `admin/config/development/performance/advagg` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Compress Javascript #### +Install AdvAgg Compress Javascript if not enabled and go to +`admin/config/development/performance/advagg/js-compress` + + - Select JSMin if available; otherwise select JSMin+ + - Select Strip everything (smallest files) + - Save configuration + - Click the batch compress link to process these files at the top. + +#### AdvAgg Async Font Loader #### +Install AdvAgg Async Font Loader if not enabled and go to +`admin/config/development/performance/advagg/font` + + - Select Local file included in aggregate (version: X.X.X). If this option is + not available follow the directions right below the options on how to install + it. + +Keep the 2 checkboxes checked. + +#### AdvAgg Bundler #### +Install AdvAgg Bundler if not enabled and go to +`admin/config/development/performance/advagg/bundler` + +If your server supports HTTP 2 then select "Use HTTP 2.0 settings"; otherwise +leave it at the "Use HTTP 1.1 settings". + +#### AdvAgg Relocate #### +Install AdvAgg Relocate if not enabled and go to +`admin/config/development/performance/advagg/relocate` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Modifier #### +Install AdvAgg Modifier if not enabled and go to +`admin/config/development/performance/advagg/mod` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Critical CSS module #### +Install AdvAgg Critical CSS if not enabled and go to +`admin/config/development/performance/advagg/critical-css` + +These are the directions for the front page of your site. + +Under Add Critical CSS +- Select the theme that is your front page; usually the default is correct. +- User type should be set to 'anonymous' under most circumstances. +- Type of lookup, select URL +- Value to lookup, type in `` +- Critical CSS, paste in the generated CSS from running your homepage url +through https://www.sitelocity.com/critical-path-css-generator which is inside +of the 'Critical Path CSS' textarea on the sitelocity page. +- Click Save Configuration. + +Other landing pages should have their critical CSS added as well. If you have +Google Analytics this will show you how to find your top landing pages +https://developers.google.com/analytics/devguides/reporting/core/v3/common-queries#top-landing-pages +or for Piwik https://piwik.org/faq/how-to/faq_160/. You can also use this +chrome browser plugin to generate critical CSS +https://chrome.google.com/webstore/detail/critical-style-snapshot/gkoeffcejdhhojognlonafnijfkcepob?hl=en + + +JSMIN PHP EXTENSION +------------------- + +The AdvAgg JS Compress module can take advantage of jsmin.c. JavaScript parsing +and minimizing will be done in C instead of PHP dramatically speeding up the +process. If using PHP 5.3.10 or higher https://github.com/sqmk/pecl-jsmin is +recommended. If using PHP 5.3.9 or lower +http://www.ypass.net/software/php_jsmin/ is recommended. + + +BROTLI PHP EXTENSION +-------------------- + +The AdvAgg module can take advantage of Brotli compression. Install this +extension to take advantage of it. Should reduce CSS/JS files by 20%. +https://github.com/kjdev/php-ext-brotli + + +ZOPFLI PHP EXTENSION +-------------------- + +The AdvAgg module can take advantage of the Zopfli compression algorithm. +Install this extension to take advantage of it. This gives higher gzip +compression ratios compared to stock PHP. +https://github.com/kjdev/php-ext-zopfli + + +NGINX CONFIGURATION +------------------- + +https://drupal.org/node/1116618 +Note that @drupal (last line of code below) might be @rewrite or @rewrites +depending on your servers configuration. If there are image style rules in your +Nginx configuration add this right below that. If you want to have brotli +support https://github.com/google/ngx_brotli is how to install that; add +`brotli_static on;` right above `gzip_static on;` in the configuration below. + + ### + ### advagg_css and advagg_js support + ### + location ~* files/advagg_(?:css|js)/ { + gzip_static on; + access_log off; + expires max; + add_header ETag ""; + add_header Cache-Control "max-age=31449600, no-transform, public"; + try_files $uri $uri/ @drupal; + } + +Also noted that some ready made nginx configurations add in a Last-Modified +header inside the advagg directories. These should be removed. + + +JAVASCRIPT BOOKMARKLET +---------------------- + +You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. +See http://en.wikipedia.org/wiki/Bookmarklet for more details. + + javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); + + +TROUBLESHOOTING +--------------- + +If the core Fast 404 Pages functionality is enabled via settings.php, the +settings must be changed in order for the on-demand file compilation to work. +Change this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; + +to this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles|advagg_(cs|j)s)\//'; + +Similarly, if the Fast_404 module is enabled, the 'fast_404_string_whitelisting' +variable must be set inside of settings.php. Add this to your settings.php file: + + $conf['fast_404_string_whitelisting'][] = '/advagg_'; + + +Modules like the Central Authentication Services https://drupal.org/project/cas +will redirect all anonymous requests to a login page. Most of the time there is +a setting that allows certain pages to be excluded from the redirect. You should +add the following to those exclusions. Note that sites/default/files is the +location of you public file system (public://) so you might have to adjust this +to fit your setup. services/* is the default (`CAS_EXCLUDE`) and +`httprl_async_function_callback` is needed if httprl will be used. + + services/* + sites/default/files/advagg_css/* + sites/default/files/advagg_js/* + httprl_async_function_callback + +In the example of CAS this setting can be found on the `admin/config/people/cas` +page and under Redirection there should be a setting called "Excluded Pages". + + +If Far-Future headers are not being sent out and you are using Apache here are +some tips to hopefully get it working. For Apache enable `mod_rewrite`, +`mod_headers`, and `mod_expires`. Add the following code to the bottom of +Drupal's core .htaccess file (located at the webroot level). + + + # No mod_headers. Apache module headers is not enabled. + + # No mod_expires. Apache module expires is not enabled. + + # Use ETags. + FileETag MTime Size + + + + # Use Expires Directive if apache module expires is enabled. + + # Do not use ETags. + FileETag None + # Enable expirations. + ExpiresActive On + # Cache all aggregated css/js files for 52 weeks after access (A). + ExpiresDefault A31449600 + + + # Use Headers Directive if apache module headers is enabled. + + # Do not use etags for cache validation. + Header unset ETag + + # Set a far future Cache-Control header to 52 weeks. + Header set Cache-Control "max-age=31449600, no-transform, public" + + + Header append Cache-Control "no-transform, public" + + + + # Force advagg .js file to have the type of application/javascript. + + ForceType application/javascript + + + +If pages on the site stop working correctly or looks broken after Advanced +CSS/JS Aggregation is enabled, the first step should be to validate the +individual CSS and/or JS files using the included `advagg_validator` module - +something as simple as an errant unfinished comment in one file may cause entire +aggregates of files to be ignored. + + +If AdvAgg was installed via drush sometimes directory permissions need to be +fixed. Using `chown -R` on the advagg directories usually solves this issue. + + +If hosting on Pantheon, you might need to add this to your settings.php file if +you get Numerous login prompts after enabling Adv Agg module on Pantheon Test +and Live instances. + + if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) { + // NO trailing slash when setting the $base_url variable. + switch ($_SERVER['PANTHEON_ENVIRONMENT']) { + case 'dev': + $base_url = 'http://dev-sitename.gotpantheon.com'; + break; + + case 'test': + $base_url = 'http://test-sitename.gotpantheon.com'; + break; + + case 'live': + $base_url = 'http://www.domain.tld'; + break; + } + // Remove a trailing slash if one was added. + if (!empty($base_url)) { + $base_url = rtrim($base_url, '/'); + } + } + + +If you're getting the "HTTP requests to advagg are not getting though" error, +you can try to fix it by making sure the `$base_url` is correctly set for +production and not production environments. + + +If you're getting mixed content error for CSS JS files over HTTPS then you can +try to redirect all http traffic to be https. + + RewriteCond %{HTTPS} off + RewriteCond %{HTTP:X-Forwarded-Proto} !https + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + + +If brotli compression is not working and you are using Apache here are some tips +to hopefully get it working. For Apache enable `mod_rewrite`, `mod_headers`, and +`mod_expires`. Add the following code right above this line `# Rules to +correctly serve gzip compressed CSS and JS files.` + + + # Serve brotli compressed CSS if they exist and the client accepts br. + RewriteCond %{HTTP:Accept-encoding} br + RewriteCond %{REQUEST_FILENAME}\.br -s + RewriteRule ^(.*)\.css $1\.css\.br [QSA] + RewriteRule \.css\.br$ - [T=text/css,E=no-gzip:1] + + # Serve brotli compressed JS if they exist and the client accepts br. + RewriteCond %{HTTP:Accept-encoding} br + RewriteCond %{REQUEST_FILENAME}\.br -s + RewriteRule ^(.*)\.js $1\.js\.br [QSA] + RewriteRule \.js\.br$ - [T=application/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding br + # Force proxies to cache compressed and non-compressed css/js files + # separately. + Header append Vary Accept-Encoding + + + + +If you are having 5 minute or longer timeouts on the admin/reports/status page +then you might need to use an alternative to drupal_httP_request(). The cURL +HTTP Request module https://www.drupal.org/project/chr might fix this issue. + + +FEATURES & BENEFITS +------------------- + +**Advanced CSS/JS Aggregation core features** + + - On demand generation of CSS/JS Aggregates. If the file doesn't exist it will + be generated on demand. + - Stampede protection for CSS and JS aggregation. Uses locking so multiple + requests for the same thing will result in only one thread doing the work. + - Fully cached CSS/JS assets allow for zero file I/O if the Aggregated file + already exists. Results in better page generation performance. + - Smarter aggregate deletion. CSS/JS aggregates only get removed from the + folder if they have not been used/accessed in the last 30 days. + - Smarter cache flushing. Scans all CSS/JS files that have been added to any + aggregate; if that file has changed then flush the correct caches so the + changes go out. The new name ensures changes go out when using CDNs. + - One can add JS to any region of the theme & have it aggregated. + - Url query string to turn off aggregation for that request. `?advagg=0` will + turn off file aggregation if the user has the "bypass advanced aggregation" + permission. `?advagg=-1` will completely bypass all of Advanced CSS/JS + Aggregations modules and submodules. `?advagg=1` will enable Advanced CSS/JS + Aggregation if it is currently disabled. + - Button on the admin page for dropping a cookie that will turn off file + aggregation. Useful for theme development. + - Gzip support. All aggregated files can be pre-compressed into a .gz file and + served from Apache. This is faster then gzipping the file on each request. + +**Included submodules** + + - `advagg_bundler`: + Smartly groups files together - given a target number of CSS/JS aggregates, + this will try very hard to meet that goal. + - `advagg_css_cdn`: + Load CSS libraries from a public CDN; currently only supports Google's CDN. + - `advagg_css_compress`: + Compress the compiled CSS files using a 3rd party compressor; currently + supports YUI (included). + - `advagg_js_cdn`: + Load JavaScript libraries from a public CDN; currently only supports Google's + CDN. + - `advagg_js_compress`: + Compress the compiled JavaScript files using a 3rd party compressor; + currently supports JSMin+ (included). + - `advagg_mod`: + Includes additional tweaks that may not work for all sites: + - Force preprocessing for all CSS/JS. + - Move JS to footer. + - Add defer tag to all JS. + - Defer loading of CSS. + - Inline all CSS/JS for given paths. + - Use a shared directory for a unified multisite. + - `advagg_validator`: + Validate all CSS files using jigsaw.w3.org. Check all CSS files with CSSLint. + Check all JS files with JSHint. + + +CONFIGURATION +------------- + +Settings page is located at: +`admin/config/development/performance/advagg` + +**Global Options** + + - Enable Advanced Aggregation: Check this to start using this module. You can + also quickly disable the module here. For testing purposes, this has the same + effect as placing `?advagg=-1` in the URL. Disabled by default. + - Use Cores Grouping Logic: Leave this checkbox enabled until you are ready to + begin exploring the AdvAgg Bundler sub-module which overrides Core's + functionality. This groups files just like Core does so should just work. + Enabled by default. You will also have to disable this checkbox if you wish + to enable some of the CSS Options below on this settings page. + - Use HTTPRL to generate aggregates: If the HTTPRL module is enabled - + https://drupal.org/project/httprl - advagg will use it to generate aggregates + on the fly in a background parallel process. Enabling HTTPRL will improve + page generation speeds when a new aggregate is created because the CSS/JS + file creation will happen in a different process. If HTTPRL is installed it + is Enabled by default; otherwise is it Disabled. + - AdvAgg Cache Settings: As a reference, core takes about 25 ms to run. + Development will scan all files for a change on every page load. Normal is + fine for all use cases. Aggressive should be fine in almost all use cases; + if your inline css/js changes based off of a variable then the aggressive + cache hit ratio will be low; if that is the case consider using + Drupal.settings for a better cache hit ratio. + +**Resource Hints** + +Preemptively get resources (CSS/JS & sub requests). This will set tags in the +document head telling the browser to open up connections before they are needed. + + - DNS Prefetch: Start the DNS lookup for external CSS and JavaScript files as + soon as possible. + - Preconnect: Start the connection to external resources before an HTTP request + is actually sent to the server. On HTTPS this can have a dramatic effect. + - Location of resource hints: This only needs to be changed if the above + settings are not working. + - Preload link http headers: If your server supports HTTP/2 push then this + allows for resources to be sent before the browser knows it needs it. + +**Cron Options** + +Adjusting the frequency of how often something happens on cron. + +**Obscure Options** + + - Create .gz files: Check this by default as it will improve your performance. + For every Aggregated file generated, this will create a gzip version of file + and then only serve it out if the browser accepts gzip files compression. + Enabled by default. + - Create .br files: Check this by default as it will improve your performance. + For every Aggregated file generated, this will create a brotli version of + file and then only serve it out if the browser accepts gzip files + compression. Enabled by default IF the Brotli Extension for PHP is installed. + See https://github.com/kjdev/php-ext-brotli + - Run advagg_ajax_render_alter(): Turn this off if you're having issues with + ajax. Also keep in mind that the max_input_vars setting can cause issues if + you are submitting a lot of data. + - Include the base_url variable in the hooks hash array: Enabled only if you + know you need it. + - Convert absolute paths to be self references: Turn on unless pages are used + inside of an iframe. + - Convert absolute paths to be protocol relative paths: Safe to use unless you + need to support IE6. + - Convert http:// to https://: Usually not needed, but here in case you do. + - Do not run CSS url() values through file_create_url(): Usually not needed, + but here in case you do. + + +**CSS Options & JS Options** + + - Combine CSS files by using media queries: "Use cores grouping logic" needs to + be unchecked in order for this to work. Also noted is that due to an issue + with IE9, compatibility mode is forced off if this is enabled by adding this + tag in the html head: + + + + Disabled by default. + - Prevent more than 4095 CSS selectors in an aggregated CSS file: Internet + Explorer before version 10; IE9, IE8, IE7, & IE6 all have 4095 as the limit + for the maximum number of css selectors that can be in a file. Enabling this + will prevent CSS aggregates from being created that exceed this limit. For + more information see + http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx Disabled + by default. + - Fix improperly set type (CSS/JS): If type is external but does not start with + http, https, or // change it to be type file. If type is file but it starts + with http, https, or // change type to be external. + +**Information page** + +located at `admin/config/development/performance/advagg/info`. This page +provides debugging information. There are no configuration options here. + + - Hook Theme Info: Displays the `process_html` order. Used for debugging. + - Hook Render Info: Displays the scripts and styles render callbakcs. Used for + debugging. + - CSS files: Displays how often a file has changed. + - JS files: Displays how often a file has changed. + - Modules implementing AdvAgg CSS/JS hooks: Lets you know what modules are + using advagg. + - AdvAgg CSS/JS hooks implemented by modules: Lets you know what advagg hooks + are in use. + - Hooks And Variables Used In Hash: Show what is used to calculate the 3rd hash + of an aggregates filename. + - Get detailed info about an aggregate file: Look up detailed array about any + CSS or JS file listed. + +**Operations page** + +located at `admin/config/development/performance/advagg/operations`. This is a +collection of commands to control the cache and to manage testing of this +module. In general this page is useful when troubleshooting some aggregation +issues. For normal operations, you do not need to do anything on this page below +the Smart Cache Flush. There are no configuration options here. + + - Smart Cache Flush + - Flush AdvAgg Cache: Scan all files referenced in aggregated files. If + any of them have changed, increment the counters containing that file and + rebuild the bundle. + + - Aggregation Bypass Cookie + - Toggle The "aggregation bypass cookie" For This Browser: This will set or + remove a cookie that disables aggregation for the remainder of the browser + session. It acts almost the same as adding `?advagg=0` to every URL. + + - Cron Maintenance Tasks + - Clear All Stale Files: Scan all files in the `advagg_css/js` directories + and remove the ones that have not been accessed in the last 30 days. + - Delete Orphaned Aggregates: Scan CSS/JS advagg dir and remove file if + there is no associated db record. + - Clear Missing Files From the Database: Scan for missing files and remove + the associated entries in the database. + - Delete Unused Aggregates from Database: Delete aggregates that have not + been accessed in the last 6 weeks. + - Delete orphaned/expired advagg locks from the semaphore database table. + - Delete leftover temporary files: Delete old temporary files from the + filesystem. + + - Drastic Measures + - Clear All Caches: Remove all data stored in the advagg cache bins. + - Clear All Files: Remove All Generated Files. Remove all files in the + `advagg_(css|js)` directories. + - Force New Aggregates: Increment Global Counter. Force the creation of all + new aggregates by incrementing a global counter. + - Rescan All Files: Force rescan all files and clear the cache. Useful if a + css/js change from a deployment did not work. + - Remove deleted files in the advagg_files table: Remove entries in the + advagg_files table that have a filesize of 0 and delete the + javascript_parsed variable. This gets around the grace period that the + cron cleanup does. + - Reset the AdvAgg Files table: Truncate the advagg_files table and delete + the javascript_parsed variable. This may cause some 404s for CSS/JS assets + for a short amount of time (seconds). Useful if you really want to reset + some stuff. Might be best to put the site into maintenance mode before + doing this. + +**Hidden Settings** + +The following settings are not configurable from the admin UI and must be set in +settings.php. In general they are settings that should not be changed. The +current defaults are shown. + + // Display a message that the bypass cookie is set. + $conf['advagg_show_bypass_cookie_message'] = TRUE; + + // Display a message when a css/js file changed while in development mode. + $conf['advagg_show_file_changed_message'] = TRUE; + + // Skip the 404 check on status page. + $conf['advagg_skip_404_check'] = FALSE; + + // Force the scripts #aggregate_callback to always be _advagg_aggregate_js. + $conf['advagg_enforce_scripts_callback'] = TRUE; + + // Default location of AdvAgg configuration items. + $conf['advagg_admin_config_root_path'] = 'admin/config/development/performance'; + + // Run advagg_url_inbound_alter(). + $conf['advagg_url_inbound_alter'] = TRUE; + + // Allow JavaScript insertion into any scope even if theme does not support + // it. + $conf['advagg_scripts_scope_anywhere'] = FALSE; + + // Empty the scripts key inside of template_process_html replacement + // function. + $conf['advagg_scripts_scope_anywhere'] = FALSE; + + // Do more file operations in main thread if the file system is fast. If + // AdvAgg's directories are mounted on something like S3, you might want to + // set this to FALSE. + $conf['advagg_fast_filesystem'] = TRUE; + + // Pregenerate aggregate files. If disable the browser requesting the file + // will cause the generation to happen. If advagg 404 handling is broken + // then setting this to false will break your site in bad ways. + $conf['advagg_pregenerate_aggregate_files'] = TRUE; + + // Set the jQuery UI version. + $conf['advagg_css_cdn_jquery_ui_version'] = '1.8.7'; + + // See if jQuery UI should be grabbed from the Google CDN. + $conf['advagg_css_cdn_jquery_ui'] = TRUE; + + // Set the jQuery UI version. + $conf['advagg_js_cdn_jquery_ui_version'] = '1.8.7'; + + // Set the jQuery version. + $conf['advagg_js_cdn_jquery_version'] = '1.4.4'; + + // Use minification. + $conf['advagg_js_cdn_compression'] = TRUE; + + // See if jQuery UI should be grabbed from the Google CDN. + $conf['advagg_js_cdn_jquery_ui'] = TRUE; + + // See if jQuery should be grabbed from the Google CDN. + $conf['advagg_js_cdn_jquery'] = TRUE; + + // Value for the compression ratio test. + $conf['advagg_js_compress_max_ratio'] = 0.9; + + // Value for the compression ratio test. + $conf['advagg_js_compress_ratio'] = 0.1; + + // Skip far future check on status page. + $conf['advagg_skip_far_future_check'] = FALSE; + + // Skip preprocess and enabled checks. + $conf['advagg_skip_enabled_preprocess_check'] = FALSE; + + // Default root dir for the advagg files; see advagg_get_root_files_dir(). + $conf['advagg_root_dir_prefix'] = 'public://'; + + // How long to wait when writing the aggregate if a file is missing or the + // hash doesn't match. + $conf['advagg_file_read_failure_timeout'] = 3600; + + // If FALSE mtime of files will only trigger a change if they are in the + // future. + $conf['advagg_strict_mtime_check'] = TRUE; + + // Skip 304 check on status page. + $conf['advagg_skip_304_check'] = FALSE; + + // Control how many bytes can be inlined. + $conf['advagg_mod_css_defer_inline_size_limit'] = 12288; + + // Control how many bytes the preload header can use. + $conf['advagg_resource_hints_preload_max_size'] = 3072; + + // If TRUE, only verify 1st hash instead of all 3 of the filename. + $conf['advagg_weak_file_verification'] = FALSE; + + +ADDITIONAL OPTIONS FOR DRUPAL_ADD_CSS/JS FUNCTIONS +-------------------------------------------------- + +AdvAgg extends the available options inside of `drupal_add_css` and +`drupal_add_js`. + +`drupal_add_js` - additional keys for $options. + + - `browsers`: Works the same as the one found in drupal_add_css. + - `onload`: Run this js code when after the js file has loaded. + - `onerror`: Run this js code when if the js file did not load. + - `async`: TRUE - Load this file using async. + - `no_defer`: TRUE - Never defer or async load this js file. + +Both `drupal_add_js` + `drupal_add_css` - additional keys for $options. + + - `scope_lock`: TRUE - Make sure the scope of this will not ever change. + - `movable`: FALSE - Make sure the ordering of this will not ever change. + - `preprocess_lock`: TRUE - Make sure the preprocess key will not ever change. + + +TECHNICAL DETAILS & HOOKS +------------------------- + +**Technical Details** + + - There are five database tables and two cache table used by advagg. + `advagg_schema` documents what they are used for. + - Files are generated by this pattern: + + css__[BASE64_HASH]__[BASE64_HASH]__[BASE64_HASH].css + + The first base64 hash value tells us what files are included in the + aggregate. Changing what files get included will change this value. + + The second base64 hash value is used as a sort of version control; it changes + if any of the base files contents have changed. Changing a base files content + (like drupal.js) will change this value. + + The third base64 hash value records what settings were used when generating + the aggregate. Changing a setting that affects how aggregates get built + (like toggling "Create .gz files") will change this value. + + - To trigger scanning of the CSS / JS file cache to identify new files, run + the following: + + // Trigger reloading the CSS and JS file cache in AdvAgg. + if (module_exists('advagg')) { + module_load_include('inc', 'advagg', 'advagg.cache'); + advagg_push_new_changes(); + } + + - Aggressive Cache Setting: This will fully cache the rendered html generated + by AdvAgg. The cache ID is set by this code: + + // CSS. + $hooks_hash = advagg_get_current_hooks_hash(); + $css_cache_id_full = + 'advagg:css:full:' . $hooks_hash . ':' . + drupal_hash_base64(serialize($full_css)); + // JS. + $hooks_hash = advagg_get_current_hooks_hash(); + $js_cache_id_full = + 'advagg:js:full:' . $hooks_hash . ':' . + drupal_hash_base64(serialize($js_scope_array)); + + The second and final hash value in this cache id is the css/js_hash value. + This takes the input from `drupal_add_css/js()` and creates a hash value from + it. If a different file is added and/or inline code changed, this hash value + will be different. + + The first hash value will take the current_hooks_hash value which is the + third base64 hash value listed above in this section (Technical Details) as + the first part of the hash. This means that if any value is changed in this + nested array a different cache id will be used. You can see the contents of + this nested array by going to + `admin/config/development/performance/advagg/info` under + "Hooks And Variables Used In Hash". An example of this being properly used is + if you enable the core locale module the language key will appear in the + array. This is needed because the `locale_css_alter` and `locale_js_alter` + functions both use the global $language variable in determining what css or + js files need to be altered. To add in your own context you can use + `hook_advagg_current_hooks_hash_array_alter` to do so. Be careful when doing + so as including something like the user id will make every user have a + different set of aggregate files. + +**Hooks** + +Modify file contents: + + - `advagg_get_css_file_contents_alter`. Modify the data of each file before it + gets glued together into the bigger aggregate. Useful for minification. + - `advagg_get_js_file_contents_alter`. Modify the data of each file before it + gets glued together into the bigger aggregate. Useful for minification. + - `advagg_get_css_aggregate_contents_alter`. Modify the data of the complete + aggregate before it gets written to a file. Useful for minification. + - `advagg_get_js_aggregate_contents_alter`. Modify the data of the complete + aggregate before it gets written to a file.Useful for minification. + - `advagg_save_aggregate_alter`. Modify the data of the complete aggregate + allowing one create multiple files from one base file. Useful for gzip + compression. Also useful for mirroring data. + +Modify file names and aggregate bundles: + + - `advagg_current_hooks_hash_array_alter`. Add in your own settings and hooks + allowing one to modify the 3rd base64 hash in a filename. + - `advagg_build_aggregate_plans_alter`. Regroup files into different + aggregates. + - `advagg_css_groups_alter`. Allow other modules to modify `$css_groups` right + before it is processed. + - `advagg_js_groups_alter`. Allow other modules to modify `$js_groups` right + before it is processed. + +Others: + + - `advagg_hooks_implemented_alter`. Tell advagg about other hooks related to + advagg. + - `advagg_get_root_files_dir_alter`. Allow other modules to alter css and js + paths. + - `advagg_modify_css_pre_render_alter`. Allow other modules to modify $children + & $elements before they are rendered. + - `advagg_modify_js_pre_render_alter`. Allow other modules to modify $children + & $elements before they are rendered. + - `advagg_changed_files`. Let other modules know about the changed files. + - `advagg_removed_aggregates`. Let other modules know about removed aggregates. + - `advagg_scan_for_changes`. Let other modules see if files related to this + file has changed. Useful for detecting changes to referenced images in css. + - `advagg_get_info_on_files_alter`. Let other modules modify information about + the base CSS/JS files. + - `advagg_context_alter`. Allow other modules to swap important contextual + information on generation. + - `advagg_bundler_analysis`. If the bundler module is installed allow for other + modules to change the bundler analysis. diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg.admin.inc new file mode 100644 index 000000000..80601001e --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.admin.inc @@ -0,0 +1,1965 @@ + t('Use default (safe) settings'), + 2 => t('Use recommended (optimized) settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_admin_mode', ADVAGG_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible.
    Recommended settings are optimized for speed."), + ); + + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_admin_mode"]' => array('value' => '4'), + ), + ), + ); + + // Simple checkbox settings. + $form['global_container']['global'] = array( + '#type' => 'fieldset', + '#title' => t('Global Options'), + ); + $form['global_container']['global']['advagg_enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enable advanced aggregation (recommended)'), + '#default_value' => variable_get('advagg_enabled', ADVAGG_ENABLED), + '#description' => t('Uncheck this box to completely disable AdvAgg functionality.'), + '#recommended_value' => TRUE, + ); + $advagg_core_groups_default = variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS); + if (variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA) + || variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + || (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE))) { + $advagg_core_groups_default = FALSE; + } + $form['global_container']['global']['advagg_core_groups'] = array( + '#type' => 'checkbox', + '#title' => t('Use cores grouping logic'), + '#default_value' => $advagg_core_groups_default, + '#description' => t('Will group files just like core does.'), + '#recommended_value' => FALSE, + '#states' => array( + 'enabled' => array( + '#edit-advagg-combine-css-media' => array('checked' => FALSE), + '#edit-advagg-ie-css-selector-limiter' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['global']['advagg_use_httprl'] = array( + '#type' => 'checkbox', + '#title' => t('Use HTTPRL to generate aggregates (recommended)'), + '#default_value' => module_exists('httprl') ? variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) : FALSE, + '#disabled' => module_exists('httprl') ? FALSE : TRUE, + '#description' => t('If HTTPRL is installed, advagg will use it to generate aggregates on the fly in a background parallel process.', array('@link' => 'http://drupal.org/project/httprl')), + '#recommended_value' => TRUE, + ); + + $aggressive_cache_conflicts = advagg_aggressive_cache_conflicts(); + if (empty($aggressive_cache_conflicts)) { + $description = t('It appears that there are no incompatible modules, so you should be able to safely use the Aggressive cache.'); + } + else { + $description = t('It appears that there might be some incompatible modules. I would test these out before setting the cache to aggressive: %modules', array('%modules' => implode(', ', $aggressive_cache_conflicts))); + } + $options = array( + -1 => t('Development ~ 300ms'), + 1 => t('Normal ~ 60ms'), + 3 => t('Render Cache ~ 30ms (recommended)'), + 5 => t('Aggressive Render Cache ~ 10ms'), + ); + $form['global_container']['global']['advagg_cache_level'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Cache Settings'), + '#default_value' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL), + '#options' => $options, + '#recommended_value' => 3, + '#description' => t("As a reference, core takes about 25 ms to run. Development will scan all files for a change on every page load. Normal and the render cache is fine for all use cases. Aggressive should be fine for most use cases. If your inline css/js changes based off of a variable then the cache hit ratio will be low; if that is the case consider using Drupal.settings for a better cache hit ratio when using the render cache. The aggressive render cache will cache the output from js_alter and css_alter. !description", array( + '@information' => url($config_path . '/advagg/info', array( + 'fragment' => 'edit-hooks-implemented', + )), + '!description' => $description, + )), + ); + $stream_wrappers = file_get_stream_wrappers(); + $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX); + if ($prefix !== 'public://' + || $stream_wrappers['public']['class'] !== 'DrupalPublicStreamWrapper' + || count($stream_wrappers) > 2 + || file_default_scheme() !== 'public' + ) { + $stream_wrappers_string = array(); + foreach ($stream_wrappers as $key => $values) { + $stream_wrappers_string[] = t("@key:// uses @class; @description", array( + '@key' => $key, + '@class' => $values['class'], + '@description' => $values['description'], + )); + } + $stream_wrappers_string = implode("
    \n", $stream_wrappers_string); + $form['global_container']['global']['advagg_root_dir_prefix'] = array( + '#type' => 'textfield', + '#title' => t('Stream wrapper for AdvAgg.'), + '#default_value' => $prefix, + '#description' => t("Options:
    \n!stream_wrappers", array( + '!stream_wrappers' => $stream_wrappers_string, + )), + ); + } + + $form['global_container']['global']['dev_container'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="advagg_cache_level"]' => array('value' => '-1'), + ), + ), + ); + // Show msg about advagg css compress. + if (module_exists('advagg_css_compress') && (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) > 0 || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { + $form['global_container']['global']['dev_container']['advagg_css_compress_msg'] = array( + '#markup' => '

    ' . t('The AdvAgg CSS Compression module is disabled when in development mode.', array('@css' => url($config_path . '/advagg/css-compress'))) . '

    ', + ); + } + // Show msg about advagg js compress. + if (module_exists('advagg_js_compress') && (variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) || variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE))) { + $form['global_container']['global']['dev_container']['advagg_js_compress_msg'] = array( + '#markup' => '

    ' . t('The AdvAgg JS Compression module is disabled when in development mode.', array('@js' => url($config_path . '/advagg/js-compress'))) . '

    ', + ); + } + + // Show msg about the jquery update compression setting. + if (module_exists('jquery_update')) { + if (variable_get('jquery_update_compression_type', 'min') === 'min') { + $form['global_container']['global']['dev_container']['advagg_jquery_update_development'] = array( + '#markup' => '

    ' . t('You might want to change the jQuery update compression level to "Development" as well.', array( + '!url' => url('admin/config/development/jquery_update'), + )) . '

    ', + ); + } + else { + $form['global_container']['global']['prod_container'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="advagg_cache_level"]' => array('!value' => '-1'), + ), + ), + ); + $form['global_container']['global']['prod_container']['advagg_jquery_update_development'] = array( + '#markup' => '

    ' . t('You might want to change the jQuery update compression level to "Production" as well.', array( + '!url' => url('admin/config/development/jquery_update'), + )) . '

    ', + ); + } + } + + $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH); + $advagg_resource_hints_preconnect = variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT); + $advagg_resource_hints_preload = variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD); + $form['global_container']['global']['resource_hints'] = array( + '#type' => 'fieldset', + '#title' => t('Resource Hints'), + '#collapsible' => TRUE, + '#collapsed' => ($advagg_resource_hints_dns_prefetch || $advagg_resource_hints_preconnect || $advagg_resource_hints_preload) ? FALSE : TRUE, + '#description' => t('Preemptively get resources (CSS/JS & sub requests).'), + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_dns_prefetch'] = array( + '#type' => 'checkbox', + '#title' => t('DNS Prefetch (recommended).'), + '#default_value' => $advagg_resource_hints_dns_prefetch, + '#disabled' => '', + '#description' => t('Start the DNS lookup for external CSS and JavaScript files as soon as possible.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_preconnect'] = array( + '#type' => 'checkbox', + '#title' => t('Preconnect (recommended).'), + '#default_value' => $advagg_resource_hints_preconnect, + '#disabled' => '', + '#description' => t('Start the connection to external resources before an HTTP request is actually sent to the server.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_location'] = array( + '#type' => 'radios', + '#title' => t('Location of resource hints.'), + '#default_value' => variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION), + '#description' => t('If you have css and/or js files being loaded right after the "charset=utf-8" meta tag, you need to use the "above charset=utf-8" option in order for some of the resource hints to work; Optimizely is an example that would need this other option. Link headers are not supported in every browser.'), + '#recommended_value' => 1, + '#options' => array( + 1 => t('Below charset=utf-8 (recommended)'), + 3 => t('Above charset=utf-8'), + ), + '#states' => array( + 'disabled' => array( + '#edit-advagg-resource-hints-dns-prefetch' => array('checked' => FALSE), + '#edit-advagg-resource-hints-preconnect' => array('checked' => FALSE), + ), + ), + ); + + // Preload Section. + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload'] = array( + '#type' => 'checkbox', + '#title' => t('Preload link http headers.'), + '#default_value' => $advagg_resource_hints_preload, + '#disabled' => '', + '#description' => t('Use link http headers to send out preload hints for local and external resources.'), + '#recommended_value' => FALSE, + ); + $advagg_resource_hints_preload_settings = advagg_get_resource_hints_preload_settings(); + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Preload Ordering and Settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Change the order of what preload items get sent first. Can also control what will be sent and if HTTP/2 push should be used (if your server supports it). If your webserver supports HTTP/2 Push, and it is enabled, the server will send every asset flagged to be pushed to the browser on every request, ignoring the browser cache (not a good thing for repeat visits). More complex server configuration will be needed to make this efficient; no solutions are currently available.'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-resource-hints-preload' => array('checked' => FALSE), + ), + ), + ); + // Build the rows. + foreach ($advagg_resource_hints_preload_settings as $id => $entry) { + // Build the table rows. + $rows[$id] = array( + 'data' => array( + // Cell for the cross drag and drop element. + array('class' => array('entry-cross')), + // Weight item for the tabledrag. + array( + 'data' => array( + '#type' => 'weight', + '#title' => t('Weight'), + '#title_display' => 'invisible', + '#default_value' => $entry['#weight'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'weight', + ), + '#attributes' => array( + 'class' => array('entry-order-weight'), + ), + ), + ), + check_plain($entry['title']), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('Enable'), + '#title_display' => 'invisible', + '#default_value' => $entry['enabled'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'enabled', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('HTTP/2 Push'), + '#title_display' => 'invisible', + '#default_value' => $entry['push'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'push', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('Local'), + '#title_display' => 'invisible', + '#default_value' => $entry['local'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'local', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('External'), + '#title_display' => 'invisible', + '#default_value' => $entry['external'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'external', + ), + ), + ), + ), + 'class' => array('draggable'), + ); + // Build rows of the form elements in the table. + $row_elements[$id] = array( + 'weight' => &$rows[$id]['data'][1]['data'], + 'enabled' => &$rows[$id]['data'][3]['data'], + 'push' => &$rows[$id]['data'][4]['data'], + 'local' => &$rows[$id]['data'][5]['data'], + 'external' => &$rows[$id]['data'][6]['data'], + ); + } + + // Add the table to the form. + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_table'] = array( + '#theme' => 'table', + // The row form elements need to be processed and build, + // therefore pass them as element children. + 'elements' => $row_elements, + '#header' => array( + // We need two empty columns for the weight field and the cross. + array('data' => NULL, 'colspan' => 2), + t('Name'), + t('Enabled'), + t('HTTP/2 Push'), + t('Local'), + t('External'), + ), + '#rows' => $rows, + '#attributes' => array('id' => 'entry-order'), + ); + drupal_add_tabledrag('entry-order', 'order', 'sibling', 'entry-order-weight'); + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_resource_hints_preload_reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + '#submit' => array('advagg_admin_resource_hints_preload_reset'), + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_use_immutable'] = array( + '#type' => 'checkbox', + '#title' => t('Send immutable header for all aggregated files (recommended).'), + '#default_value' => variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE), + '#description' => t('Have Apache send Cache-Control: immutable for all aggregated files. Current browser support', array( + '@url1' => 'http://bitsup.blogspot.de/2016/05/cache-control-immutable.html', + '@url2' => 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility', + )), + '#recommended_value' => TRUE, + ); + + $form['global_container']['global']['cron'] = array( + '#type' => 'fieldset', + '#title' => t('Cron Options'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Unless you have a good reason to adjust these values you should leave them alone.'), + ); + // @codingStandardsIgnoreStart + $short_times = drupal_map_assoc(array( + 60 * 15, // 15 min. + 60 * 30, // 30 min. + 60 * 45, // 45 min. + 60 * 60, // 1 hour. + 60 * 60 * 2, // 2 hours. + 60 * 60 * 4, // 4 hours. + 60 * 60 * 6, // 6 hours. + 60 * 60 * 8, // 8 hours. + 60 * 60 * 10, // 10 hours. + 60 * 60 * 12, // 12 hours. + 60 * 60 * 18, // 18 hours. + 60 * 60 * 23, // 23 hours. + 60 * 60 * 24, // 1 day. + 60 * 60 * 24 * 2, // 2 days. + ), 'format_interval'); + $long_times = drupal_map_assoc(array( + 60 * 60 * 24 * 2, // 2 days. + 60 * 60 * 24 * 3, // 3 days. + 60 * 60 * 24 * 4, // 4 days. + 60 * 60 * 24 * 5, // 5 days. + 60 * 60 * 24 * 6, // 6 days. + 60 * 60 * 24 * 7, // 1 week. + 60 * 60 * 24 * 7 * 2, // 2 weeks. + 60 * 60 * 24 * 7 * 3, // 3 weeks. + 60 * 60 * 24 * 30, // 1 month. + 60 * 60 * 24 * 45, // 1 month 2 weeks. + 60 * 60 * 24 * 60, // 2 months. + ), 'format_interval'); + // @codingStandardsIgnoreEnd + $last_ran = variable_get('advagg_cron_timestamp', 0); + if (!empty($last_ran)) { + $last_ran = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $last_ran))); + } + else { + $last_ran = t('never'); + } + + $form['global_container']['global']['cron']['advagg_cron_frequency'] = array( + '#type' => 'select', + '#options' => $short_times, + '#title' => 'Minimum amount of time between advagg_cron() runs.', + '#default_value' => variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY), + '#description' => t('The default value for this is %value. The last time advagg_cron was ran is %time.', array( + '%value' => format_interval(ADVAGG_CRON_FREQUENCY), + '%time' => $last_ran, + )), + ); + $form['global_container']['global']['cron']['drupal_stale_file_threshold'] = array( + '#type' => 'select', + '#options' => $long_times, + '#title' => 'Delete aggregates accessed/modified more than a set time ago.', + // @codingStandardsIgnoreLine + '#default_value' => variable_get('drupal_stale_file_threshold', 2592000), + '#description' => t('The default value for this is %value.', array('%value' => format_interval(2592000))), + ); + $form['global_container']['global']['cron']['advagg_remove_missing_files_from_db_time'] = array( + '#type' => 'select', + '#options' => $long_times, + '#title' => 'How long to wait until unaccessed aggregates with missing files are removed from the database.', + '#default_value' => variable_get('advagg_remove_missing_files_from_db_time', ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME), + '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME))), + ); + $form['global_container']['global']['cron']['advagg_remove_old_unused_aggregates_time'] = array( + '#type' => 'select', + '#options' => $long_times, + '#title' => 'How long to wait until unaccessed aggregates are removed from the database.', + '#default_value' => variable_get('advagg_remove_old_unused_aggregates_time', ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME), + '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME))), + ); + + $form['global_container']['global']['obscure'] = array( + '#type' => 'fieldset', + '#title' => t('Obscure Options'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Some of the more obscure AdvAgg settings. Odds are you do not need to change anything in here.'), + ); + $form['global_container']['global']['obscure']['advagg_gzip'] = array( + '#type' => 'checkbox', + '#title' => t('Create .gz files'), + '#default_value' => variable_get('advagg_gzip', ADVAGG_GZIP), + '#description' => t('All aggregated files can be pre-compressed into a .gz file and + served from Apache. This is faster then gzipping the file on each request.'), + ); + $form['global_container']['global']['obscure']['advagg_brotli'] = array( + '#type' => 'checkbox', + '#title' => t('Create .br files'), + '#default_value' => variable_get('advagg_brotli', ADVAGG_BROTLI), + '#description' => t('All aggregated files can be pre-compressed into a .br file and + served from Apache. This is faster then brotli compressing the file on each request.'), + ); + if (!function_exists('brotli_compress') || !defined('BROTLI_TEXT')) { + $form['global_container']['global']['obscure']['advagg_brotli']['#default_value'] = FALSE; + $form['global_container']['global']['obscure']['advagg_brotli']['#disabled'] = TRUE; + $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('The PHP brotli extension is needed in order to enable this feature.', array('@url' => 'https://github.com/kjdev/php-ext-brotli')); + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('If running on windows this is a helpful guide on how to compile PHP extensions. Also noted that IIS has a Brotli extension,', array( + '@url' => 'https://wiki.php.net/internals/windows/stepbystepbuild', + '@iis' => 'https://www.iis.net/downloads/community/2016/03/iis-brotli', + )); + } + } + $form['global_container']['global']['obscure']['advagg_ajax_render_alter'] = array( + '#type' => 'checkbox', + '#title' => t('Run advagg_ajax_render_alter()'), + '#default_value' => variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER), + '#description' => t('If disabled, AdvAgg will not alter the aggregates returned by ajax requests.'), + ); + $form['global_container']['global']['obscure']['advagg_include_base_url'] = array( + '#type' => 'checkbox', + '#title' => t('Include the base_url variable in the hooks hash array.'), + '#default_value' => variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL), + '#description' => t('If you would like a unique set of aggregates for every permutation of the base_url (current value: %value) then enable this setting. Read more.', array( + '%value' => $GLOBALS['base_url'], + '@issue' => 'https://www.drupal.org/node/2353811', + )), + ); + $form['global_container']['global']['obscure']['advagg_convert_absolute_to_relative_path'] = array( + '#type' => 'checkbox', + '#title' => t('Convert absolute paths to be self references.'), + '#default_value' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH), + '#description' => t('If the src to a CSS/JS file points to this domain, convert the absolute path to a relative path. Will also convert url() references inside of css files. This is generally safe unless the drupal page will be embedded, like it is when used as an iframe.'), + ); + $form['global_container']['global']['obscure']['advagg_convert_absolute_to_protocol_relative_path'] = array( + '#type' => 'checkbox', + '#title' => t('Convert absolute paths to be protocol relative paths.'), + '#default_value' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH), + '#description' => t('If the src to a CSS/JS file points starts with http:// or https://, convert it to use a protocol relative path //. Will also convert url() references inside of css files. This is incompatible with IE6 and might cause extra bandwidth usage in IE7 and IE8 in regards to url() statements inside css files. According to current web browser market share IE 6 is less than 0.2% of the total number of browsers in use and IE 7+8 make up under 5% of browsers in use.', array( + '@url' => 'http://www.w3counter.com/trends', + )), + '#states' => array( + 'enabled' => array( + '#edit-advagg-force-https-path' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['global']['obscure']['advagg_force_https_path'] = array( + '#type' => 'checkbox', + '#title' => t('Convert http:// to https://.'), + '#default_value' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH), + '#description' => t('If the src to a CSS/JS file starts with http:// convert it https://. Will also convert url() references inside of css files.'), + '#states' => array( + 'enabled' => array( + '#edit-advagg-convert-absolute-to-protocol-relative-path' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['global']['obscure']['advagg_css_absolute_path'] = array( + '#type' => 'checkbox', + '#title' => t('Convert CSS url() to absolute paths if file is outside of the public:// dir.'), + '#default_value' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH), + '#description' => t('Useful for CSS files hosted on S3 where the referenced files inside of url() is not in the same location as the aggregated CSS file.'), + ); + $form['global_container']['global']['obscure']['advagg_skip_file_create_url_inside_css'] = array( + '#type' => 'checkbox', + '#title' => t('Do not run CSS url() values through file_create_url().'), + '#default_value' => variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS), + '#description' => t('If checked, the processing of url() values within CSS will be handled more like core than the CDN module. This will not convert relative paths in your source CSS to absolute paths in the aggregated CSS.'), + ); + $form['global_container']['global']['obscure']['advagg_htaccess_symlinksifownermatch'] = array( + '#type' => 'checkbox', + '#title' => t('Use "Options +SymLinksIfOwnerMatch"'), + '#default_value' => variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH), + '#description' => t('By default the custom .htaccess files are configured to use "Options +FollowSymLinks". Some hosting companies do not support this so "Options +SymLinksIfOwnerMatch" must be used instead.'), + ); + $form['global_container']['global']['obscure']['advagg_disable_on_admin'] = array( + '#type' => 'checkbox', + '#title' => t('Do not use AdvAgg or any aggregation in the admin section.'), + '#default_value' => variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN), + '#description' => t('If checked, AdvAgg will not be used in the /admin section as well as any place the admin theme is used.'), + ); + $form['global_container']['global']['obscure']['advagg_disable_on_listed_pages'] = array( + '#type' => 'textarea', + '#title' => t('Do not use AdvAgg or any aggregation in the following pages'), + '#default_value' => variable_get('advagg_disable_on_listed_pages', ADVAGG_DISABLE_ON_LISTED_PAGES), + '#description' => t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog.", array( + '%blog' => 'blog', + '%blog-wildcard' => 'blog/*', + )), + ); + // Get core htaccess files. + $core_htaccess = advagg_htaccess_rewritebase(); + $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); + if (!empty($core_htaccess)) { + // Get advagg htaccess files. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); + $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); + // Build best guess dir name. + $core_htaccess_directive = trim(str_ireplace('RewriteBase ', '', $core_htaccess)); + $new_dir = str_replace('//', '/', $core_htaccess_directive . '/' . dirname($css_path[1])); + + $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( + '#type' => 'textfield', + '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), + '#default_value' => $advagg_htaccess_rewritebase, + '#description' => t('If gzip and/or brotli files are getting a 307 instead of a 200, this might fix the issue. This Drupal install is using "@core_htaccess" in the webroot .htaccess file. Based off of this and the current location of the advagg css/js directories (@css_path, @js_path) this is the recommended value for this field:

    @best_guess

    Current advagg htaccess values:

    @advagg_css_htaccess
    @advagg_js_htaccess

    Note that the advagg CSS/JS directories are automatically added onto the end of the given input value.', array( + '@core_htaccess' => $core_htaccess, + '@best_guess' => $new_dir, + '@css_path' => $css_path[1], + '@js_path' => $js_path[1], + '@advagg_css_htaccess' => $advagg_css_htaccess, + '@advagg_js_htaccess' => $advagg_js_htaccess, + )), + ); + } + elseif (!empty($advagg_htaccess_rewritebase)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); + $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); + // Offer advice if this setting should be disabled. + $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( + '#type' => 'textfield', + '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), + '#default_value' => $advagg_htaccess_rewritebase, + '#description' => t('This Drupal install is no longer using RewriteBase in the webroot .htaccess file. The recommended value for this field is a blank string (empty)
    Current advagg htaccess values:

    @advagg_css_htaccess
    @advagg_js_htaccess

    ', array( + '@advagg_css_htaccess' => $advagg_css_htaccess, + '@advagg_js_htaccess' => $advagg_js_htaccess, + )), + ); + } + + $form['global_container']['css'] = array( + '#type' => 'fieldset', + '#title' => t('CSS Options'), + ); + $form['global_container']['css']['advagg_combine_css_media'] = array( + '#type' => 'checkbox', + '#title' => t('Combine CSS files by using media queries (recommended)'), + '#default_value' => variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA), + '#description' => t('Will combine more CSS files together because different CSS media types can be used in the same file by using media queries. Use cores grouping logic needs to be unchecked in order for this to work. Also noted is that due to an issue with IE9, compatibility mode is forced off if this is enabled.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-core-groups' => array('checked' => TRUE), + ), + ), + '#recommended_value' => TRUE, + ); + $form['global_container']['css']['advagg_ie_css_selector_limiter'] = array( + '#type' => 'checkbox', + '#title' => t('Prevent more than %limit CSS selectors in an aggregated CSS file', array('%limit' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE))), + '#default_value' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Enabling this will prevent CSS aggregates from being created that exceed this limit. More info. Use cores grouping logic needs to be unchecked in order for this to work.', array('@link' => 'http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx')), + '#states' => array( + 'disabled' => array( + '#edit-advagg-core-groups' => array('checked' => TRUE), + ), + ), + '#recommended_value' => FALSE, + ); + $form['global_container']['css']['advagg_ie_css_selector_limiter_value'] = array( + '#type' => 'textfield', + '#title' => t('The selector count the IE CSS limiter should use'), + '#default_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Use this field to modify the value used; 4095 sometimes may be still be too many with media queries.'), + '#states' => array( + 'visible' => array( + '#edit-advagg-ie-css-selector-limiter' => array('checked' => TRUE), + ), + 'disabled' => array( + '#edit-advagg-ie-css-selector-limiter' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['css']['advagg_css_fix_type'] = array( + '#type' => 'checkbox', + '#title' => t('Fix improperly set type (recommended)'), + '#default_value' => variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE), + '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( + '@link' => 'https://www.drupal.org/node/2336217', + )), + '#recommended_value' => TRUE, + ); + $form['global_container']['css']['advagg_css_remove_empty_files'] = array( + '#type' => 'checkbox', + '#title' => t('Remove empty CSS files from aggregates (recommended)'), + '#default_value' => variable_get('advagg_css_remove_empty_files', ADVAGG_CSS_REMOVE_EMPTY_FILES), + '#description' => t('If an empty CSS file ends up being in its own aggregate, that aggregate can end up as a 404.'), + '#recommended_value' => TRUE, + ); + + $form['global_container']['js'] = array( + '#type' => 'fieldset', + '#title' => t('JS Options'), + ); + $form['global_container']['js']['advagg_js_fix_type'] = array( + '#type' => 'checkbox', + '#title' => t('Fix improperly set type (recommended)'), + '#default_value' => variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE), + '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( + '@link' => 'https://www.drupal.org/node/2336217', + )), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_js_remove_empty_files'] = array( + '#type' => 'checkbox', + '#title' => t('Remove empty JS files from aggregates (recommended)'), + '#default_value' => variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES), + '#description' => t('If an empty JS file ends up being in its own aggregate, that aggregate can end up as a 404.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_scripts_scope_anywhere'] = array( + '#type' => 'checkbox', + '#title' => t('Allow for JS to be added to blocks and views'), + '#default_value' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE), + '#description' => t('The scope key when using drupal_add_js and friends can be used to insert script tags throughout the html body. To see possible insertion points set @theme_debug in your settings.php file and view the page source while looking for @comment html comments. Some example scopes:

    !example

    ', array( + '@theme_debug' => '$conf[\'theme_debug\'] = TRUE;', + '@comment' => '', + '!example' => "page_top:prefix
    block:prefix:system:powered-by
    view:suffix:frontpage:page", + )), + ); + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Form builder; Do advagg operations. + */ +function advagg_admin_operations_form($form, $form_state) { + drupal_set_title(t('AdvAgg: Operations')); + advagg_display_message_if_requirements_not_met(); + + // Explain what can be done on this page. + $form['tip'] = array( + '#markup' => '

    ' . t('This is a collection of commands to control the cache and to manage testing of this module. In general this page is useful when troubleshooting some aggregation issues. For normal operations, you do not need to do anything on this page below the Smart Cache Flush. There are no configuration options here.') . '

    ', + ); + + // Buttons to do stuff. + // AdvAgg smart cache flushing. + $form['smart_flush'] = array( + '#type' => 'fieldset', + '#title' => t('Smart Cache Flush'), + '#description' => t('Scan all files referenced in aggregated files. If any of them have changed, clear that cache so the changes will go out.'), + ); + $form['smart_flush']['advagg_flush'] = array( + '#type' => 'submit', + '#value' => t('Flush AdvAgg Cache'), + '#submit' => array('advagg_admin_flush_cache_button'), + ); + + // Set/Remove Bypass Cookie. + $form['bypass'] = array( + '#type' => 'fieldset', + '#title' => t('Aggregation Bypass Cookie'), + '#description' => t('This will set or remove a cookie that disables aggregation for a set period of time.'), + ); + $bypass_length = drupal_map_assoc(array( + 60 * 60 * 6, + 60 * 60 * 12, + 60 * 60 * 24, + 60 * 60 * 24 * 2, + 60 * 60 * 24 * 7, + 60 * 60 * 24 * 30, + 60 * 60 * 24 * 365, + ), 'format_interval'); + $form['bypass']['timespan'] = array( + '#type' => 'select', + '#title' => 'Bypass length', + '#options' => $bypass_length, + ); + $form['bypass']['submit'] = array( + '#type' => 'submit', + '#value' => t('Toggle The "aggregation bypass cookie" For This Browser'), + '#attributes' => array('onclick' => 'javascript:return advagg_toggle_cookie()'), + '#submit' => array('advagg_admin_toggle_bypass_cookie'), + ); + // Add in aggregation bypass cookie javascript. + $form['#attached']['js'][] = array( + 'data' => array( + 'advagg' => array( + 'key' => drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')), + ), + ), + 'type' => 'setting', + ); + $form['#attached']['js'][] = drupal_get_path('module', 'advagg') . '/advagg.admin.js'; + + // Regenerate .htaccess files. + if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $form['htaccess'] = array( + '#type' => 'fieldset', + '#title' => t('Regenerate .htaccess files'), + '#description' => t('@advagg_css_htaccess
    @advagg_js_htaccess', array( + '@advagg_css_htaccess' => $css_path[1] . '/.htaccess', + '@advagg_js_htaccess' => $js_path[1] . '/.htaccess', + )), + ); + $form['htaccess']['advagg_regenerate_htaccess'] = array( + '#type' => 'submit', + '#value' => t('Recreate htaccess files'), + '#submit' => array('advagg_admin_regenerate_htaccess_button'), + ); + } + + // Tasks run by cron. + $form['cron'] = array( + '#type' => 'fieldset', + '#title' => t('Cron Maintenance Tasks'), + '#description' => t('The following 7 operations are ran on cron but you can run them manually here.'), + ); + $form['cron']['smart_file_flush'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Clear All Stale Files'), + '#description' => t('Remove all stale files. Scan all files in the advagg_css/js directories and remove the ones that have not been accessed in the last 30 days.'), + ); + $form['cron']['smart_file_flush']['advagg_flush_stale_files'] = array( + '#type' => 'submit', + '#value' => t('Remove All Stale Files'), + '#submit' => array('advagg_admin_flush_stale_files_button'), + ); + + $form['cron']['delete_empty_aggregates'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete empty aggregates'), + '#description' => t('Delete all aggregates that have no content so that they can be regenerated.'), + ); + $form['cron']['delete_empty_aggregates']['advagg_delete_empty_aggregates'] = array( + '#type' => 'submit', + '#value' => t('Delete empty aggregates'), + '#submit' => array('advagg_delete_empty_aggregates_button'), + ); + + $form['cron']['delete_orphaned_aggregates'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete orphaned aggregates'), + '#description' => t('Scan CSS/JS advagg dir and remove file if there is no associated db record.'), + ); + $form['cron']['delete_orphaned_aggregates']['advagg_delete_orphaned_aggregates'] = array( + '#type' => 'submit', + '#value' => t('Delete orphaned aggregates'), + '#submit' => array('advagg_admin_delete_orphaned_aggregates_button'), + ); + + $form['cron']['remove_missing_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Clear Missing Files From Database'), + '#description' => t('Scan for missing files and remove the associated entries in the database.'), + ); + $form['cron']['remove_missing_files']['advagg_remove_missing_files_from_db'] = array( + '#type' => 'submit', + '#value' => t('Clear Missing Files From Database'), + '#submit' => array('advagg_admin_remove_missing_files_from_db_button'), + ); + + $form['cron']['remove_old_aggregates'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete Unused Aggregates From Database'), + '#description' => t('Delete aggregates that have not been accessed in the last 6 weeks.'), + ); + $form['cron']['remove_old_aggregates']['advagg_remove_old_unused_aggregates'] = array( + '#type' => 'submit', + '#value' => t('Delete Unused Aggregates From Database'), + '#submit' => array('advagg_admin_remove_old_unused_aggregates_button'), + ); + + $form['cron']['cleanup_semaphore_table'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete Orphaned Semaphore Locks'), + '#description' => t('Delete orphaned/expired advagg locks from the semaphore database table.'), + ); + $form['cron']['cleanup_semaphore_table']['advagg_cleanup_semaphore_table'] = array( + '#type' => 'submit', + '#value' => t('Delete Orphaned Semaphore Locks'), + '#submit' => array('advagg_admin_cleanup_semaphore_table_button'), + ); + + $form['cron']['cleanup_temp_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete leftover temporary files'), + '#description' => t('Delete old temporary files from the filesystem.'), + ); + $form['cron']['cleanup_temp_files']['advagg_remove_temp_files'] = array( + '#type' => 'submit', + '#value' => t('Delete leftover temporary files'), + '#submit' => array('advagg_admin_cleanup_temp_files_button'), + ); + + if (module_exists('locale')) { + $form['cron']['refresh_locale_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Refresh all locale files'), + '#description' => t('Run all js files in the advagg_files table through locale_js_alter; loop for each enabled language.'), + ); + $form['cron']['refresh_locale_files']['advagg_refresh_all_locale_files'] = array( + '#type' => 'submit', + '#value' => t('Refresh all locale files'), + '#submit' => array('advagg_admin_refresh_locale_files_button'), + ); + } + + // Hide drastic measures as they should not be done unless you really need it. + $form['drastic_measures'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Drastic Measures'), + '#description' => t('The options below should normally never need to be done.'), + ); + $form['drastic_measures']['dumb_cache_flush'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Clear All Caches'), + '#description' => t('Remove all entries from the advagg cache bins. Useful if you suspect a cache is not getting cleared.'), + ); + $form['drastic_measures']['dumb_cache_flush']['advagg_flush_all_caches'] = array( + '#type' => 'submit', + '#value' => t('Clear All Caches'), + '#submit' => array('advagg_admin_clear_all_caches_button'), + ); + $form['drastic_measures']['dumb_file_flush'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Clear All Files'), + '#description' => t('Remove all generated files. Useful if you think some of the generated files got corrupted and thus need to be deleted.'), + ); + $form['drastic_measures']['dumb_file_flush']['advagg_flush_all_files'] = array( + '#type' => 'submit', + '#value' => t('Remove All Generated Files'), + '#submit' => array('advagg_admin_clear_all_files_button'), + ); + $form['drastic_measures']['force_change'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Force new aggregates'), + '#description' => t('Force the creation of all new aggregates by incrementing a global counter. Current value of counter: %value. This is useful if a CDN has cached an aggregate incorrectly as it will force new ones to be used even if nothing else has changed.', array('%value' => advagg_get_global_counter())), + ); + $form['drastic_measures']['force_change']['increment_global_counter'] = array( + '#type' => 'submit', + '#value' => t('Increment Global Counter'), + '#submit' => array('advagg_admin_increment_global_counter'), + ); + $form['drastic_measures']['rescan_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Rescan all files'), + '#description' => t('Force rescan all files and clear the cache. Useful if a css/js change from a deployment did not work.'), + ); + $form['drastic_measures']['rescan_files']['reset_mtime'] = array( + '#type' => 'submit', + '#value' => t('Reset All mtime Values'), + '#submit' => array('advagg_admin_reset_mtime'), + ); + $form['drastic_measures']['remove_empty_advagg_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Remove deleted files in the advagg_files table'), + '#description' => t('Remove entries in the advagg_files table that have a filesize of 0 and delete the javascript_parsed variable. This gets around the grace period that the cron cleanup does.'), + ); + $form['drastic_measures']['remove_empty_advagg_files']['advagg_admin_remove_empty_advagg_files'] = array( + '#type' => 'submit', + '#value' => t('Remove deleted files from advagg_files'), + '#submit' => array('advagg_admin_remove_empty_advagg_files'), + ); + $form['drastic_measures']['reset_advagg_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Reset the AdvAgg Files table'), + '#description' => t('Truncate the advagg_files table and delete the javascript_parsed variable. This may cause some 404s for CSS/JS assets for a short amount of time (seconds). Useful if you really want to reset some stuff. Might be best to put the site into maintenance mode before doing this.'), + ); + $form['drastic_measures']['reset_advagg_files']['truncate_advagg_files'] = array( + '#type' => 'submit', + '#value' => t('Truncate advagg_files'), + '#submit' => array('advagg_admin_truncate_advagg_files'), + ); + + $form['drastic_measures']['clear_base_file'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete all aggregates containing this base file.'), + '#description' => t('Pinpoint clearing of all aggregates that contain a file.'), + ); + $form['drastic_measures']['clear_base_file']['filename'] = array( + '#type' => 'textfield', + '#size' => 170, + '#maxlength' => 256, + '#default_value' => '', + '#title' => t('Filename'), + ); + $form['drastic_measures']['clear_base_file']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete Select Aggregates'), + '#validate' => array('advagg_admin_clear_file_aggregate_validate'), + '#submit' => array('advagg_admin_clear_file_aggregate_submit'), + '#ajax' => array( + 'callback' => 'advagg_admin_clear_file_aggregate_callback', + 'wrapper' => 'advagg-clear-file-aggregate-ajax', + 'effect' => 'fade', + ), + ); + $types = array('css', 'js'); + $css_file = ''; + $js_file = ''; + foreach ($types as $type) { + // Get valid filename. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename')) + ->condition('filetype', $type) + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + if (empty($css_file) && $type === 'css') { + $css_file = $row['filename']; + break; + } + if (empty($js_file) && $type === 'js') { + $js_file = $row['filename']; + break; + } + } + } + $form['drastic_measures']['clear_base_file']['tip'] = array( + '#markup' => '

    ' . t('Takes input like "@css_file" or "@advagg_js"', array( + '@css_file' => $css_file, + '@advagg_js' => $js_file, + )) . '

    ', + ); + $form['drastic_measures']['clear_base_file']['wrapper'] = array( + '#markup' => "
    ", + ); + + return $form; +} + +/** + * Form builder; Show info about advagg and advagg settings. + * + * @see system_settings_form() + */ +function advagg_admin_info_form($form, $form_state) { + drupal_set_title(t('AdvAgg: Information')); + advagg_display_message_if_requirements_not_met(); + + // Explain what can be done on this page. + $form['tip'] = array( + '#markup' => '

    ' . t('This page provides debugging information. There are no configuration options here.') . '

    ', + ); + + // Get all hooks and variables. + drupal_theme_initialize(); + $core_hooks = theme_get_registry(); + $advagg_hooks = advagg_hooks_implemented(); + list(, $js_path) = advagg_get_root_files_dir(); + + // Output html process functions hooks. + $form['theme_info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Hook Theme Info'), + ); + $data = implode("\n", $core_hooks['html']['process functions']); + $form['theme_info']['advagg_theme_info'] = array( + '#markup' => '
    ' . $data . '
    ', + ); + + $form['render_info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Hook Render Info'), + ); + $element_info = array(); + $element_info['scripts'] = element_info('scripts'); + $element_info['styles'] = element_info('styles'); + $output = ''; + foreach ($element_info as $type => &$values) { + $output .= $type . ":\n"; + foreach ($values as $key => &$value) { + if ($key === '#items' || $key === '#type') { + continue; + } + if (empty($value)) { + $output .= ' ' . $key . ' - ' . str_replace(array("\r", "\n"), '', (string) var_export($value, TRUE)) . "\n"; + } + elseif (is_array($value)) { + $output .= ' ' . $key . ' - ' . implode(', ', $value) . "\n"; + } + else { + $output .= ' ' . $key . ' - ' . print_r($value, TRUE) . "\n"; + } + } + } + $form['render_info']['advagg_render_info'] = array( + '#markup' => '
    ' . $output . '
    ', + ); + + // Get all parent css and js files. + $types = array('css', 'js'); + $css_file = ''; + foreach ($types as $type) { + $form[$type] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('@type files', array('@type' => drupal_strtoupper($type))), + ); + // Get filename, filename_hash, and changes. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'filename_hash', 'changes')) + ->condition('filetype', $type) + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + if (empty($css_file) && $type === 'css') { + $css_file = basename($row['filename']); + } + + $form[$type][$row['filename_hash']] = array( + '#markup' => '
    ' . format_plural($row['changes'], 'changed 1 time - %file
    ', 'changed %changes times - %file
    ', array( + '%changes' => $row['changes'], + '%file' => $row['filename'], + )), + ); + } + } + + // Display as module -> hook instead of hook -> module. + ksort($advagg_hooks); + $module_hooks = array(); + foreach ($advagg_hooks as $hook => $values) { + if (!empty($values)) { + foreach ($values as $module_name) { + if (!isset($module_hooks[$module_name])) { + $module_hooks[$module_name] = array(); + } + $module_hooks[$module_name][] = $hook; + } + } + else { + $module_hooks['not in use'][] = $hook; + } + } + ksort($module_hooks); + + // Output all advagg hooks implemented. + foreach ($module_hooks as $hook => $values) { + if (empty($values)) { + $form['modules_implementing_advagg'][$hook] = array( + '#markup' => '
    ' . check_plain($hook) . ': 0
    ', + ); + } + else { + $form['modules_implementing_advagg'][$hook] = array( + '#markup' => '
    ' . check_plain($hook) . ': ' . count($values) . '
      ' . filter_xss(implode('
      ', $values), array('br')) . '
    ', + ); + } + } + $form['modules_implementing_advagg'] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Modules implementing AdvAgg CSS/JS hooks'), + ); + + // Output all advagg hooks implemented. + foreach ($advagg_hooks as $hook => $values) { + if (empty($values)) { + $form['hooks_implemented'][$hook] = array( + '#markup' => '
    ' . check_plain($hook) . ': 0
    ', + ); + } + else { + $form['hooks_implemented'][$hook] = array( + '#markup' => '
    ' . check_plain($hook) . ': ' . count($values) . '
      ' . filter_xss(implode('
      ', $values), array('br')) . '
    ', + ); + } + } + $form['hooks_implemented'] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('AdvAgg CSS/JS hooks implemented by modules'), + ); + + // Output what is used inside of the advagg_get_current_hooks_hash() function. + $form['hooks_variables_hash'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Hooks And Variables Used In Hash'), + ); + $form['hooks_variables_hash']['description'] = array( + '#markup' => t('Current Value: %value. Below is the listing of variables and hooks used to generate the 3rd hash of an aggregates filename.', array('%value' => advagg_get_current_hooks_hash())), + ); + $form['hooks_variables_hash']['output'] = array( + // @ignore production_php + '#markup' => '
    ' . print_r(advagg_current_hooks_hash_array(), TRUE) . '
    ', + ); + + // Get info about a file. + $form['get_info_about_agg'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Get detailed info about an aggregate file'), + ); + $form['get_info_about_agg']['filename'] = array( + '#type' => 'textfield', + '#size' => 170, + '#maxlength' => 256, + '#default_value' => '', + '#title' => t('Filename'), + ); + $form['get_info_about_agg']['submit'] = array( + '#type' => 'submit', + '#value' => t('Lookup Details'), + '#submit' => array('advagg_admin_get_file_info_submit'), + '#validate' => array('advagg_admin_get_file_info_validate'), + '#ajax' => array( + 'callback' => 'advagg_admin_get_file_info_callback', + 'wrapper' => 'advagg-file-info-ajax', + 'effect' => 'fade', + ), + ); + module_load_include('install', 'advagg', 'advagg'); + $first_file_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $js_path[0] : $js_path[1]; + $form['get_info_about_agg']['tip'] = array( + '#markup' => '

    ' . t('Takes input like "@css_file" or a full aggregate name like "@advagg_js"', array( + '@css_file' => $css_file, + '@advagg_js' => advagg_install_get_first_advagg_file($first_file_path, 'js'), + )) . '

    ', + ); + $form['get_info_about_agg']['wrapper'] = array( + '#markup' => "
    ", + ); + + return $form; +} + +/** + * Add various advagg settings to the system_performance_settings form. + */ +function advagg_admin_system_performance_settings_form(&$form, $form_state) { + $msg = t('NOTE: If you wish to bypass aggregation for a set amount of time, you can go to the AdvAgg operations page and press the "aggregation bypass cookie" button.', array( + '@operations' => url('admin/config/development/performance/advagg/operations'), + )); + $msg .= ' '; + if (user_access('bypass advanced aggregation')) { + $msg .= t('You can also selectively bypass aggregation by adding @code to the URL of any page.', array( + '@code' => '?advagg=0', + )); + } + else { + $msg .= t('You do not have the bypass advanced aggregation permission so adding @code to the URL will not work at this time for you; either grant this permission to your user role or use the bypass cookie if you wish to selectively bypass aggregation.', array( + '@permission' => url('admin/people/permissions', array('fragment' => 'module-advagg')), + '@code' => '?advagg=0', + )); + } + if (is_callable('omega_extension_enabled') + && is_callable('omega_theme_get_setting') + && omega_extension_enabled('development') + && omega_theme_get_setting('omega_livereload', TRUE) + ) { + $msg .= ' ' . t('The omega theme is in development mode and livereload is also enabled. This setting combination disables CSS aggregation. If you wish to have CSS aggregation turned on, go to the theme settings page, select the Omega theme/subtheme and turn off LiveReload and/or turn off the Development extension.', array('@url' => url('admin/appearance/settings'))); + } + + $form['bandwidth_optimization']['advagg_note'] = array( + '#markup' => $msg, + ); +} + +/** + * @} End of "defgroup advagg_forms". + */ + +/** + * @defgroup advagg_forms_callback AdvAgg forms callbacks and validation. + * @{ + * Functions that handle user input from forms. + */ + +/** + * Clear out the advagg cache bin when the save configuration button is pressed. + */ +function advagg_admin_settings_form_submit($form, &$form_state) { + // Remove non variables. + if (isset($form_state['values']['advagg_resource_hints_preload_reset'])) { + unset($form_state['values']['advagg_resource_hints_preload_reset']); + } + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_admin_mode'); + + // Sort and fix values before saving to the db. + foreach ($form_state['values']['advagg_resource_hints_preload_settings'] as &$entry) { + if (isset($entry['weight'])) { + $entry['#weight'] = $entry['weight']; + unset($entry['weight']); + } + ksort($entry); + } + uasort($form_state['values']['advagg_resource_hints_preload_settings'], 'element_sort'); + + // Make sure .htaccess file exists in the advagg dir. + if (isset($form_state['values']['advagg_resource_hints_use_immutable']) + && variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE) != $form_state['values']['advagg_resource_hints_use_immutable'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + // Set variable. + variable_set('advagg_resource_hints_use_immutable', $form_state['values']['advagg_resource_hints_use_immutable']); + unset($form_state['values']['advagg_resource_hints_use_immutable']); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + // Alter htaccess if needed. + if (isset($form_state['values']['advagg_htaccess_rewritebase']) + && variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE) != $form_state['values']['advagg_htaccess_rewritebase'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + // Set variable. + variable_set('advagg_htaccess_rewritebase', trim($form_state['values']['advagg_htaccess_rewritebase'])); + unset($form_state['values']['advagg_htaccess_rewritebase']); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH) != $form_state['values']['advagg_htaccess_symlinksifownermatch'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Push out new setting. + variable_set('advagg_htaccess_symlinksifownermatch', $form_state['values']['advagg_htaccess_symlinksifownermatch']); + unset($form_state['values']['advagg_htaccess_symlinksifownermatch']); + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + // Clear caches. + advagg_cache_clear_admin_submit(); +} + +/** + * Set or remove the AdvAggDisabled cookie. + */ +function advagg_admin_toggle_bypass_cookie($form, &$form_state) { + $cookie_name = 'AdvAggDisabled'; + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + + // If the cookie does exist then remove it. + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + setcookie($cookie_name, '', -1, $GLOBALS['base_path'], '.' . $_SERVER['HTTP_HOST']); + unset($_COOKIE[$cookie_name]); + drupal_set_message(t('AdvAgg Bypass Cookie Removed.')); + } + // If the cookie does not exist then set it. + else { + // Cookie will last for 12 hours. + setcookie($cookie_name, $key, REQUEST_TIME + $form_state['values']['timespan'], $GLOBALS['base_path'], '.' . $_SERVER['HTTP_HOST']); + $_COOKIE[$cookie_name] = $key; + drupal_set_message(t('AdvAgg Bypass Cookie Set for %time.', array('%time' => format_interval($form_state['values']['timespan'])))); + } +} + +/** + * Display file info in a drupal message. + */ +function advagg_admin_get_file_info_submit($form, &$form_state) { + // @codingStandardsIgnoreLine + if (!empty($form_state['input']['ajax_page_state'])) { + return; + } + $info = advagg_admin_get_file_info($form_state['values']['filename']); + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
    ' . check_plain(print_r($info, TRUE)) . '
    '; + } + // @ignore security_dsm + drupal_set_message($output); +} + +/** + * Display file info via ajax callback. + */ +function advagg_admin_get_file_info_callback($form, &$form_state) { + if (!empty($form_state['values']['error'])) { + return '
    '; + } + $info = advagg_admin_get_file_info($form_state['values']['filename']); + if (empty($info)) { + form_set_error('filename', t('Please input a valid aggregate filename.')); + return '
    '; + } + else { + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
    ' . print_r($info, TRUE) . '
    '; + } + return '
    ' . $output . '
    '; + } +} + +/** + * Verify that the filename is correct. + */ +function advagg_admin_get_file_info_validate($form, &$form_state) { + if (empty($form_state['values']['filename'])) { + form_set_error('filename', t('Please input an aggregate filename.')); + $form_state['values']['error'] = TRUE; + } +} + +/** + * Get detailed info about the given filename. + * + * @param string $filename + * Name of file to lookup. + * + * @return array + * Returns an array of detailed info about this file. + */ +function advagg_admin_get_file_info($filename) { + module_load_include('inc', 'advagg', 'advagg.missing'); + module_load_include('inc', 'advagg', 'advagg'); + + // Strip quotes and trim. + $filename = trim(str_replace(array('"', "'"), '', $filename)); + + $data = advagg_get_hashes_from_filename(basename($filename)); + $output = array(); + if (is_array($data)) { + list($type, $aggregate_filenames_hash, $aggregate_contents_hash) = $data; + + // Get a list of files. + $files = advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash); + if (empty($files)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + // Skip if the file exists. + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + if (file_exists($uri)) { + $atime = advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri); + if (REQUEST_TIME - $atime > variable_get('drupal_stale_file_threshold', 2592000)) { + $files = t('This is an old aggregate, it should be deleted on the next cron run.'); + } + else { + $files = t('This is an old aggregate, it should be deleted on the cron run after !time.', array('!time' => format_interval(variable_get('drupal_stale_file_threshold', 2592000) - (REQUEST_TIME - $atime)))); + } + } + else { + $files = t('This aggregate file no longer exists.'); + } + } + $data['files'] = $files; + + // Get detailed info on each file. + $files_info_filenames = array(); + foreach ($data['files'] as $filename => &$info) { + $files_info_filenames[] = $filename; + } + unset($info); + + // Get filesystem data. + $files_info = advagg_get_info_on_files($files_info_filenames); + + foreach ($data['files'] as $filename => &$info) { + $info += $files_info[$filename]; + if (module_exists('advagg_bundler')) { + $bundler = advagg_bundler_analysis($filename); + $info['group_hash'] = $bundler['group_hash']; + } + } + unset($info); + $output = $data; + } + else { + $results = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', '%' . db_like($filename), 'LIKE') + ->execute(); + while ($row = $results->fetchAssoc()) { + $row += advagg_get_info_on_file($row['filename']); + if (module_exists('advagg_bundler')) { + $bundler = advagg_bundler_analysis($row['filename']); + $row['group_hash'] = $bundler['group_hash']; + } + $output[] = $row; + } + } + return $output; +} + +/** + * Reset the advagg_resource_hints_preload_settings variable. + */ +function advagg_admin_resource_hints_preload_reset() { + variable_del('advagg_resource_hints_preload_settings'); +} + +/** + * Recreate the advagg htaccess files. + */ +function advagg_admin_regenerate_htaccess_button() { + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + $errors = array(); + foreach ($files as $type => $uri) { + $errors += advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + if (empty($errors)) { + drupal_set_message(t('The .htaccess files have been regenerated.')); + } + else { + drupal_set_message(t('The .htaccess files failed to be regenerated. @errors', array('@errors' => implode(', ', $errors)))); + } +} + +/** + * Perform a smart flush. + */ +function advagg_admin_flush_cache_button() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + $flushed = advagg_push_new_changes(); + + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + // Display a simple message if not in Development mode. + drupal_set_message(t('Advagg Cache Cleared')); + } + else { + list($css_path) = advagg_get_root_files_dir(); + $parts_uri = $css_path[1] . '/parts'; + + // Report back the results. + foreach ($flushed as $filename => $data) { + if (strpos($filename, $parts_uri) === 0) { + // Do not report on css files manged in the parts directory. + unset($flushed[$filename]); + continue; + } + $ext = pathinfo($filename, PATHINFO_EXTENSION); + drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins. Trigger: @changes', array( + '%filename' => $filename, + '%db_usage' => $data[0], + '%db_count' => $data[1], + '@changes' => print_r($data[2], TRUE), + '%type' => $ext, + ))); + } + + if (empty($flushed)) { + drupal_set_message(t('No changes found. Nothing was cleared.')); + return; + } + } +} + +/** + * Clear out all advagg cache bins. + */ +function advagg_admin_clear_all_caches_button() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + advagg_flush_all_cache_bins(); + + // Report back the results. + drupal_set_message(t('All AdvAgg cache bins have been cleared.')); + // Clear it again at the end of the request to be sure. + drupal_register_shutdown_function('advagg_flush_all_cache_bins', FALSE); + drupal_register_shutdown_function('advagg_push_new_changes'); +} + +/** + * Clear out all advagg aggregated files. + */ +function advagg_admin_clear_all_files_button() { + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_files, $js_files) = advagg_remove_all_aggregated_files(); + + // Report back the results. + drupal_set_message(t('All AdvAgg files have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( + '%css_count' => count($css_files), + '%js_count' => count($js_files), + ))); +} + +/** + * Clear out all stale advagg aggregated files. + */ +function advagg_admin_flush_stale_files_button() { + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_files, $js_files) = advagg_delete_stale_aggregates(); + + // Report back the results. + if (count($css_files) > 0 || count($js_files) > 0) { + drupal_set_message(t('All stale aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( + '%css_count' => count($css_files), + '%js_count' => count($js_files), + ))); + } + else { + drupal_set_message(t('No stale aggregates found. Nothing was deleted.')); + } +} + +/** + * Delete all empty advagg aggregated files. + */ +function advagg_delete_empty_aggregates_button() { + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_files, $js_files) = advagg_delete_empty_aggregates(); + + // Report back the results. + if (count($css_files) > 0 || count($js_files) > 0) { + drupal_set_message(t('All empty aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( + '%css_count' => count($css_files), + '%js_count' => count($js_files), + ))); + } + else { + drupal_set_message(t('No empty aggregates found. Nothing was deleted.')); + } +} + +/** + * Clear out all advagg cache bins and increment the counter. + */ +function advagg_admin_increment_global_counter() { + // Clear out the cache. + advagg_admin_clear_all_caches_button(); + + // Increment counter. + module_load_include('inc', 'advagg', 'advagg.cache'); + $new_value = advagg_increment_global_counter(); + drupal_set_message(t('Global counter is now set to %new_value', array('%new_value' => $new_value))); +} + +/** + * Clear out all advagg cache bins and increment the counter. + */ +function advagg_admin_reset_mtime() { + // Reset mtime. + db_update('advagg_files') + ->fields(array( + 'mtime' => 0, + )) + ->execute(); + drupal_set_message(t('All files have been rescanned.')); + + // Smart cache clear. + advagg_admin_flush_cache_button(); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * Remove filesize zero files from the advagg_files table and clear caches. + */ +function advagg_admin_remove_empty_advagg_files() { + // Remove dead files from the advagg_files table. + db_delete('advagg_files') + ->condition('filesize', 0) + ->execute(); + variable_del('javascript_parsed'); + drupal_set_message(t('All empty files in the advagg_files table have been removed.')); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * Truncate the advagg_files table and clear caches. + */ +function advagg_admin_truncate_advagg_files() { + // Truncate advagg_files table. + db_truncate('advagg_files')->execute(); + variable_del('javascript_parsed'); + drupal_set_message(t('All files from the advagg_files table have been removed.')); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * Scan CSS/JS advagg dir and remove file if there is no associated db record. + */ +function advagg_admin_delete_orphaned_aggregates_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Remove aggregates that include missing files. + $deleted = advagg_delete_orphaned_aggregates(); + if (empty($deleted[0]) && empty($deleted[1])) { + drupal_set_message(t('All files have an associated db record; nothing was deleted.')); + } + else { + drupal_set_message(t('Some files had no associated db record and could be safely deleted from the file system. @raw', array('@raw' => print_r($deleted[1], TRUE)))); + } +} + +/** + * Scan for missing files and remove the associated entries in the database. + */ +function advagg_admin_remove_missing_files_from_db_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Remove aggregates that include missing files. + $deleted = advagg_remove_missing_files_from_db(); + if (empty($deleted)) { + drupal_set_message(t('No missing files found or they could not be safely cleared out of the database.')); + } + else { + drupal_set_message(t('Some missing files were found and could be safely cleared out of the database.
     @raw 
    ', array('@raw' => print_r($deleted, TRUE)))); + } +} + +/** + * Delete aggregates that have not been accessed in the last 6 weeks. + */ +function advagg_admin_remove_old_unused_aggregates_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Remove unused aggregates. + $count = advagg_remove_old_unused_aggregates(); + if (empty($count)) { + drupal_set_message(t('No old and unused aggregates found. Nothing was deleted.')); + } + else { + drupal_set_message(t('Some old and unused aggregates were found. A total of %count database entries were removed.', array('%count' => $count))); + } +} + +/** + * Delete orphaned/expired advagg locks from the semaphore database table. + */ +function advagg_admin_cleanup_semaphore_table_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Delete orphaned/expired advagg locks from the semaphore database table. + $count = advagg_cleanup_semaphore_table(); + if (empty($count)) { + drupal_set_message(t('No orphaned advagg semaphore database table locks discovered. Nothing was deleted.')); + } + else { + drupal_set_message(format_plural($count, '1 orphaned advagg semaphore database table lock was found. A total of 1 database entry was removed.', 'Some orphaned advagg semaphore database table locks were found. A total of @count database entries were removed.')); + } +} + +/** + * Delete orphaned/expired advagg locks from the semaphore database table. + */ +function advagg_admin_refresh_locale_files_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Refresh all locale files. + $locale_files = advagg_refresh_all_locale_files(); + if (empty($locale_files)) { + drupal_set_message(t('No locale files are being used.')); + } + else { + drupal_set_message(t('The following locale files are being used:
    @files
    ', array('@files' => print_r($locale_files, TRUE)))); + } +} + +/** + * Delete leftover temp files. + */ +function advagg_admin_cleanup_temp_files_button() { + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Delete orphaned/expired advagg locks from the semaphore database table. + $count = advagg_remove_temp_files(); + if (empty($count)) { + drupal_set_message(t('No leftover temp files found. Nothing was deleted.')); + } + else { + drupal_set_message(format_plural($count, '1 leftover temp files from advagg was found. A total of 1 temp files was removed.', 'Some leftover temp files from advagg were found. A total of @count temp files were removed.')); + } +} + +/** + * Verify that the filename is correct. + */ +function advagg_admin_clear_file_aggregate_validate($form, &$form_state) { + if (empty($form_state['values']['filename'])) { + form_set_error('filename', t('Please input a filename.')); + $form_state['values']['error'] = TRUE; + } +} + +/** + * Display what files where deleted in a drupal message. + */ +function advagg_admin_clear_file_aggregate_submit($form, &$form_state) { + // @codingStandardsIgnoreLine + if (!empty($form_state['input']['ajax_page_state'])) { + return; + } + $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
    ' . check_plain(print_r($info, TRUE)) . '
    '; + } + // @ignore security_dsm + drupal_set_message($output); +} + +/** + * Display what files where deleted via ajax callback. + */ +function advagg_admin_clear_file_aggregate_callback($form, &$form_state) { + if (!empty($form_state['values']['error'])) { + return '
    '; + } + $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); + if (empty($info)) { + form_set_error('filename', t('Please input a valid filename.')); + return '
    '; + } + else { + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
    ' . print_r($info, TRUE) . '
    '; + } + return '
    ' . $output . '
    '; + } +} + +/** + * Remove the aggregates that contain the given filename. + * + * @param string $filename + * Name of file to lookup. Can be a comma separated list. + * @param bool $dry_run + * If TRUE, return the regex search string. + * + * @return array + * Returns an array of the parent file and what children where deleted. + */ +function advagg_admin_clear_file_aggregates($filename, $dry_run = FALSE) { + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_path, $js_path) = advagg_get_root_files_dir(); + $space = ADVAGG_SPACE; + $output = array(); + $options = array('callback' => 'file_unmanaged_delete'); + if ($dry_run) { + $options = array(); + } + + // Strip quotes and trim. + $filenames = array_map('trim', explode(',', trim(str_replace(array('"', "'"), '', $filename)))); + + foreach ($filenames as $filename) { + $results = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $filename) + ->execute(); + while ($row = $results->fetchAssoc()) { + // Get aggregates that use this file. + $row['aggregates_using_file'] = advagg_get_aggregates_using_file($row['filename_hash']); + // Get dir and other info from file. + if ($row['filetype'] === 'css') { + $dirname = $css_path[0]; + $basename_prefix = "{$row['filetype']}"; + } + if ($row['filetype'] === 'js') { + $dirname = $js_path[0]; + $basename_prefix = "{$row['filetype']}"; + } + + // Build regex search string for file_scan_directory(). + $regex_search = array(); + foreach ($row['aggregates_using_file'] as $values) { + $regex_search[] = preg_quote("{$basename_prefix}{$space}{$values['aggregate_filenames_hash']}{$space}") . '.*'; + } + $regex_search = array_unique($regex_search); + $regex_search_string = '/(' . implode('|', $regex_search) . ')/'; + $files = file_scan_directory($dirname, $regex_search_string, $options); + + // List what files were deleted. + $row['aggregates_deleted'] = array(); + $files_deleted = array_keys($files); + if (!empty($files_deleted)) { + $row['aggregates_deleted'][] = $files_deleted; + } + + $output[$filename] = $row['aggregates_deleted']; + } + } + + return $output; +} + +/** + * @} End of "defgroup advagg_forms_callback". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.admin.js b/frontend/drupal/sites/all/modules/advagg/advagg.admin.js new file mode 100644 index 000000000..5a4e29ec2 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.admin.js @@ -0,0 +1,132 @@ +/** + * @file + * Used to toggle the AdvAgg Bypass Cookie client side. + */ + +/* global Drupal:false */ +/* eslint-disable no-unused-vars */ + +/** + * Test to see if the given string contains unicode. + * + * @param {int} interval + * String to test. + * @param {int} granularity + * String to test. + * @param {string} langcode + * Language used in translation. + * + * @return {bool} + * true if string contains non ASCII characters. + * false if string only contains ASCII characters. + */ +Drupal.formatInterval = function (interval, granularity, langcode) { + 'use strict'; + granularity = typeof granularity !== 'undefined' ? granularity : 2; + langcode = typeof langcode !== 'undefined' ? langcode : null; + var output = ''; + + /* eslint-disable key-spacing */ + while (granularity > 0) { + var value = 0; + if (interval >= 31536000) { + value = 31536000; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 year', '@count years', {langcode : langcode}); + } + else if (interval >= 2592000) { + value = 2592000; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 month', '@count months', {langcode : langcode}); + } + else if (interval >= 604800) { + value = 604800; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 week', '@count weeks', {langcode : langcode}); + } + else if (interval >= 86400) { + value = 86400; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 day', '@count days', {langcode : langcode}); + } + else if (interval >= 3600) { + value = 3600; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 hour', '@count hours', {langcode : langcode}); + } + else if (interval >= 60) { + value = 60; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 min', '@count min', {langcode : langcode}); + } + else if (interval >= 1) { + value = 1; + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 sec', '@count sec', {langcode : langcode}); + } + + interval %= value; + granularity--; + } + + return output.length ? output : Drupal.t('0 sec', {}, {langcode : langcode}); + /* eslint-enable key-spacing */ +}; + +/** + * Test to see if the given string contains unicode. + * + * @param {string} str + * String to test. + * + * @return {bool} + * true if string contains non ASCII characters. + * false if string only contains ASCII characters. + */ +function advagg_is_unicode(str) { + 'use strict'; + for (var i = 0, n = str.length; i < n; i++) { + if (str.charCodeAt(i) > 255) { + return true; + } + } + return false; +} + +/** + * Toggle the advagg cookie. + * + * @return {bool} + * true if hostname contains unicode. + * false so the form does not get submitted. + */ +function advagg_toggle_cookie() { + 'use strict'; + // Fallback to submitting the form for Unicode domains like ".рф". + if (advagg_is_unicode(document.location.hostname)) { + return true; + } + + var cookie_name = 'AdvAggDisabled'; + + // See if the cookie exists. + var cookie_pos = document.cookie.indexOf(cookie_name + '=' + Drupal.settings.advagg.key); + + // If the cookie does exist then remove it. + if (cookie_pos !== -1) { + document.cookie = + cookie_name + '=' + + '; expires=Thu, 01 Jan 1970 00:00:00 GMT' + + '; path=' + Drupal.settings.basePath + + '; domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Removed')); + } + // If the cookie does not exist then set it. + else { + var bypass_length = document.getElementById('edit-timespan').value; + var expire_date = new Date(new Date().getTime() + bypass_length * 1000); + + document.cookie = + cookie_name + '=' + Drupal.settings.advagg.key + + '; expires=' + expire_date.toGMTString() + + '; path=' + Drupal.settings.basePath + + '; domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Set for @time.', {'@time': Drupal.formatInterval(bypass_length)})); + } + + // Must return false, if returning true then form gets submitted. + return false; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.advagg.inc b/frontend/drupal/sites/all/modules/advagg/advagg.advagg.inc new file mode 100644 index 000000000..bd6aba380 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.advagg.inc @@ -0,0 +1,662 @@ + $contents). + // * @param array $aggregate_settings + // * Array of settings. + // * @param array $other_parameters + // * Array of containing $files and $type. + $file_types = array(); + // Handle gzip. + if (!empty($aggregate_settings['variables']['advagg_gzip'])) { + // Use zopfli_encode if it exists. + // See https://github.com/kjdev/php-ext-zopfli + if (function_exists('zopfli_encode') + && defined('ZOPFLI_GZIP') + && empty($aggregate_settings['variables']['advagg_no_zopfli']) + ) { + $file_types['.gz'] = array('zopfli_encode', 15, constant('ZOPFLI_GZIP')); + } + else { + $file_types['.gz'] = array('gzencode', 9, FORCE_GZIP); + } + } + // Handle brotli. + // See https://github.com/kjdev/php-ext-brotli + if (!empty($aggregate_settings['variables']['advagg_brotli']) + && defined('BROTLI_TEXT') + && function_exists('brotli_compress') + ) { + $file_types['.br'] = array('brotli_compress', 11, constant('BROTLI_TEXT')); + } + + if (empty($file_types)) { + return; + } + + // Special S3 handling. + $s3fs = FALSE; + $files_to_save_keys = array_keys($files_to_save); + foreach ($files_to_save_keys as $uri) { + $wrapper = file_stream_wrapper_get_instance_by_uri($uri); + if ($wrapper && get_class($wrapper) === 'S3fsStreamWrapper') { + $s3fs = TRUE; + break; + } + } + + foreach ($file_types as $ext => $settings) { + // See if a file already exists with this extension. + $ext_exists = FALSE; + foreach ($files_to_save as $uri => $contents) { + // See if this uri contains $ext near the end of it. + if (strlen($uri) > 91 + strlen(ADVAGG_SPACE) * 3) { + $pos = strripos($uri, $ext, 91 + strlen(ADVAGG_SPACE) * 3); + } + else { + $pos = strripos($uri, $ext); + } + if (!empty($pos)) { + $len = strlen($uri); + // $ext file exists, exit loop. + if ($pos == $len - 3) { + $ext_exists = TRUE; + break; + } + } + } + + // If a $ext file does not exist, create one. + if (!$ext_exists) { + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + // Compress it and add it to the $files_to_save array. + $callback = $settings[0]; + $settings[0] = $data; + $compressed = call_user_func_array($callback, $settings); + if (!empty($compressed)) { + if ($s3fs && $ext === '.gz') { + // Only serve gzip files from S3. + $files_to_save[$uri] = $compressed; + } + elseif ($s3fs && $ext === '.br' && !isset($file_types['.gz'])) { + // Only serve br files from S3. + $files_to_save[$uri] = $compressed; + } + else { + $files_to_save[$uri . $ext] = $compressed; + } + } + } + } +} + +/** + * Implements hook_advagg_build_aggregate_plans_alter(). + * + * Used to alter the plan so it has the same grouping as cores. + */ +function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $type) { + // * @param array $files + // * List of files in the aggregate as well as the aggregate name. + // * @param bool $modified + // * Change this to TRUE if $files has been changed. + // * @param string $type + // * String containing css or js. + // + // Do nothing if core grouping is disabled. + if (!variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS)) { + return; + } + + $temp_new_files = array(); + $counter = 0; + foreach ($files as $data) { + $group = NULL; + $every_page = NULL; + foreach ($data['files'] as $fileinfo) { + // Grouped by group and every_page variables. + if (is_null($group)) { + $group = $fileinfo['group']; + } + if (is_null($every_page)) { + $every_page = $fileinfo['every_page']; + } + + // Bump Counter if group/every_page has changed from the last one. + if ($group != $fileinfo['group'] || $every_page != $fileinfo['every_page']) { + ++$counter; + $group = $fileinfo['group']; + $every_page = $fileinfo['every_page']; + $modified = TRUE; + } + $temp_new_files[$counter][] = $fileinfo; + } + ++$counter; + } + + // Replace $files array with new aggregate filenames. + $files = advagg_generate_filenames(array($temp_new_files), $type); +} + +/** + * Implements hook_advagg_context_alter(). + */ +function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) { + if ($mode == 0) { + // Change context. + $original['base_root'] = $GLOBALS['base_root']; + $original['base_url'] = $GLOBALS['base_url']; + $original['base_path'] = $GLOBALS['base_path']; + $original['is_https'] = $GLOBALS['is_https']; + $original['language'] = isset($GLOBALS['language']) ? $GLOBALS['language'] : NULL; + + $GLOBALS['is_https'] = $aggregate_settings['variables']['is_https']; + if ($aggregate_settings['variables']['is_https']) { + $GLOBALS['base_root'] = str_replace('http://', 'https://', $GLOBALS['base_root']); + } + else { + $GLOBALS['base_root'] = str_replace('https://', 'http://', $GLOBALS['base_root']); + } + $GLOBALS['base_path'] = $aggregate_settings['variables']['base_path']; + $GLOBALS['base_url'] = rtrim($GLOBALS['base_root'] . $GLOBALS['base_path'], '/'); + + if (isset($aggregate_settings['variables']['language'])) { + $languages = language_list(); + if (isset($languages[$aggregate_settings['variables']['language']])) { + $GLOBALS['language'] = $languages[$aggregate_settings['variables']['language']]; + } + } + } + elseif ($mode == 1) { + // Change context back. + if (isset($original['base_root'])) { + $GLOBALS['base_root'] = $original['base_root']; + } + if (isset($original['base_url'])) { + $GLOBALS['base_url'] = $original['base_url']; + } + if (isset($original['base_path'])) { + $GLOBALS['base_path'] = $original['base_path']; + } + if (isset($original['is_https'])) { + $GLOBALS['is_https'] = $original['is_https']; + } + if (isset($original['language'])) { + $GLOBALS['language'] = $original['language']; + } + } + + // Moved this in here due to incomplete bug reports from + // https://www.drupal.org/node/1942230. I can not reproduce the reported + // issues with the patch for the CDN module so I've moved the code into + // advagg. + if (!function_exists('cdn_advagg_context_alter') && module_exists('cdn')) { + if ($mode == 0) { + // Save original context. + $original[CDN_MODE_VARIABLE] = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC); + $original[CDN_BASIC_FARFUTURE_VARIABLE] = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT); + $original[CDN_HTTPS_SUPPORT_VARIABLE] = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE); + $original[CDN_STATUS_VARIABLE] = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); + + // Set context for file_create_url()/cdn_file_url_alter(). + $GLOBALS['conf'][CDN_MODE_VARIABLE] = isset($aggregate_settings['variables'][CDN_MODE_VARIABLE]) + ? $aggregate_settings['variables'][CDN_MODE_VARIABLE] + : variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC); + $GLOBALS['conf'][CDN_BASIC_FARFUTURE_VARIABLE] = isset($aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE]) + ? $aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE] + : variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT); + $GLOBALS['conf'][CDN_HTTPS_SUPPORT_VARIABLE] = isset($aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE]) + ? $aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE] + : variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE); + $GLOBALS['conf'][CDN_STATUS_VARIABLE] = isset($aggregate_settings['variables'][CDN_STATUS_VARIABLE]) + ? $aggregate_settings['variables'][CDN_STATUS_VARIABLE] + : variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); + + // Disable CDN if cdn_check_drupal_path is FALSE. + if (empty($aggregate_settings['variables']['cdn_check_drupal_path'])) { + $original[CDN_STATUS_VARIABLE] = CDN_DISABLED; + } + + // Handle HTTPS. + if (!empty($aggregate_settings['variables']['cdn_request_is_https']) && !cdn_request_is_https()) { + if (isset($_SERVER['HTTPS'])) { + $original['HTTPS'] = $_SERVER['HTTPS']; + } + else { + $original['HTTPS'] = FALSE; + } + $_SERVER['HTTPS'] = 'on'; + } + } + elseif ($mode == 1) { + // Set context back. + $GLOBALS['conf'][CDN_MODE_VARIABLE] = $original[CDN_MODE_VARIABLE]; + $GLOBALS['conf'][CDN_BASIC_FARFUTURE_VARIABLE] = $original[CDN_BASIC_FARFUTURE_VARIABLE]; + $GLOBALS['conf'][CDN_HTTPS_SUPPORT_VARIABLE] = $original[CDN_HTTPS_SUPPORT_VARIABLE]; + $GLOBALS['conf'][CDN_STATUS_VARIABLE] = $original[CDN_STATUS_VARIABLE]; + + // Handle HTTPS. + if (isset($original['HTTPS']) && !is_null($original['HTTPS'])) { + $_SERVER['HTTPS'] = $original['HTTPS']; + } + } + } +} + +/** + * Implements hook_advagg_get_info_on_files_alter(). + * + * Used to make sure the info is up to date in the cache. + */ +function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { + // Check for the ie_css_selector_limiter. + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); + + // Get the css path. + list($css_path) = advagg_get_root_files_dir(); + $css_parts_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $css_path[1]; + $parts_path = $css_parts_path . '/parts/'; + + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + + // Check if this is a split css file. + if (strpos($info['data'], $parts_path) !== FALSE) { + $info['split'] = TRUE; + } + // Break large file into multiple small files if needed. + elseif ($info['linecount'] > $limit_value) { + advagg_split_css_file($info); + } + } + unset($info); + } + + // Capture resource_hints. + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + $aggregate_settings = advagg_current_hooks_hash_array(); + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + $file_contents = advagg_load_css_stylesheet($info['data'], FALSE, $aggregate_settings, $file_contents); + + // Get domain names and external assets in this css file. + $hosts = array(); + $urls = array(); + $matches = array(); + $pattern = '%url\(\s*+[\'"]?+(http:\/\/|https:\/\/|\/\/)([^\'"()\s]++)[\'"]?+\s*+\)%i'; + preg_match_all($pattern, $file_contents, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $key => $match) { + $url = $match . $matches[2][$key]; + $parse = @parse_url($url); + if (!empty($parse['host'])) { + $extra = ''; + $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + $supported = array( + 'eot', + 'woff2', + 'woff', + 'ttf', + 'otf', + ); + if (in_array($ext, $supported)) { + $extra .= '#crossorigin'; + } + $hosts[$parse['host'] . $extra] = $match . $parse['host'] . '/' . $extra; + $urls[$url] = $url; + } + } + } + if (!empty($hosts) && (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + )) { + $info['dns_prefetch'] = array_values($hosts); + } + + // Get local files to preload in this css file. + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + $matches = array(); + $pattern = '/url\(\s*+[\'"]?(.*?)[\'"\s]?+\)/i'; + preg_match_all($pattern, $file_contents, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $key => $match) { + $url = advagg_convert_abs_to_rel($match); + $parse = @parse_url($url); + if (empty($parse['host'])) { + $urls[$url] = $url; + } + } + } + if (!empty($urls)) { + $info['preload'] = array_values($urls); + } + } + } + unset($info); + } +} + +/** + * Implements hook_advagg_changed_files(). + * + * If the color module is enabled regenerate color module css when it changes. + * If a responsive file inside an adaptive theme has changed, regenerate it. + */ +function advagg_advagg_changed_files(array $files, array $types) { + // * @param array $files + // * List of files that have changed. + // * @param array $types + // * Array with the css and or the js key. + if (module_exists('locale')) { + _advagg_locale_changed_files($files, $types); + } + + // Keep track of what themes have been done. + static $themes_done; + if (!isset($themes_done)) { + $themes_done = array(); + } + + // Skip if no css changed. + if (empty($types['css'])) { + return; + } + + foreach ($files as $filename => $meta_data) { + // Only care about css files. + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if ($ext !== 'css') { + continue; + } + advagg_advagg_scan_for_changes($filename, TRUE); + } + + // Save error states and clear them. + $errors_before = drupal_static('form_set_error', array()); + form_clear_error(); + + // See if the css file is used the theme. + $themes = list_themes(); + $changed_files = array_keys($files); + + $submit_ran = FALSE; + foreach ($themes as $theme_name => $theme_values) { + $files_in_theme = array(); + foreach ($changed_files as $css_file) { + // Skip if we already did a form submit for this theme. + if (!empty($themes_done[$theme_name])) { + continue; + } + + // Skip if the file that was changed is not in this themes directory. + $theme_path = drupal_get_path('theme', $theme_name); + if ((!empty($theme_path)) && strpos($css_file, $theme_path) !== 0) { + continue; + } + $files_in_theme[] = $css_file; + } + + // Skip the theme if none of the changed files live in here. + if (empty($files_in_theme)) { + continue; + } + + // Get the form for this theme. + $router_item = menu_get_item('admin/appearance/settings/' . $theme_name); + if ($router_item['include_file']) { + require_once DRUPAL_ROOT . '/' . $router_item['include_file']; + } + $form = drupal_get_form('system_theme_settings', $theme_name); + // Get the form defaults. + $defaults = array(); + advagg_get_defaults_from_form($defaults, $form); + + $rebuild = FALSE; + if (isset($defaults['atcore_version_test'])) { + // Rebuild if the theme is an adaptive theme. + $rebuild = TRUE; + } + if (!$rebuild && module_exists('color')) { + foreach ($files_in_theme as $css_file) { + if (isset($form['color'])) { + // Rebuild if the file that was changed is a color module file. + foreach ($defaults['info']['css'] as $theme_file) { + if ($theme_path . '/' . $theme_file === $css_file) { + $rebuild = TRUE; + break 2; + } + } + } + } + } + + // Skip if themes css does not need to be generated dynamically. + if (empty($rebuild)) { + continue; + } + + // Build the palette value. + $palette = array(); + if (isset($form['color'])) { + foreach (element_children($form['color']['palette']) as $key) { + $palette[$key] = $form['color']['palette'][$key]['#value']; + } + } + + // Build the form state array. + $form_state = array( + 'values' => array('palette' => $palette), + 'build_info' => array('args' => array(0 => $theme_name)), + ); + $form_state['values'] += $defaults; + + if (isset($defaults['atcore_version_test'])) { + // Validate form. + at_core_settings_validate($form, $form_state); + $errors = form_set_error(); + if (empty($errors)) { + // Only submit if no errors. + at_core_settings_submit($form, $form_state); + $themes_done[$theme_name] = TRUE; + $submit_ran = TRUE; + } + } + elseif (isset($form['color'])) { + // Validate form. + color_scheme_form_validate($form, $form_state); + $errors = form_set_error(); + if (empty($errors)) { + // Only submit if no errors. + color_scheme_form_submit($form, $form_state); + $themes_done[$theme_name] = TRUE; + $submit_ran = TRUE; + } + } + // Reset form errors. + form_clear_error(); + } + // Save error states back. + $form_set_error = &drupal_static('form_set_error', array()); + $form_set_error = $errors_before; + + // Rescan again as the submit will generate new files in the css dir. + if ($submit_ran) { + advagg_push_new_changes(); + } +} + +/** + * Implements hook_advagg_scan_for_changes(). + * + * Used to see if the responsive files inside an adaptive theme has changed. + */ +function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { + // Skip if this file is not a css file. + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if ($ext !== 'css') { + return FALSE; + } + + // Skip if the file is not in an adaptive theme. + $adaptivethemes = array(); + $themes = list_themes(); + foreach ($themes as $theme_name => $theme_values) { + $path = variable_get('theme_' . $theme_name . '_files_directory', ''); + if (!empty($path) && strpos($filename, $path) !== FALSE) { + $adaptivethemes[$theme_name] = $path; + } + } + if (empty($adaptivethemes)) { + return; + } + + $file_changed = array(); + foreach ($adaptivethemes as $theme_name => $path) { + // Set up some paths we use to get and save files. + $path_to_responsive_css = drupal_get_path('theme', $theme_name) . '/css/'; + $path_to_panels_css = drupal_get_path('theme', 'adaptivetheme') . '/layouts/css/'; + + // Map files to generated file names. + $file_map = array( + "$path/$theme_name.responsive.styles.css" => array( + $path_to_responsive_css . 'responsive.custom.css', + $path_to_responsive_css . 'responsive.smalltouch.portrait.css', + $path_to_responsive_css . 'responsive.smartphone.portrait.css', + $path_to_responsive_css . 'responsive.smalltouch.landscape.css', + $path_to_responsive_css . 'responsive.smartphone.landscape.css', + $path_to_responsive_css . 'responsive.tablet.portrait.css', + $path_to_responsive_css . 'responsive.tablet.landscape.css', + $path_to_responsive_css . 'responsive.desktop.css', + ), + "$path/$theme_name.lt-ie8.layout.css" => array( + $path_to_panels_css . 'ie_defaults.css', + ), + ); + if (!isset($file_map[$filename])) { + continue; + } + + // See if anything has changed. + $changes = advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes); + if (!empty($changes)) { + $file_changed[$path] = $changes; + } + } + + return $file_changed; +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * If the locale module is enabled regenerate locale translations. + * + * @param array $files + * List of files that have changed. + * @param array $types + * Array with the css and or the js key. + */ +function _advagg_locale_changed_files(array $files, array $types) { + // Skip if no js changed. + if (empty($types['js'])) { + return; + } + + $javascript = array(); + foreach ($files as $filename => $meta_data) { + // Only care about js files. + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if ($ext !== 'js') { + continue; + } + $javascript[] = array( + 'type' => 'file', + 'data' => $filename, + ); + } + if (!empty($javascript)) { + $javascript_before = $javascript; + $language_before = $GLOBALS['language']; + $language_list = language_list(); + foreach ($language_list as $lang) { + if ($lang->enabled) { + $GLOBALS['language'] = $lang; + $javascript = $javascript_before; + _advagg_locale_js_alter($javascript); + } + } + $GLOBALS['language'] = $language_before; + } +} + +/** + * Given a form get the default values from it. + * + * @param array $defaults + * An empty array used to populate the default values. + * @param array $form + * The form returned from drupal_get_form(). + * @param string $parent_key + * The key name of the parent. + */ +function advagg_get_defaults_from_form(array &$defaults, array $form, $parent_key = '') { + foreach (element_children($form) as $key) { + $values = $form[$key]; + if (isset($values['#value'])) { + // Grab defaults at this level. + if (!isset($defaults[$key])) { + $defaults[$key] = $values['#value']; + } + else { + $defaults[$parent_key . '-' . $key] = $values['#value']; + } + } + elseif (isset($values['#default_value'])) { + // Grab defaults at this level. + if (!isset($defaults[$key])) { + $defaults[$key] = $values['#default_value']; + } + else { + $defaults[$parent_key . '-' . $key] = $values['#default_value']; + } + } + elseif (is_array($values)) { + // Go deeper if needed. + advagg_get_defaults_from_form($defaults, $values, $key); + } + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.api.php b/frontend/drupal/sites/all/modules/advagg/advagg.api.php new file mode 100644 index 000000000..b0e68d0d9 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.api.php @@ -0,0 +1,700 @@ + $data) { + if ($filename) { + // This is the filename. + } + $group = NULL; + $every_page = NULL; + foreach ($data['files'] as $fileinfo) { + // Grouped by group and every_page variables. + if (is_null($group)) { + $group = $fileinfo['group']; + } + if (is_null($every_page)) { + $every_page = $fileinfo['every_page']; + } + + // Bump Counter if group/every_page has changed from the last one. + if ($group != $fileinfo['group'] || $every_page != $fileinfo['every_page']) { + ++$counter; + $group = $fileinfo['group']; + $every_page = $fileinfo['every_page']; + $modified = TRUE; + } + $temp_new_files[$counter][] = $fileinfo; + } + ++$counter; + } + + // Replace $files array with new aggregate filenames. + $files = advagg_generate_filenames(array($temp_new_files), $type); +} + +/** + * Let other modules know about the changed files. + * + * @param array $files + * An associative array. + * filename - meta_data. + * @param array $types + * Array containing css and/or js. + * + * @return array + * Not used currently. + * + * @see advagg_push_new_changes() + * @see advagg_js_compress_advagg_changed_files() + */ +function hook_advagg_changed_files(array $files, array $types) { + // Only care about js files. + if (empty($types['js'])) { + return array(); + } + $return = array(); + foreach ($files as $filename => $meta_data) { + // Only care about js files. + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if ($ext !== 'js') { + continue; + } + + $return[$filename] = advagg_js_compress_run_test($filename); + } + return $return; +} + +/** + * Allow other modules to add in their own settings and hooks. + * + * @param array $aggregate_settings + * An associative array of hooks and settings used. + * + * @see advagg_current_hooks_hash_array() + * @see advagg_js_compress_advagg_current_hooks_hash_array_alter() + */ +function hook_advagg_current_hooks_hash_array_alter(array &$aggregate_settings) { + $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); + $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); + $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); +} + +/** + * Allow other modules to alter the contents and add new files to save (.gz). + * + * @param array $files_to_save + * Array($uri => $contents). + * @param array $aggregate_settings + * Array of settings. + * @param array $other_parameters + * Array of containing $files and $type. + * + * @see advagg_save_aggregate() + * @see advagg_advagg_save_aggregate_alter() + */ +function hook_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) { + // Return if gzip is disabled. + if (empty($aggregate_settings['variables']['advagg_gzip'])) { + return; + } + + // See if a .gz file already exists. + $gzip_exists = FALSE; + foreach ($files_to_save as $uri => $contents) { + // See if this uri contains .gz near the end of it. + $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); + if (!empty($pos)) { + $len = strlen($uri); + // .gz file exists, exit loop. + if ($pos == $len - 3) { + $gzip_exists = TRUE; + break; + } + } + } + + // If a .gz file does not exist, create one. + if (!$gzip_exists) { + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + // Compress it and add it to the $files_to_save array. + $compressed = gzencode($data, 9, FORCE_GZIP); + $files_to_save[$uri . '.gz'] = $compressed; + } +} + +/** + * Allow other modules to alter css and js paths. + * + * @param array $css_paths + * Array containing the local path and url path. + * @param array $js_paths + * Array containing the local path and url path. + * + * @see advagg_get_root_files_dir() + * @see advagg_mod_advagg_get_root_files_dir_alter() + */ +function hook_advagg_get_root_files_dir(array &$css_paths, array &$js_paths) { + $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); + if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { + return; + } + // Change directory. + $css_paths[0] = $dir . '/advagg_css'; + $js_paths[0] = $dir . '/advagg_js'; + + file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); + file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); + + // Set the URI of the directory. + $css_paths[1] = advagg_get_relative_path($css_paths[0]); + $js_paths[1] = advagg_get_relative_path($js_paths[0]); +} + +/** + * Allow other modules to modify this aggregates contents. + * + * @param string $data + * Raw CSS data. + * @param array $files + * List of files used to create this aggregate. + * @param array $aggregate_settings + * An associative array of hooks and settings used. + * + * @see advagg_get_css_aggregate_contents() + * @see advagg_css_compress_advagg_get_css_aggregate_contents_alter() + */ +function hook_advagg_get_css_aggregate_contents_alter(&$data, array $files, array $aggregate_settings) { + if (empty($aggregate_settings['variables']['advagg_css_compressor'])) { + return; + } + + if ($aggregate_settings['variables']['advagg_css_compressor'] == 2) { + advagg_css_compress_yui_cssmin($data); + } +} + +/** + * Allow other modules to modify this aggregates contents. + * + * @param string $data + * Raw JS data. + * @param array $files + * List of files used to create this aggregate. + * @param array $aggregate_settings + * An associative array of hooks and settings used. + * + * @see advagg_get_js_aggregate_contents() + * @see advagg_js_compress_advagg_get_js_aggregate_contents_alter() + */ +function hook_advagg_get_js_aggregate_contents_alter(&$data, array $files, array $aggregate_settings) { + // Do nothing if js file compression is disabled. + if (empty($aggregate_settings['variables']['advagg_js_compressor'])) { + return; + } + + // Compress it. + $filename = drupal_hash_base64(serialize($files)); + advagg_js_compress_prep($data, $filename, $aggregate_settings, FALSE); +} + +/** + * Allow other modules to modify this files contents. + * + * @param string $contents + * Raw file data. + * @param string $file + * Filename. + * @param array $aggregate_settings + * An associative array of hooks and settings used. + * + * @see advagg_get_css_aggregate_contents() + * @see advagg_css_compress_advagg_get_css_aggregate_contents_alter() + */ +function hook_advagg_get_css_file_contents_alter(&$contents, $file, array $aggregate_settings) { + if (empty($aggregate_settings['variables']['advagg_css_compressor'])) { + return; + } + + if ($aggregate_settings['variables']['advagg_css_compressor'] == 2) { + advagg_css_compress_yui_cssmin($contents); + } +} + +/** + * Allow other modules to modify this files contents. + * + * @param string $contents + * Raw file data. + * @param string $filename + * Filename. + * @param array $aggregate_settings + * An associative array of hooks and settings used. + * + * @see advagg_get_css_aggregate_contents() + * @see advagg_css_compress_advagg_get_css_aggregate_contents_alter() + */ +function hook_advagg_get_js_file_contents_alter(&$contents, $filename, array $aggregate_settings) { + // Do nothing if js file compression is disabled. + if (empty($aggregate_settings['variables']['advagg_js_compressor'])) { + return; + } + + // Make sure this file has been tested. + $compressor = $aggregate_settings['variables']['advagg_js_compressor']; + module_load_include('inc', 'advagg', 'advagg'); + $info = advagg_get_info_on_file($filename); + if (!isset($info['advagg_js_compress'][$compressor]['code'])) { + // Test file here on the spot. + $info = advagg_js_compress_run_test($filename); + } + + // Compress it if it passes the test. + if (!empty($info['advagg_js_compress'][$compressor]['code']) && $info['advagg_js_compress'][$compressor]['code'] == 1) { + advagg_js_compress_prep($contents, $filename, $aggregate_settings); + } +} + +/** + * Allow other modules to modify $css_groups right before it is processed. + * + * @param array $css_groups + * An associative array. + * key - group. + * @param bool $preprocess_css + * TRUE if preprocessing is enabled. + * + * @see _advagg_aggregate_css() + * @see advagg_css_cdn_advagg_css_groups_alter() + */ +function hook_advagg_css_groups_alter(array &$css_groups, $preprocess_css) { + // Work around a bug with seven_css_alter. + // http://drupal.org/node/1937860 + $theme_keys[] = $GLOBALS['theme']; + if (!empty($GLOBALS['base_theme_info'])) { + foreach ($GLOBALS['base_theme_info'] as $base) { + $theme_keys[] = $base->name; + } + } + $match = FALSE; + foreach ($theme_keys as $name) { + if ($name === 'seven') { + $match = TRUE; + } + } + if (empty($match)) { + return; + } + + $target = FALSE; + $last_group = FALSE; + $last_key = FALSE; + $kill_key = FALSE; + $replaced = FALSE; + foreach ($css_groups as $key => $group) { + if (empty($target)) { + if ($group['type'] === 'external' && $group['preprocess'] && $preprocess_css) { + foreach ($group['items'] as $k => $value) { + if ($value['data'] === 'themes/seven/jquery.ui.theme.css') { + // Type should be file and not external (core bug). + $value['type'] = 'file'; + $target = $value; + unset($css_groups[$key]['items'][$k]); + if (empty($css_groups[$key]['items'])) { + unset($css_groups[$key]); + $kill_key = $key; + } + } + } + } + } + else { + $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); + if ($group['type'] != $target['type'] + || $group['group'] != $target['group'] + || $group['every_page'] != $target['every_page'] + || $group['media'] != $target['media'] + || $group['media'] != $target['media'] + || $group['preprocess'] != $target['preprocess'] + || !empty($diff) + ) { + if (!empty($last_group)) { + $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); + if ($last_group['type'] != $target['type'] + || $last_group['group'] != $target['group'] + || $last_group['every_page'] != $target['every_page'] + || $last_group['media'] != $target['media'] + || $last_group['media'] != $target['media'] + || $last_group['preprocess'] != $target['preprocess'] + || !empty($diff) + ) { + // Insert New. + $css_groups[$kill_key] = array( + 'group' => $target['group'], + 'type' => $target['type'], + 'every_page' => $target['every_page'], + 'media' => $target['media'], + 'preprocess' => $target['preprocess'], + 'browsers' => $target['browsers'], + 'items' => array($target), + ); + $replaced = TRUE; + } + else { + // Insert above. + $css_groups[$last_key]['items'][] = $target; + $replaced = TRUE; + } + } + } + else { + // Insert below. + array_unshift($css_groups[$key]['items'], $target); + $replaced = TRUE; + } + } + $last_group = $group; + $last_key = $key; + if ($replaced) { + break; + } + } + ksort($css_groups); +} + +/** + * Allow other modules to modify $js_groups right before it is processed. + * + * @param array $js_groups + * An associative array. + * key - group. + * @param bool $preprocess_js + * TRUE if preprocessing is enabled. + * + * @see _advagg_aggregate_js() + * @see labjs_advagg_js_groups_alter() + */ +function hook_advagg_js_groups_alter(array &$js_groups, $preprocess_js) { + if (!$preprocess_js) { + return; + } + $labjs_location = labjs_get_path(); + + foreach ($js_groups as &$data) { + foreach ($data['items'] as &$values) { + if ($values['data'] == $labjs_location) { + // Strictly enforce preprocess = FALSE for labjs. + $values['preprocess'] = FALSE; + $data['preprocess'] = FALSE; + break 2; + } + } + unset($values); + } + unset($data); +} + +/** + * Allow other modules to modify $children and $elements before rendering. + * + * @param array $children + * An array of children elements. + * @param array $elements + * A render array containing: + * - #items: The CSS items as returned by drupal_add_css() and + * altered by drupal_get_css(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @see advagg_modify_css_pre_render() + * @see advagg_css_compress_advagg_modify_css_pre_render_alter() + */ +function hook_advagg_modify_css_pre_render_alter(array &$children, array &$elements) { + // Get variables. + $compressor = variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE); + + // Do nothing if the compressor is disabled. + if (empty($compressor)) { + return; + } + + // Do nothing if the page is not cacheable and inline compress if not + // cacheable is not checked. + if (!variable_get('advagg_css_compress_inline_if_not_cacheable', ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE) && !drupal_page_is_cacheable()) { + return; + } + + module_load_include('inc', 'advagg_css_compress', 'advagg_css_compress.advagg'); + if ($compressor == 2) { + // Compress any inline CSS with YUI. + foreach ($children as &$values) { + if (!empty($values['#value'])) { + advagg_css_compress_yui_cssmin($values['#value']); + } + } + unset($values); + } +} + +/** + * Allow other modules to modify $children and $elements before rendering. + * + * @param array $children + * An array of children elements. + * @param array $elements + * A render array containing: + * - #items: The JavaScript items as returned by drupal_add_js() and + * altered by drupal_get_js(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @see advagg_modify_js_pre_render() + * @see advagg_js_compress_advagg_modify_js_pre_render_alter() + */ +function hook_advagg_modify_js_pre_render_alter(array &$children, array &$elements) { + // Get variables. + $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE); + $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); + + // Do nothing if the compressor is disabled. + if (empty($aggregate_settings['variables']['advagg_js_compressor'])) { + return; + } + + // Do nothing if the page is not cacheable and inline compress if not + // cacheable is not checked. + if (!variable_get('advagg_js_compress_inline_if_not_cacheable', ADVAGG_JS_COMPRESS_INLINE_IF_NOT_CACHEABLE) && !drupal_page_is_cacheable()) { + return; + } + + // Compress any inline JS. + module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); + foreach ($children as &$values) { + if (!empty($values['#value'])) { + $contents = $values['#value']; + $filename = drupal_hash_base64($contents); + advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE); + $values['#value'] = $contents; + } + } + unset($values); +} + +/** + * Allow other modules to swap important contextual information on generation. + * + * @param array $original + * Array of original settings. + * @param array $aggregate_settings + * Array of contextual settings. + * @param int $mode + * Use 0 to change context to what is inside of $aggregate_settings. + * Use 1 to change context back. + * + * @see advagg_context_switch() + * @see advagg_advagg_context_alter() + */ +function hook_advagg_context_alter(array &$original, array $aggregate_settings, $mode) { + if ($mode == 0) { + // Change context. + $original['base_root'] = $GLOBALS['base_root']; + $original['base_url'] = $GLOBALS['base_url']; + $original['base_path'] = $GLOBALS['base_path']; + $original['is_https'] = $GLOBALS['is_https']; + $GLOBALS['is_https'] = $aggregate_settings['variables']['is_https']; + if ($aggregate_settings['variables']['is_https']) { + $GLOBALS['base_root'] = str_replace('http://', 'https://', $GLOBALS['base_root']); + $GLOBALS['base_url'] = str_replace('http://', 'https://', $GLOBALS['base_url']); + } + else { + $GLOBALS['base_root'] = str_replace('https://', 'http://', $GLOBALS['base_root']); + $GLOBALS['base_url'] = str_replace('https://', 'http://', $GLOBALS['base_url']); + } + $GLOBALS['base_path'] = $aggregate_settings['variables']['base_path']; + } + elseif ($mode == 1) { + // Change context back. + if (isset($original['base_root'])) { + $GLOBALS['base_root'] = $original['base_root']; + } + if (isset($original['base_url'])) { + $GLOBALS['base_url'] = $original['base_url']; + } + if (isset($original['base_path'])) { + $GLOBALS['base_path'] = $original['base_path']; + } + if (isset($original['is_https'])) { + $GLOBALS['is_https'] = $original['is_https']; + } + } +} + +/** + * Let other modules know about the aggregate files that have been removed. + * + * @param array $kill_list + * An array of aggregate files that have been removed. + * + * @see advagg_delete_files_if_stale() + */ +function hook_advagg_removed_aggregates(array $kill_list) { + foreach ($kill_list as $uri) { + if ($uri) { + // This is the uri. + } + // Do something else. + } +} + +/** + * Let other modules tell advagg that a file has changed. + * + * Useful for things like embedded images in CSS; generating a new aggregate + * when the image in the CSS file has changed. + * + * @param string $filename + * Name of the root CSS or JavaScript file. + * + * @return bool + * Set to TRUE to trigger a rebuild of the aggregates that contain this file. + * + * @see advagg_scan_for_changes() + * @see css_emimage_advagg_scan_for_changes() + */ +function hook_advagg_scan_for_changes($filename) { + if ($filename) { + return FALSE; + } +} + +/** + * Let other modules add/alter additional information about files passed in. + * + * @param array $return + * An associative array; filename -> data. + * @param array $cached_data + * What data was found in the cache; cache_id -> data. + * @param bool $bypass_cache + * If TRUE the loaded data did not come from the cache. + * + * @see advagg_get_info_on_files() + * @see advagg_advagg_get_info_on_files_alter() + */ +function hook_advagg_get_info_on_files_alter(array &$return, array $cached_data, $bypass_cache) { + if (!variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + return; + } + $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($js_path) { + // This is the js_path array. + } + $parts_path = $css_path[1] . '/parts'; + + foreach ($return as $filename => &$info) { + if ($filename) { + // This is the filename. + } + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + + // Break large file into multiple small files. + if ($info['linecount'] > $limit_value) { + advagg_split_css_file($info); + } + elseif (strpos($info['data'], $parts_path) === 0) { + $info['split'] = TRUE; + } + } + unset($info); +} + +/** + * Tell advagg about other hooks related to advagg. + * + * @param array $hooks + * Array of hooks related to advagg. + * @param bool $all + * If FALSE get only the subset of hooks that alter the filename/contents. + * + * @see advagg_hooks_implemented() + * @see advagg_bundler_advagg_hooks_implemented_alter() + */ +function hook_advagg_hooks_implemented_alter(array &$hooks, $all) { + if ($all) { + $hooks['advagg_bundler_analysis_alter'] = array(); + } +} + +/** + * Let other modules modify the analysis array before it is used. + * + * @param array $analysis + * An associative array; filename -> data. + * + * @see advagg_bundler_analysis() + */ +function hook_advagg_bundler_analysis_alter(array &$analysis) { + foreach ($analysis as $filename => &$data) { + if ($filename) { + // This is the filename. + } + + // This changes often; 604800 is 1 week. + if ($data['changes'] > 10 && $data['mtime'] >= REQUEST_TIME - 604800) { + // Modify the group hash so this doesn't end up in a big aggregate. + $data['group_hash']; + } + } + unset($data); +} + +/** + * @} End of "defgroup advagg_hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.cache.inc b/frontend/drupal/sites/all/modules/advagg/advagg.cache.inc new file mode 100644 index 000000000..8d1d1586c --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.cache.inc @@ -0,0 +1,945 @@ +fields('af') + ->execute(); + if (!empty($result)) { + module_load_include('inc', 'advagg', 'advagg'); + $filenames = array(); + + $data = array(); + foreach ($result as $row) { + $filenames[] = $row->filename; + $data[$row->filename] = (array) $row; + } + + // Get filesystem data. + $files_info = advagg_get_info_on_files($filenames, TRUE); + foreach ($files_info as $info) { + if (!isset($data[$info['data']])) { + continue; + } + $row = $data[$info['data']]; + + // Select the keys to compare. + $keys_to_compare = array( + 'filesize', + 'content_hash', + 'linecount', + ); + $changed = array(); + foreach ($keys_to_compare as $key) { + if ($row[$key] != $info[$key]) { + $changed[] = $key . ' db:' . $row[$key] . ' file:' . $info[$key]; + break; + } + } + // Compare mtime if it is not zero. + if (empty($info['split']) && !empty($info['mtime'])) { + if (variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK) && $row['mtime'] != $info['mtime']) { + $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; + } + elseif ($row['mtime'] < $info['mtime']) { + $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; + } + } + + if (empty($changed)) { + // Call hook_advagg_scan_for_changes(). + $changes_array = module_invoke_all('advagg_scan_for_changes', $row['filename']); + if (is_array($changes_array)) { + foreach ($changes_array as $value) { + if (!empty($value)) { + $changed[] = $value; + break; + } + } + } + } + + // If file has changed, add it to the array. + if (!empty($changed)) { + $info['changes'] = $changed; + $files_that_have_changed[$row['filename']] = $info; + } + } + } + + return $files_that_have_changed; +} + +/** + * Flush the correct caches so CSS/JS changes go live. + * + * @return array + * Array of files that have changed and caches flushed. + */ +function advagg_push_new_changes(array $files = array()) { + $results = array(); + // Scan the file system for changes to CSS/JS files. + if (empty($files)) { + $files = advagg_scan_for_changes(); + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Changes detected in
    @files
    .', array('@files' => print_r($files, TRUE)), WATCHDOG_DEBUG); + } + } + + // Clear some static caches. + drupal_static_reset('advagg_get_info_on_file'); + drupal_static_reset('advagg_drupal_hash_base64'); + drupal_static_reset('advagg_current_hooks_hash_array'); + drupal_static_reset('advagg_get_current_hooks_hash'); + + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + // Exception used to get a compact stack trace. + $e = new Exception(); + watchdog('advagg-debug', 'New changes called by:
    @changes
    ', array('@changes' => print_r($e->getTraceAsString(), TRUE)), WATCHDOG_DEBUG); + } + + // If something changed, flush the correct caches so that change goes out. + if (!empty($files)) { + $types = array(); + module_load_include('inc', 'advagg', 'advagg'); + foreach ($files as $filename => $meta_data) { + // Lookup the aggregates/cache ids that use this file. + $cache_ids = advagg_get_aggregates_using_file($meta_data['filename_hash'], TRUE); + $cache_hits = array(); + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $types[$ext] = TRUE; + + if (!empty($cache_ids)) { + $cache_hits = cache_get_multiple($cache_ids, 'cache_advagg_info'); + foreach ($cache_hits as $cid => $data) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Clearing cache @cid.', array('@cid' => $cid), WATCHDOG_DEBUG); + } + cache_clear_all($cid, 'cache_advagg_info', FALSE); + } + } + + $changes = array(); + if (!empty($meta_data['changes'])) { + $changes = $meta_data['changes']; + unset($meta_data['changes']); + } + + $results[$filename] = array( + count($cache_ids), + count($cache_hits), + $changes, + ); + + // Update database. + advagg_insert_update_files(array($filename => $meta_data), $ext); + } + + // Change query-strings on css/js files to enforce reload for all users. + // Change css_js_query_string variable. + _drupal_flush_css_js(); + + // Let other modules know about the changed files. + // Call hook_advagg_changed_files(). + module_invoke_all('advagg_changed_files', $files, $types); + + // Clear out the full aggregates cache. + foreach ($types as $ext => $bool) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Clearing cache advagg:@ext: in cache_advagg_aggregates.', array('@ext' => print_r($ext, TRUE)), WATCHDOG_DEBUG); + } + cache_clear_all('advagg:' . $ext . ':', 'cache_advagg_aggregates', TRUE); + } + } + // Return what was done. + return $results; +} + +/** + * Given a filename hash get back all aggregates that include it. + * + * @param string $filename_hash + * Hash of the filename. + * @param bool $cid_only + * Set to TRUE to only have cache ids returned. + * + * @return array + * Array of aggregates that use this file. + */ +function advagg_get_aggregates_using_file($filename_hash, $cid_only = FALSE) { + // Create main query for the advagg_aggregates table. + $query = db_select('advagg_aggregates', 'aa') + ->condition('aa.filename_hash', $filename_hash); + // Create join query for the advagg_aggregates_versions table. + $query->join('advagg_aggregates_versions', 'aav', 'aa.aggregate_filenames_hash = aav.aggregate_filenames_hash AND aav.atime > 0'); + $query = $query->fields('aav', array('aggregate_filenames_hash', 'aggregate_contents_hash')); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + // Put results into $aggregates array. + $aggregates = array(); + foreach ($results as $row) { + $row = (array) $row; + $cid = 'advagg:db:' . $row['aggregate_filenames_hash'] . ADVAGG_SPACE . $row['aggregate_contents_hash']; + if ($cid_only) { + $aggregates[] = $cid; + } + else { + $row['cid'] = $cid; + $aggregates[] = $row; + } + } + return $aggregates; +} + +/** + * Get all CSS/JS advagg files. + * + * @param array $options + * Array of options to pass along to file_scan_directory(). + * + * @return array + * Array of css and js files. + */ +function advagg_get_all_files(array $options = array()) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $options += array('nomask' => '/(\.\.?|CVS|\.gz|\.br)$/'); + + // Get a list of files. + $css_files = file_scan_directory($css_path[0], '/.*/', $options); + $js_files = file_scan_directory($js_path[0], '/.*/', $options); + return array($css_files, $js_files); +} + +/** + * Scan CSS/JS advagg dir and remove that file if atime is grater than 30 days. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_stale_aggregates() { + list($css_files, $js_files) = advagg_get_all_files(); + + // Make the advagg_get_hashes_from_filename() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $css_files = advagg_delete_files_if_stale($css_files); + $js_files = advagg_delete_files_if_stale($js_files); + return array($css_files, $js_files); +} + +/** + * Given an array of files remove that file if atime is grater than 30 days. + * + * @param array $files + * Array of files returned by file_scan_directory. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_files_if_stale(array $files) { + // Array used to record what files were deleted. + $kill_list = array(); + + foreach ($files as $uri => $file) { + // Get info on file. + $filename = $file->filename; + $data = advagg_get_hashes_from_filename($filename); + if (is_array($data)) { + list(, $aggregate_filenames_hash, $aggregate_contents_hash) = $data; + } + else { + // Can not get data on file, remove it. + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; + } + + // Get atime of file. + $atime = advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri); + if (empty($atime)) { + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; + } + + // Default stale file threshold is 30 days. + if (REQUEST_TIME - $atime > variable_get('drupal_stale_file_threshold', 2592000)) { + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; + } + + } + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); + return $kill_list; +} + +/** + * Scan CSS/JS advagg dir and remove that file if it is empty. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_empty_aggregates() { + list($css_files, $js_files) = advagg_get_all_files(); + $css_files = advagg_delete_files_if_empty($css_files); + $js_files = advagg_delete_files_if_empty($js_files); + return array($css_files, $js_files); +} + +/** + * Given an array of files remove that file if it is empty. + * + * @param array $files + * Array of files returned by file_scan_directory. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_files_if_empty(array $files) { + // Array used to record what files were deleted. + $kill_list = array(); + + foreach ($files as $uri => $file) { + // Ignore temp files. There's a separate process for cleaning those up. + if (strpos($uri, '/advagg_file_') !== FALSE) { + continue; + } + $size = filesize($uri); + if ($size === 0) { + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; + } + } + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); + return $kill_list; +} + +/** + * Delete a file, and any compressed versions. + * + * @param string $uri + * URI of the file to delete. + * + * @return string + * The given URI. + */ +function advagg_delete_file_by_uri($uri) { + if (file_exists($uri)) { + file_unmanaged_delete($uri); + } + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + if (file_exists($uri . '.br')) { + file_unmanaged_delete($uri . '.br'); + } + return $uri; +} + +/** + * Perform a cache_clear_all on all bins returned by advagg_flush_caches(TRUE). + * + * @param bool $push_new_changes + * FALSE: Do not scan for changes. + */ +function advagg_flush_all_cache_bins($push_new_changes = TRUE) { + $bins = advagg_flush_caches(TRUE, $push_new_changes); + foreach ($bins as $bin) { + cache_clear_all('*', $bin, TRUE); + } +} + +/** + * Remove all files from the advagg CSS/JS directories. + * + * @param bool $kill_htaccess + * Set to TRUE to remove the htaccess files as well. + * + * @return array + * Array of all files removed. + */ +function advagg_remove_all_aggregated_files($kill_htaccess = FALSE) { + $options = array( + 'callback' => 'file_unmanaged_delete', + 'nomask' => '/(\.\.?|CVS)$/', + ); + list($css_files, $js_files) = advagg_get_all_files($options); + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $css_files); + module_invoke_all('advagg_removed_aggregates', $js_files); + + // Remove the htaccess files as well. + if ($kill_htaccess) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if (file_exists($css_path[0] . '/.htaccess')) { + file_unmanaged_delete($css_path[0] . '/.htaccess'); + $css_files[] = $css_path[0] . '/.htaccess'; + } + if (file_exists($js_path[0] . '/.htaccess')) { + file_unmanaged_delete($js_path[0] . '/.htaccess'); + $js_files[] = $js_path[0] . '/.htaccess'; + } + } + return array($css_files, $js_files); +} + +/** + * Increment the advagg_global_counter variable by one. + * + * @todo Allow this value to be kept in sync across a multisite. + * + * @return int + * New value of advagg_global_counter. + */ +function advagg_increment_global_counter() { + $new_value = advagg_get_global_counter() + 1; + variable_set('advagg_global_counter', $new_value); + return $new_value; +} + +/** + * Scan for missing files and remove the associated entries in the database. + * + * @return array + * Array of what files were cleared out of the database. + */ +function advagg_remove_missing_files_from_db() { + $missing_files = array(); + $deleted = array(); + + // Get all files stored in the database. + $result = db_select('advagg_files', 'af') + ->fields('af') + ->execute(); + if (empty($result)) { + return $deleted; + } + + // Find missing files. + module_load_include('inc', 'advagg', 'advagg'); + foreach ($result as $row) { + $row = (array) $row; + $info = advagg_get_info_on_file($row['filename'], TRUE); + + // Make sure file exists. + if (empty($info['content_hash'])) { + $info += advagg_get_aggregates_using_file($info['filename_hash']); + $missing_files[$row['filename']] = $info; + continue; + } + } + if (empty($missing_files)) { + return $deleted; + } + + // Remove missing file database entries. + $types = array(); + foreach ($missing_files as $filename => $data) { + // Setup this run. + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $advagg_files_del = 0; + $advagg_aggregates_del = 0; + $advagg_aggregates_versions_del = 0; + $clean_sweep = TRUE; + $filename_hash = ''; + + // Scan the data. + foreach ($data as $key => $values) { + if (!is_numeric($key)) { + $filename_hash = $values; + } + else { + // Remove the entry from the database if this aggregate has not been + // accessed in the last 2 weeks. + $can_delete = db_delete('advagg_aggregates_versions') + ->condition('aggregate_filenames_hash', $values['aggregate_filenames_hash']) + ->condition('atime', REQUEST_TIME - variable_get('advagg_remove_missing_files_from_db_time', ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME), '<') + ->execute(); + + if ($can_delete > 0) { + $advagg_aggregates_versions_del += $can_delete; + $advagg_aggregates_del += db_delete('advagg_aggregates') + ->condition('aggregate_filenames_hash', $values['aggregate_filenames_hash']) + ->execute(); + } + else { + $clean_sweep = FALSE; + } + // Clear the cache. + cache_clear_all($values['cid'], 'cache_advagg_info', FALSE); + } + } + + // Remove the file entry if all aggregates referencing it have been removed. + if ($clean_sweep) { + $advagg_files_del += db_delete('advagg_files') + ->condition('filename_hash', $filename_hash) + ->execute(); + + // Add info to array. + if (!empty($advagg_files_del) + || !empty($advagg_aggregates_versions_del) + || !empty($advagg_aggregates_del) + ) { + $types[$ext] = TRUE; + $deleted[$filename] = array( + 'advagg_files' => $advagg_files_del, + 'advagg_aggregates_versions' => $advagg_aggregates_versions_del, + 'advagg_aggregates' => $advagg_aggregates_del, + ); + } + } + } + + // If something was deleted, clear the full aggregates cache. + if (!empty($deleted)) { + foreach ($types as $ext => $bool) { + cache_clear_all('advagg:' . $ext . ':', 'cache_advagg_aggregates', TRUE); + } + } + + // Return what was deleted. + return $deleted; +} + +/** + * Scan CSS/JS advagg dir and remove file if there is no associated db record. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_orphaned_aggregates() { + list($css_files, $js_files) = advagg_get_all_files(); + + // Make the advagg_get_hashes_from_filename() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $css_files = advagg_delete_files_if_orphaned($css_files); + $js_files = advagg_delete_files_if_orphaned($js_files); + return array($css_files, $js_files); +} + +/** + * Given an array of files remove that file if there is no associated db record. + * + * @param array $files + * Array of files returned by file_scan_directory. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_files_if_orphaned(array $files) { + // Get the uri for the advagg_css/parts directory. + list($css_path) = advagg_get_root_files_dir(); + $parts_uri = $css_path[0] . '/parts/'; + + // Array used to record what files were deleted. + $kill_list = $keyed_file_list = array(); + + // Create a listing of all file names and associated hashes. + foreach ($files as $uri => $file) { + // Get info on file. + $data = advagg_get_hashes_from_filename($file->filename, TRUE); + if (is_array($data)) { + list(, $aggregate_filenames_hash) = $data; + // Check to see if the file is in the database. + $keyed_file_list[$aggregate_filenames_hash] = $uri; + } + else { + // Check to see if this is a parts css file. + $start = strpos($file->uri, $parts_uri); + if ($start !== FALSE) { + // Get the original filename. + $original_file = substr($file->uri, $start + strlen($parts_uri)); + $original_file = preg_replace('/(.\\d+\\.css)$/i', '.css', $original_file); + if (file_exists($original_file)) { + // Original file exists, do not delete. + continue; + } + } + + // Can not get data on file, remove it. + $kill_list[] = $uri; + continue; + } + } + + if (!empty($keyed_file_list)) { + $filenames_hash = array_keys($keyed_file_list); + $aggregates_in_database = array(); + // Process in chunks when a large array is passed. + do { + // Check if the aggregate_filenames_hash exists in the database. + $aggregates_in_database += db_select('advagg_aggregates_versions', 'av') + ->fields('av', array('aggregate_filenames_hash')) + ->condition('av.aggregate_filenames_hash', array_splice($filenames_hash, 0, 1000), 'IN') + ->distinct() + ->execute() + ->fetchAllAssoc('aggregate_filenames_hash'); + } while (count($filenames_hash)); + + // Get values not found in the database. + $to_delete = array_values(array_diff_key($keyed_file_list, $aggregates_in_database)); + // Add the file uri to the kill list. + $kill_list = array_merge($kill_list, $to_delete); + } + + if (!empty($kill_list)) { + foreach ($kill_list as $uri) { + advagg_delete_file_by_uri($uri); + } + } + + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); + return $kill_list; +} + +/** + * Delete aggregates that have not been accessed in the last 6 weeks. + * + * @return int + * Count of the number of rows removed from the databases. + */ +function advagg_remove_old_unused_aggregates() { + $advagg_aggregates_versions_del = 0; + $advagg_aggregates_del = 0; + + // Find orphaned aggregate versions entries. + // Create main query. + $query = db_select('advagg_aggregates_versions', 'aav') + ->fields('aav', array('aggregate_filenames_hash')) + ->groupBy('aav.aggregate_filenames_hash'); + // Create join and add in query comment. + $query->leftjoin('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); + $query->isNull('aa.aggregate_filenames_hash'); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + // If we have an orphaned db entry, delete it. + if (!empty($results)) { + foreach ($results as $row) { + $advagg_aggregates_versions_del += db_delete('advagg_aggregates_versions') + ->condition('aggregate_filenames_hash', $row->aggregate_filenames_hash) + ->execute(); + } + } + + // Delete aggregate versions that have not been accessed in the last 45 days. + $advagg_aggregates_versions_del += db_delete('advagg_aggregates_versions') + ->condition('atime', REQUEST_TIME - variable_get('advagg_remove_old_unused_aggregates_time', ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME), '<') + ->execute(); + + // See if any aggregates are orphaned now. + // Create main query. + $query = db_select('advagg_aggregates', 'aa') + ->fields('aa', array('aggregate_filenames_hash')) + ->groupBy('aa.aggregate_filenames_hash'); + // Create join and add in query comment. + $query->leftjoin('advagg_aggregates_versions', 'aav', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); + $query->isNull('aav.aggregate_filenames_hash'); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + // If we have an orphaned db entry, delete it. + if (!empty($results)) { + foreach ($results as $row) { + $advagg_aggregates_del += db_delete('advagg_aggregates') + ->condition('aggregate_filenames_hash', $row->aggregate_filenames_hash) + ->execute(); + } + } + + // Return the total count of entires removed from the database. + return $advagg_aggregates_versions_del + $advagg_aggregates_del; +} + +/** + * Delete orphaned/expired advagg locks from the semaphore database table. + * + * @return int + * Count of the number of rows removed from the databases. + */ +function advagg_cleanup_semaphore_table() { + // Let expiration times vary by 5 minutes. + $fuzz_factor = 300; + $results = db_delete('semaphore') + ->condition('name', db_like('advagg_') . '%', 'LIKE') + ->condition('expire', REQUEST_TIME - $fuzz_factor, '<') + ->execute(); + return $results; +} + +/** + * Delete leftover temp files. + * + * @return int + * Count of the number of files removed + */ +function advagg_remove_temp_files() { + // Make sure advagg_get_root_files_dir() is available. + drupal_load('module', 'advagg'); + // Make sure advagg_install_delete_empty_file_if_stale() is available. + module_load_include('install', 'advagg', 'advagg'); + + // Get the advagg paths. + $advagg_path = advagg_get_root_files_dir(); + $total_count = 0; + // Get the top level path. + $top_level = substr($advagg_path[0][0], 0, strpos($advagg_path[0][0], 'advagg_css')); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/file.*|fil.*\.tmp/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/file_advagg_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_css. + $files = file_scan_directory($advagg_path[0][0], '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Remove empty temp files from public://advagg_js. + $files = file_scan_directory($advagg_path[1][0], '/advagg_file_.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_delete_temp_file_if_stale', + )); + foreach ($files as $key => $file) { + if (file_exists($file->uri)) { + unset($files[$key]); + } + } + $total_count += count($files); + + // Output info. + return $total_count; +} + +/** + * Refresh all locale files. + * + * @return int + * Count of the number of files removed + */ +function advagg_refresh_all_locale_files() { + $locale_files = array(); + if (!module_exists('locale')) { + return $locale_files; + } + + $results = db_select('advagg_files', 'af') + ->fields('af') + ->condition('af.filetype', 'js') + ->condition('af.filesize', 0, '>') + ->execute(); + $javascript = array(); + foreach ($results as $row) { + $javascript[] = array( + 'type' => 'file', + 'data' => $row->filename, + ); + } + + if (!empty($javascript)) { + $javascript_before = $javascript; + $language_before = $GLOBALS['language']; + $language_list = language_list(); + foreach ($language_list as $lang) { + if ($lang->enabled) { + $GLOBALS['language'] = $lang; + $javascript = $javascript_before; + locale_js_alter($javascript); + $locale_file = array_diff_key($javascript, $javascript_before); + $locale_files += $locale_file; + } + } + $GLOBALS['language'] = $language_before; + } + return $locale_files; +} + +/** + * Callback to delete files if modified more than 60 seconds ago. + * + * @param string $uri + * Location of the file to check. + */ +function advagg_delete_temp_file_if_stale($uri) { + // Set stale file threshold to 60 seconds. + if (REQUEST_TIME - filemtime($uri) > 60) { + file_unmanaged_delete($uri); + } +} + +/** + * See if any of the subfiles has changed. + * + * @param string $filename + * Name of the file that is related to the subfiles. + * @param array $subfiles + * An array of files to check for changes. + * @param string $keyname + * Under what key to save the info on the files. + * @param bool $save_changes + * If TRUE then the changes will be updated in the cache. + * + * @return bool + * TRUE if one of the subfiles has changed. + */ +function advagg_detect_subfile_changes($filename, array $subfiles, $keyname, $save_changes = FALSE) { + // Get the info on this file from the cache. + module_load_include('inc', 'advagg', 'advagg'); + $info = advagg_get_info_on_file($filename); + $hash_id = 'advagg:subfiles:' . $keyname . ':' . $info['filename_hash']; + if (!isset($info[$keyname])) { + // Pull up the info from the database if missing from the cache. + $info[$keyname] = advagg_get_hash_settings($hash_id); + } + + $subfile_changed = array(); + // Check every subfile seeing if they have changed. + foreach ($subfiles as $subfile) { + $current_file_info = $defaults = array( + 'hash' => '', + 'size' => 0, + 'mtime' => 0, + ); + + // Get the currently saved info on this file. + $saved_file_info = isset($info[$keyname][$subfile]) ? $info[$keyname][$subfile] : array(); + $saved_file_info += $defaults; + + // Get the current info on the file. + if (file_exists($subfile)) { + $current_file_info = array( + 'hash' => drupal_hash_base64((string) @advagg_file_get_contents($subfile)), + 'size' => filesize($subfile), + 'mtime' => filemtime($subfile), + ); + } + + // Set the info in case a save happens. + $info[$keyname][$subfile] = $current_file_info; + + // Check for any differences. + $diff = array_diff_assoc($saved_file_info, $current_file_info); + if (!empty($diff)) { + $subfile_changed[$subfile] = $diff; + } + } + if (!empty($subfile_changed) && $save_changes) { + $cache_id = 'advagg:file:' . $info['filename_hash']; + + // Set static cache. + $filename_hashes = &drupal_static('advagg_get_info_on_file'); + $filename_hashes[$cache_id] = $info; + + // Set drupal cache. + cache_set($cache_id, $info, 'cache_advagg_info', CACHE_PERMANENT); + + // Save to database. + advagg_set_hash_settings($hash_id, $info[$keyname]); + } + return $subfile_changed; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.developer-documentation.php b/frontend/drupal/sites/all/modules/advagg/advagg.developer-documentation.php new file mode 100644 index 000000000..f49d8ac0f --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.developer-documentation.php @@ -0,0 +1,41 @@ + advagg_get_global_counter())); + } +} + +/** + * Implements hook_drush_command(). + */ +function advagg_drush_command() { + $items = array(); + $items['advagg-cron'] = array( + 'description' => dt('Run the advagg cron hook.'), + 'examples' => array( + 'Standard example' => 'drush advagg-cron', + ), + 'aliases' => array('advagg-c'), + ); + $items['advagg-clear-db-cache'] = array( + 'description' => dt('Remove all entries from the advagg cache bins.'), + 'examples' => array( + 'Standard example' => 'drush advagg-clear-db-cache', + ), + 'aliases' => array('advagg-cdc'), + ); + $items['advagg-clear-all-files'] = array( + 'description' => dt('Remove all generated files.'), + 'examples' => array( + 'Standard example' => 'drush advagg-clear-all-files', + ), + 'aliases' => array('advagg-caf'), + ); + $items['advagg-force-new-aggregates'] = array( + 'description' => dt('Force the creation of all new aggregates by incrementing a global counter.'), + 'examples' => array( + 'Standard example' => 'drush advagg-force-new-aggregates', + ), + 'aliases' => array('advagg-fna'), + ); + return $items; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * Callback function for drush advagg-force-new-aggregates. + * + * Callback is called by using drush_hook_command() where + * hook is the name of the module (advagg) and command is the name of + * the Drush command with all "-" characters converted to "_" characters. + */ +function drush_advagg_force_new_aggregates() { + // Clear out the cache. + drush_advagg_clear_db_cache(); + + // Increment counter. + module_load_include('inc', 'advagg', 'advagg.cache'); + $new_value = advagg_increment_global_counter(); + drush_log(dt('Global counter is now set to @new_value', array('@new_value' => $new_value)), 'ok'); +} + +/** + * Callback function for drush advagg-clear-all-files. + * + * Callback is called by using drush_hook_command() where + * hook is the name of the module (advagg) and command is the name of + * the Drush command with all "-" characters converted to "_" characters. + */ +function drush_advagg_clear_all_files() { + // Clear out the cache. + drush_advagg_clear_db_cache(); + + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_files, $js_files) = advagg_remove_all_aggregated_files(); + + // Report back the results. + drush_log(dt('All AdvAgg files have been deleted. @css_count CSS files and @js_count JS files have been removed.', array( + '@css_count' => count($css_files), + '@js_count' => count($js_files), + )), 'ok'); +} + +/** + * Callback function for drush advagg-clear-db-cache. + * + * Callback is called by using drush_hook_command() where + * hook is the name of the module (advagg) and command is the name of + * the Drush command with all "-" characters converted to "_" characters. + */ +function drush_advagg_clear_db_cache() { + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + advagg_flush_all_cache_bins(); + + // Report back the results. + drush_log(dt('All AdvAgg cache bins have been cleared.'), 'ok'); +} + +/** + * Callback function for drush advagg-cron. + * + * Callback is called by using drush_hook_command() where + * hook is the name of the module (advagg) and command is the name of + * the Drush command with all "-" characters converted to "_" characters. + */ +function drush_advagg_cron() { + // Run AdvAgg cron job. + $output = advagg_cron(TRUE); + + // Output results from running advagg_delete_stale_aggregates(). + list($css_files, $js_files) = $output[0]; + if (count($css_files) > 0 || count($js_files) > 0) { + drush_log(dt('All stale aggregates have been deleted. @css_count CSS files and @js_count JS files have been removed.', array( + '@css_count' => count($css_files), + '@js_count' => count($js_files), + )), 'ok'); + } + else { + drush_log(dt('No stale aggregates found. Nothing was deleted.'), 'ok'); + } + + // Output results from running advagg_delete_orphaned_aggregates(). + if (empty($output[1][0]) && empty($output[1][1])) { + drush_log(dt('All files have an associated db record; nothing was deleted.'), 'ok'); + } + else { + drush_log(dt('Some files had no associated db record and could be safely deleted from the file system. @raw', array('@raw' => print_r($output[1], TRUE))), 'ok'); + } + + // Output results from running advagg_remove_missing_files_from_db(). + if (empty($output[2])) { + drupal_set_message(dt('All source files where found, no database entries where pruned.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('Some source files are missing and as a result some unused aggregates were found. A total of @count database entries were removed.', array('@count' => count($output[2]))), 'ok'); + } + + // Output results from running advagg_remove_old_unused_aggregates(). + if (empty($output[3])) { + drupal_set_message(dt('No old and unused aggregates found. Nothing was deleted.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('Some old and unused aggregates were found. A total of @count database entries were removed.', array('@count' => $output[3])), 'ok'); + } + + // Output results from running advagg_cleanup_semaphore_table(). + if (empty($output[4])) { + drupal_set_message(dt('No old semaphore locks found.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('A total of @count old semaphore entries were removed.', array('@count' => count($output[4]))), 'ok'); + } + + // Output results from running advagg_remove_temp_files(). + if (empty($output[5])) { + drupal_set_message(dt('No leftover temporary files found. Nothing was deleted.'), 'ok'); + } + else { + // format_plural() not always available. + drupal_set_message(dt('Some oleftover temporary files were found. A total of @count temporary files were removed.', array('@count' => $output[5])), 'ok'); + } + + // Output results from running advagg_refresh_all_locale_files(). + if (empty($output[6])) { + drupal_set_message(dt('Locale did not translate anything in any JavaScript files.'), 'ok'); + } + else { + drupal_set_message(dt('Locale did translate some JavaScript files. Resulting locale js files: @files', array('@files' => print_r($output[6], TRUE))), 'ok'); + } +} + +/** + * Flush the correct caches so CSS/JS changes go live. + */ +function drush_advagg_smart_cache_flush() { + // Clear the libraries cache. + if (function_exists('libraries_flush_caches')) { + $cache_tables = libraries_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + } + + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + $flushed = advagg_push_new_changes(); + + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + // Display a simple message if not in Development mode. + drush_log('Advagg Cache Cleared', 'ok'); + } + else { + list($css_path) = advagg_get_root_files_dir(); + $parts_uri = $css_path[1] . '/parts'; + + // Report back the results. + foreach ($flushed as $filename => $data) { + if (strpos($filename, $parts_uri) === 0) { + // Do not report on css files manged in the parts directory. + unset($flushed[$filename]); + continue; + } + $ext = pathinfo($filename, PATHINFO_EXTENSION); + drush_log(dt('The file @filename has changed. @db_usage aggregates are using this file. @db_count db cache entries and all @type full cache entries have been flushed from the cache bins. Trigger: @changes', array( + '@filename' => $filename, + '@db_usage' => $data[0], + '@db_count' => $data[1], + '@changes' => print_r($data[2], TRUE), + '@type' => $ext, + )), 'ok'); + } + + if (empty($flushed)) { + drush_log(dt('No changes found. Nothing was cleared.'), 'ok'); + return; + } + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.inc b/frontend/drupal/sites/all/modules/advagg/advagg.inc new file mode 100644 index 000000000..7db438b36 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.inc @@ -0,0 +1,1805 @@ + $aggregate_filenames_hash, + 'aggregate_contents_hash' => $aggregate_contents_hash, + 'atime' => 0, + 'root' => $root, + ); + + // Save new aggregate into the database if it does not exist. + $return = db_merge('advagg_aggregates_versions') + ->key(array( + 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], + 'aggregate_contents_hash' => $record['aggregate_contents_hash'], + )) + ->insertFields($record) + ->execute(); + return $return; +} + +/** + * Insert/Update data in the advagg_aggregates table. + * + * @param array $files + * List of files in the aggregate including files meta data. + * @param string $aggregate_filenames_hash + * Hash of the groupings of files. + * + * @return bool + * Return TRUE if anything was written to the database. + */ +function advagg_insert_aggregate(array $files, $aggregate_filenames_hash) { + // Record if a database write was done. + $write_done = FALSE; + + // Check if the aggregate is in the database. + $files_in_db = array(); + $query = db_select('advagg_aggregates', 'aa') + ->fields('aa', array('filename_hash')) + ->condition('aggregate_filenames_hash', $aggregate_filenames_hash) + ->orderBy('aa.porder', 'ASC') + ->execute(); + foreach ($query as $row) { + $files_in_db[$row->filename_hash] = (array) $row; + } + + $count = 0; + foreach ($files as $file_meta_data) { + ++$count; + + // Skip if the file already exists in the aggregate. + if (!empty($files_in_db[$file_meta_data['filename_hash']])) { + continue; + } + + // Store settings for this file that depend on how it was added. + $settings = array(); + if (isset($file_meta_data['media_query'])) { + $settings['media'] = $file_meta_data['media_query']; + } + + // Write record into the database. + $record = array( + 'aggregate_filenames_hash' => $aggregate_filenames_hash, + 'filename_hash' => $file_meta_data['filename_hash'], + 'porder' => $count, + 'settings' => serialize($settings), + ); + $return = db_merge('advagg_aggregates') + ->key(array( + 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], + 'filename_hash' => $record['filename_hash'], + )) + ->insertFields($record) + ->execute(); + + if ($return) { + $write_done = TRUE; + } + } + return $write_done; +} + +/** + * Insert/Update data in the advagg_files table. + * + * @param array $files + * List of files in the aggregate including files meta data. + * @param string $type + * String; css or js. + * + * @return bool + * Return TRUE if anything was written to the database. + */ +function advagg_insert_update_files(array $files, $type) { + // Record if a database write was done. + $write_done = FALSE; + + $filename_hashes = array(); + foreach ($files as $file_meta_data) { + $filename_hashes[] = $file_meta_data['filename_hash']; + } + + $files_in_db = array(); + if (!empty($filename_hashes)) { + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename_hash', $filename_hashes) + ->execute(); + foreach ($query as $row) { + $files_in_db[$row->filename] = (array) $row; + } + } + + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; + foreach ($files as $filename => $file_meta_data) { + // Create record. + $record = array( + 'filename' => $filename, + 'filename_hash' => $file_meta_data['filename_hash'], + 'content_hash' => $file_meta_data['content_hash'], + 'filetype' => $type, + 'filesize' => $file_meta_data['filesize'], + 'mtime' => $file_meta_data['mtime'], + 'linecount' => $file_meta_data['linecount'], + ); + try { + // Check the file in the database. + if (empty($files_in_db[$filename])) { + // Add in filesize_processed if the schema is 7210 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7210) { + $record['filesize_processed'] = (int) advagg_generate_filesize_processed($filename, $type); + } + // Add in use_strict if the schema is 7212 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7212) { + $record['use_strict'] = 0; + if ($type === 'js') { + $record['use_strict'] = (int) advagg_does_js_start_with_use_strict($filename); + } + } + + // Insert into database. + $record['changes'] = 1; + + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $record['filename_hash'], + )) + ->insertFields($record) + ->execute(); + if ($return) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Inserting into db
    @record
    .', array('@record' => print_r($record, TRUE)), WATCHDOG_DEBUG); + } + $write_done = TRUE; + } + } + else { + // Take changes counter out of the diff equation. + $changes = $files_in_db[$filename]['changes']; + unset($files_in_db[$filename]['changes']); + // If not in strict mode, only use mtime if newer than the existing one. + if (!variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK)) { + // Make sure mtime only moves forward. + if ($record['mtime'] <= $files_in_db[$filename]['mtime']) { + $record['mtime'] = $files_in_db[$filename]['mtime']; + } + } + + // If something is different, update. + $diff = array_diff_assoc($record, $files_in_db[$filename]); + if (!empty($diff)) { + $diff['changes'] = $changes + 1; + $diff['filename_hash'] = $record['filename_hash']; + + // Add in filesize_processed if the schema is 7210 or higher. + if (drupal_get_installed_schema_version('advagg') >= 7210) { + $diff['filesize_processed'] = (int) advagg_generate_filesize_processed($filename, $type); + } + if (drupal_get_installed_schema_version('advagg') >= 7212) { + $diff['use_strict'] = 0; + if ($type === 'js') { + $diff['use_strict'] = (int) advagg_does_js_start_with_use_strict($filename); + if (empty($diff['use_strict'])) { + $diff['use_strict'] = 0; + } + } + } + + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $diff['filename_hash'], + )) + ->fields($diff) + ->execute(); + if ($return) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Updating db
    @diff
    .', array('@diff' => print_r($diff, TRUE)), WATCHDOG_DEBUG); + } + $write_done = TRUE; + } + } + } + } + catch (PDOException $e) { + // If it fails we don't care, the file was added to the table by another + // process then. + // Still log it if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } + } + } + return $write_done; +} + +/** + * Given a filename calculate the processed filesize. + * + * @param string $filename + * String; filename containing path information as well. + * @param string $type + * String; css or js. + * + * @return int + * Processed filesize. + */ +function advagg_generate_filesize_processed($filename, $type) { + $files = &drupal_static(__FUNCTION__, array()); + if (!isset($files[$type][$filename])) { + // Make advagg_get_*_aggregate_contents() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $aggregate_settings = advagg_current_hooks_hash_array(); + + $file_aggregate = array($filename => array()); + if ($type === 'css') { + list($contents) = advagg_get_css_aggregate_contents($file_aggregate, $aggregate_settings); + } + elseif ($type === 'js') { + list($contents) = advagg_get_js_aggregate_contents($file_aggregate, $aggregate_settings); + } + if (!empty($contents)) { + $files[$type][$filename] = strlen(gzencode($contents, 9, FORCE_GZIP)); + } + else { + $files[$type][$filename] = 0; + } + } + return $files[$type][$filename]; +} + +/** + * Given a js string, see if "use strict"; is the first thing ran. + * + * @param string $filename + * String; filename containing path information as well. + * + * @return bool + * True if "use strict"; is the first thing ran. + */ +function advagg_does_js_start_with_use_strict($filename) { + $files = &drupal_static(__FUNCTION__, array()); + if (!isset($files[$filename])) { + // Make advagg_get_*_aggregate_contents() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + $aggregate_settings = advagg_current_hooks_hash_array(); + + $file_aggregate = array($filename => array()); + list($contents) = advagg_get_js_aggregate_contents($file_aggregate, $aggregate_settings); + + // See if the js file starts with "use strict";. + // Trim the JS down to 24kb. + $length = variable_get('advagg_js_header_length', ADVAGG_JS_HEADER_LENGTH); + $header = advagg_get_js_header($contents, $length); + + // Look for the string. + $use_strict = stripos($header, '"use strict";'); + $strict_js = FALSE; + if ($use_strict === FALSE) { + $use_strict = stripos($header, "'use strict';"); + } + if ($use_strict !== FALSE) { + if ($use_strict == 0) { + $strict_js = TRUE; + } + else { + // Get all text before "use strict";. + $substr = substr($header, 0, $use_strict); + // Check if there are any comments. + $single_line_comment = strpos($substr, '//'); + $multi_line_comment = strpos($substr, '/*'); + $in_function = strpos($substr, '{'); + if ($single_line_comment !== FALSE || $multi_line_comment !== FALSE) { + // Remove js comments and try again. + advagg_remove_js_comments($header); + + // Look for the string. + $use_strict = stripos($header, '"use strict";'); + if ($use_strict === FALSE) { + $use_strict = stripos($header, "'use strict';"); + } + // Get all text before "use strict"; with comments removed. + $substr = substr($header, 0, $use_strict); + // Check if there is a function before use strict. + $in_function = strpos($substr, '{'); + } + if ($in_function === FALSE) { + $strict_js = TRUE; + } + } + } + + $files[$filename] = $strict_js; + } + return $files[$filename]; +} + +/** + * Read only the first 8192 bytes to get the file header. + * + * @param string $content + * JS string to cut. + * @param int $length + * The number of bytes to grab. See advagg_js_header_length variable. + * + * @return string + * The shortened JS string. + */ +function advagg_get_js_header($content, $length) { + $content = trim($content); + // Only grab the first X bytes. + if (function_exists('mb_strcut')) { + $header = mb_strcut($content, 0, $length); + } + else { + $header = substr($content, 0, $length); + } + + return $header; +} + +/** + * Remove comments from JavaScript. + * + * @param string $content + * JS string to minify. + */ +function advagg_remove_js_comments(&$content) { + // Remove comments. + $content = preg_replace('/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?@finfo', array( + '@finfo' => var_export($info, TRUE), + )); + } + } + + // Get filesystem data. + $files_info = advagg_get_info_on_files($files_info_filenames); + + foreach ($files_with_meta_data as $info) { + // Skip if not a string or key doesn't exist. + if (!is_string($info['data']) || !array_key_exists($info['data'], $files_info)) { + continue; + } + + $filename = $info['data']; + $info += $files_info[$filename]; + // Skip if file doesn't exist. + if (empty($info['content_hash'])) { + continue; + } + + // Add info to arrays. + $filename_hashes[] = $info['filename_hash']; + $content_hashes[] = $info['content_hash']; + $filenames[$filename] = $info; + } + + // Generate filename. + $aggregate_filenames_hash = drupal_hash_base64(implode('', $filename_hashes)); + $aggregate_contents_hash = drupal_hash_base64(implode('', $content_hashes)); + $aggregate_filename = advagg_build_filename($type, $aggregate_filenames_hash, $aggregate_contents_hash); + return array( + $aggregate_filename, + $filenames, + $aggregate_filenames_hash, + $aggregate_contents_hash, + ); +} + +/** + * Load cache bin file info in static cache. + * + * @param array $files + * Array; array of filenames. + * + * @return array + * $cached_data. key is $cache_id; value is an array which contains + * + * @code + * 'filesize' => filesize($filename), + * 'mtime' => @filemtime($filename), + * 'filename_hash' => $filename_hash, + * 'content_hash' => drupal_hash_base64($file_contents), + * 'linecount' => $linecount, + * 'data' => $filename, + * 'fileext' => $ext, + * @endcode + */ +function &advagg_load_files_info_into_static_cache(array $files) { + // Get the static cache of this data. + $cached_data = &drupal_static('advagg_get_info_on_file'); + + // Get the statically cached data for all the given files. + $cache_ids = array(); + foreach ($files as $file) { + $cache_id = 'advagg:file:' . advagg_drupal_hash_base64($file); + if (!empty($cached_data) + && !empty($cached_data[$cache_id]) + ) { + // Make sure the cache_id is included. + $cached_data[$cache_id]['cache_id'] = $cache_id; + } + else { + $cache_ids[$file] = $cache_id; + } + } + // Get info from the cache back-end next. + if (!empty($cache_ids)) { + $values = array_values($cache_ids); + $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); + if (!empty($cache_hits)) { + foreach ($cache_hits as $hit) { + if (!empty($hit->data['data'])) { + // Make sure the cache_id is included. + $hit->data['cache_id'] = $hit->cid; + // Add to static cache. + $cached_data[$hit->cid] = $hit->data; + } + } + } + } + return $cached_data; +} + +/** + * Given a filename calculate the hash for it. Uses static cache. + * + * @param string $file + * Filename. + * + * @return string + * hash of filename. + */ +function advagg_drupal_hash_base64($file) { + // Get the static cache of this data. + $cached_data = &drupal_static('advagg_drupal_hash_base64', array()); + if (!array_key_exists($file, $cached_data)) { + $cached_data[$file] = drupal_hash_base64($file); + } + return $cached_data[$file]; +} + +/** + * Given a filename calculate various hashes and gather meta data. + * + * @param array $files + * Array; array of filenames containing path information as well. + * @param bool $bypass_cache + * Bool: TRUE to bypass the cache. + * + * @return array + * $return['filename'] which contains + * + * @code + * 'filesize' => filesize($filename), + * 'mtime' => @filemtime($filename), + * 'filename_hash' => $filename_hash, + * 'content_hash' => drupal_hash_base64($file_contents), + * 'linecount' => $linecount, + * 'data' => $filename, + * 'fileext' => $ext, + * @endcode + */ +function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alter = TRUE) { + // Get the cached data. + $cached_data = &advagg_load_files_info_into_static_cache($files); + + // Get basic info on the files. + $return = array(); + foreach ($files as $file) { + $filename_hash = advagg_drupal_hash_base64($file); + $cache_id = 'advagg:file:' . $filename_hash; + // If we are not bypassing the cache add cached data. + if ($bypass_cache == FALSE + && is_array($cached_data) + && array_key_exists($cache_id, $cached_data) + ) { + $return[$file] = $cached_data[$cache_id]; + continue; + } + + // Clear PHP's internal file status cache. + advagg_clearstatcache($file); + + // Remove file in the cache if it does not exist. + if (!file_exists($file) || is_dir($file)) { + if (isset($cached_data[$cache_id])) { + cache_clear_all($cache_id, 'cache_advagg_info', FALSE); + } + // Return filename_hash and data. Empty values for the other keys. + $return[$file] = array( + 'filesize' => 0, + 'mtime' => 0, + 'filename_hash' => $filename_hash, + 'content_hash' => '', + 'linecount' => 0, + 'data' => $file, + 'cache_id' => $cache_id, + '#no_cache' => TRUE, + ); + continue; + } + + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($file); + + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if ($ext !== 'css' && $ext !== 'js') { + // Get the $ext from the database. + $row = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $file) + ->execute()->fetchAssoc(); + if (!empty($row['filetype'])) { + $ext = $row['filetype']; + } + if ($ext === 'less') { + $ext = 'css'; + } + } + + if ($ext === 'css') { + // Get the number of selectors. + $linecount = advagg_count_css_selectors($file_contents); + } + else { + // Get the number of lines. + $linecount = substr_count($file_contents, "\n"); + } + + // Build meta data array and set cache. + $return[$file] = array( + 'filesize' => (int) @filesize($file), + 'mtime' => @filemtime($file), + 'filename_hash' => $filename_hash, + 'content_hash' => drupal_hash_base64($file_contents), + 'linecount' => $linecount, + 'data' => $file, + 'fileext' => $ext, + 'cache_id' => $cache_id, + ); + if (isset($cached_data[$cache_id])) { + $return[$file] += $cached_data[$cache_id]; + } + } + + if ($run_alter) { + // Run hook so other modules can modify the data on these files. + // Call hook_advagg_get_info_on_files_alter(). + drupal_alter('advagg_get_info_on_files', $return, $cached_data, $bypass_cache); + + // Set the cache and populate return array. + foreach ($return as $info) { + // If no cache is empty add/update the cached entry. + // Update the cache if it is new or something changed. + if (empty($info['#no_cache']) + && !empty($info['cache_id']) + && (empty($cached_data[$info['cache_id']]) || $info !== $cached_data[$info['cache_id']]) + ) { + // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. + // The random 0 to 45 day addition is to prevent a cache stampede. + cache_set($info['cache_id'], $info, 'cache_advagg_info', round(REQUEST_TIME + 1209600 + mt_rand(0, 3888000), -3)); + } + + // Update static cache. + $cached_data[$info['cache_id']] = $info; + } + } + + return $return; +} + +/** + * Given a filename calculate various hashes and gather meta data. + * + * @param string $filename + * String; filename containing path information. + * @param bool $bypass_cache + * (optional) Bool: TRUE to bypass the cache. + * @param bool $run_alter + * (optional) Bool: FALSE to not run drupal_alter. + * + * @return array + * Array containing key value pairs. + * + * @code + * 'filesize' => filesize($filename), + * 'mtime' => @filemtime($filename), + * 'filename_hash' => $filename_hash, + * 'content_hash' => drupal_hash_base64($file_contents), + * 'linecount' => $linecount, + * 'data' => $filename, + * 'fileext' => $ext, + * @endcode + */ +function advagg_get_info_on_file($filename, $bypass_cache = FALSE, $run_alter = TRUE) { + $files_info = advagg_get_info_on_files(array($filename), $bypass_cache, $run_alter); + return $files_info[$filename]; +} + +/** + * Build the filename. + * + * @param string $type + * String; css or js. + * @param string $aggregate_filenames_hash + * Hash of the groupings of files. + * @param string $aggregate_contents_hash + * Hash of the files contents. + * @param string $hooks_hash + * Hash value from advagg_get_current_hooks_hash(). + * + * @return string + * String: The filename. No path info. + */ +function advagg_build_filename($type, $aggregate_filenames_hash, $aggregate_contents_hash, $hooks_hash = '') { + if (empty($hooks_hash)) { + $hooks_hash = advagg_get_current_hooks_hash(); + } + return $type . ADVAGG_SPACE . + $aggregate_filenames_hash . ADVAGG_SPACE . + $aggregate_contents_hash . ADVAGG_SPACE . + $hooks_hash . '.' . $type; +} + +/** + * Wrapper around clearstatcache so it can use php 5.3's new features. + * + * @param string $filename + * String. + * + * @return null + * value from clearstatcache(). + */ +function advagg_clearstatcache($filename = NULL) { + static $php530; + if (!isset($php530)) { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + if ($php530) { + return clearstatcache(TRUE, $filename); + } + else { + return clearstatcache(); + } +} + +/** + * Group the CSS/JS into the biggest buckets possible. + * + * @param array $files_to_aggregate + * An array of CSS/JS groups. + * @param string $type + * String; css or js. + * + * @return array + * New version of groups. + */ +function advagg_generate_groups(array $files_to_aggregate, $type) { + $groups = array(); + $count = 0; + $location = 0; + + $media = ''; + $defer = ''; + $async = ''; + $cache = ''; + $scope = ''; + $use_strict = 0; + $browsers = array(); + $selector_count = 0; + // Get CSS limit value. + $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); + + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + || variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + $filenames = array(); + foreach ($files_to_aggregate as $data) { + foreach ($data as $values) { + foreach ($values['items'] as $file_info) { + if (!empty($file_info['data']) && is_string($file_info['data'])) { + $filenames[] = $file_info['data']; + } + else { + watchdog('advagg', 'Bad data key. File info: @finfo Group info: @ginfo', array( + '@finfo' => var_export($file_info, TRUE), + '@ginfo' => var_export($values, TRUE), + )); + } + } + } + } + + // Get filesystem data. + $files_info = advagg_get_info_on_files($filenames, TRUE); + } + + $strict_files = array(); + if ($type == 'js') { + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; + if (drupal_get_installed_schema_version('advagg') >= 7213) { + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $strict_files[$row->filename] = $row->use_strict; + } + } + } + + foreach ($files_to_aggregate as $data) { + foreach ($data as $values) { + + // Group into the biggest buckets possible. + $last_ext = ''; + foreach ($values['items'] as $file_info) { + $parts = array(); + // Check to see if media, browsers, defer, async, cache, or scope has + // changed from the previous run of this loop. + $changed = FALSE; + $ext = isset($file_info['fileext']) ? $file_info['fileext'] : pathinfo($file_info['data'], PATHINFO_EXTENSION); + $ext = strtolower($ext); + if ($ext !== 'css' && $ext !== 'js') { + if (empty($last_ext)) { + // Get the $ext from the database. + $row = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $file_info['data']) + ->execute()->fetchAssoc(); + $ext = $row['filetype']; + } + else { + $ext = $last_ext; + } + } + $last_ext = $ext; + if ($ext === 'css') { + if (isset($file_info['media'])) { + if (variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { + $file_info['media_query'] = $file_info['media']; + } + elseif ($media != $file_info['media']) { + // Media changed. + $changed = TRUE; + $media = $file_info['media']; + } + } + if (empty($file_info['media']) && !empty($media)) { + // Media changed to empty. + $changed = TRUE; + $media = ''; + } + } + if (isset($file_info['browsers'])) { + // Browsers changed. + $diff = array_merge(array_diff_assoc($file_info['browsers'], $browsers), array_diff_assoc($browsers, $file_info['browsers'])); + if (!empty($diff)) { + $changed = TRUE; + $browsers = $file_info['browsers']; + } + } + if (empty($file_info['browsers']) && !empty($browsers)) { + // Browsers changed to empty. + $changed = TRUE; + $browsers = array(); + } + + if (!empty($strict_files[$file_info['data']]) && $use_strict != $strict_files[$file_info['data']]) { + // use_strict value changed to 1. + $changed = TRUE; + $use_strict = 1; + } + if (!empty($use_strict) && empty($strict_files[$file_info['data']])) { + // use_strict value changed to 0. + $changed = TRUE; + $use_strict = 0; + } + if (isset($file_info['defer']) && $defer != $file_info['defer']) { + // Defer value changed. + $changed = TRUE; + $defer = $file_info['defer']; + } + if (!empty($defer) && empty($file_info['defer'])) { + // Defer value changed to empty. + $changed = TRUE; + $defer = ''; + } + if (isset($file_info['async']) && $async != $file_info['async']) { + // Async value changed. + $changed = TRUE; + $async = $file_info['async']; + } + if (!empty($async) && empty($file_info['async'])) { + // Async value changed to empty. + $changed = TRUE; + $async = ''; + } + if (isset($file_info['cache']) && $cache != $file_info['cache']) { + // Cache value changed. + $changed = TRUE; + $cache = $file_info['cache']; + } + if (!empty($cache) && empty($file_info['cache'])) { + // Cache value changed to empty. + $changed = TRUE; + $cache = ''; + } + if (isset($file_info['scope']) && $scope != $file_info['scope']) { + // Scope value changed. + $changed = TRUE; + $scope = $file_info['scope']; + } + if (!empty($scope) && empty($file_info['scope'])) { + // Scope value changed to empty. + $changed = TRUE; + $scope = ''; + } + + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + && array_key_exists('data', $file_info) + && is_string($file_info['data']) + && array_key_exists($file_info['data'], $files_info) + ) { + $file_info += $files_info[$file_info['data']]; + // Prevent CSS rules exceeding 4095 due to limits with IE9 and below. + if ($ext === 'css') { + $selector_count += $file_info['linecount']; + if ($selector_count > $limit_value) { + $changed = TRUE; + $selector_count = $file_info['linecount']; + + // Break large file into multiple smaller files. + if ($file_info['linecount'] > $limit_value) { + $parts = advagg_split_css_file($file_info); + } + } + } + } + + // Merge in dns_prefetch. + if ((variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) + && isset($files_info[$file_info['data']]['dns_prefetch']) + ) { + if (!isset($file_info['dns_prefetch'])) { + $file_info['dns_prefetch'] = array(); + } + if (!empty($file_info['dns_prefetch']) && is_string($file_info['dns_prefetch'])) { + $temp = $file_info['dns_prefetch']; + unset($file_info['dns_prefetch']); + $file_info['dns_prefetch'] = array($temp); + } + $file_info['dns_prefetch'] = array_filter(array_unique(array_merge($file_info['dns_prefetch'], $files_info[$file_info['data']]['dns_prefetch']))); + } + + // Merge in preload. + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + && isset($files_info[$file_info['data']]['preload']) + ) { + if (!isset($file_info['preload'])) { + $file_info['preload'] = array(); + } + if (!empty($file_info['preload']) && is_string($file_info['preload'])) { + $temp = $file_info['preload']; + unset($file_info['preload']); + $file_info['preload'] = array($temp); + } + $file_info['preload'] = array_filter(array_unique(array_merge($file_info['preload'], $files_info[$file_info['data']]['preload']))); + } + + // If one of the above options changed, it needs to be in a different + // aggregate. + if (!empty($parts)) { + foreach ($parts as $part) { + ++$count; + $groups[$location][$count][] = $part; + } + } + else { + if ($changed) { + ++$count; + } + $groups[$location][$count][] = $file_info; + } + } + } + // Grouping if inline is mixed between files. + ++$location; + } + + return $groups; +} + +/** + * Given a file info array it will split the file up. + * + * @param array $file_info + * File info array from advagg_get_info_on_file(). + * @param string $file_contents + * CSS file contents. + * + * @return array + * Array with advagg_get_info_on_file data and split data. + */ +function advagg_split_css_file(array $file_info, $file_contents = '') { + // Make advagg_parse_media_blocks() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Get the CSS file and break up by media queries. + if (empty($file_contents)) { + $file_contents = (string) @advagg_file_get_contents($file_info['data']); + } + $media_blocks = advagg_parse_media_blocks($file_contents); + + // Get the advagg_ie_css_selector_limiter_value. + $selector_limit = (int) max(variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), 100); + + // Group media queries together. + $part_selector_count = 0; + $counter = 0; + $values = array(); + foreach ($media_blocks as $media_block) { + // Get the number of selectors. + $selector_count = advagg_count_css_selectors($media_block); + + // This chunk is bigger than $selector_limit. It needs to be split. + if ($selector_count > $selector_limit) { + $inner_selector_count = 0; + // Split css string. + list($media_query, $split_css_strings) = advagg_split_css_string($media_block, $selector_limit); + foreach ($split_css_strings as $split_css_strings) { + $counter_changed = FALSE; + if (empty($split_css_strings)) { + continue; + } + + // Make sure selector count doesn't go over selector limit. + $inner_selector_count = advagg_count_css_selectors($split_css_strings); + $part_selector_count += $inner_selector_count; + if ($part_selector_count > $selector_limit) { + if (!empty($values[$counter])) { + ++$counter; + } + $counter_changed = TRUE; + $part_selector_count = $inner_selector_count; + } + + // Add to output array. + if (isset($values[$counter])) { + if (!empty($media_query)) { + $values[$counter] .= "\n$media_query { $split_css_strings } "; + } + else { + $values[$counter] .= "$split_css_strings"; + } + } + else { + if (!empty($media_query)) { + $values[$counter] = "$media_query { $split_css_strings } "; + } + else { + $values[$counter] = $split_css_strings; + } + } + } + // Add to current selector counter and go to the next value. + if (!$counter_changed) { + $part_selector_count += $inner_selector_count; + } + continue; + } + + $part_selector_count += $selector_count; + if ($part_selector_count > $selector_limit) { + if (!empty($values[$counter])) { + ++$counter; + } + $values[$counter] = $media_block; + $part_selector_count = $selector_count; + } + else { + if (isset($values[$counter])) { + $values[$counter] .= "\n$media_block"; + } + else { + $values[$counter] = $media_block; + } + } + } + + // Save data. + $parts = array(); + $overall_counter = 0; + foreach ($values as $key => $value) { + $last_chunk = FALSE; + $file_info['split_last_part'] = FALSE; + if (count($values) - 1 == $key) { + $last_chunk = TRUE; + } + if ($last_chunk) { + $file_info['split_last_part'] = TRUE; + } + + // Get the number of selectors. + $selector_count = advagg_count_css_selectors($value); + $overall_counter += $selector_count; + + // Save file. + $subfile = advagg_create_subfile($value, $overall_counter, $file_info); + if (empty($subfile)) { + // Something broke; do not create a subfile. + watchdog('advagg', 'Spliting up a CSS file failed. File info: @info', array('@info' => var_export($file_info, TRUE))); + return array(); + } + $parts[] = $subfile; + } + return $parts; +} + +/** + * Count the number of selectors inside of a CSS string. + * + * @param string $css_string + * CSS string. + * + * @return int + * The number of CSS selectors. + */ +function advagg_count_css_selectors($css_string) { + return substr_count($css_string, ',') + substr_count($css_string, '{') - substr_count($css_string, '@media'); +} + +/** + * Given a css string it will split it if it's over the selector limit. + * + * @param string $css_string + * CSS string. + * @param int $selector_limit + * How many selectors can be grouped together. + * + * @return array + * Array that contains the $media_query and the $css_array. + */ +function advagg_split_css_string($css_string, $selector_limit) { + // See if this css string is wrapped in a @media statement. + $media_query = ''; + $media_query_pos = strpos($css_string, '@media'); + if ($media_query_pos !== FALSE) { + // Get the opening bracket. + $open_bracket_pos = strpos($css_string, "{", $media_query_pos); + // Skip if there is a syntax error. + if ($open_bracket_pos === FALSE) { + return array(); + } + $media_query = substr($css_string, $media_query_pos, $open_bracket_pos - $media_query_pos); + $css_string_inside = substr($css_string, $open_bracket_pos + 1); + } + else { + $css_string_inside = $css_string; + } + + // Split CSS into selector chunks. + $split = preg_split('/(\{.+?\}|,)/si', $css_string_inside, -1, PREG_SPLIT_DELIM_CAPTURE); + + $new_css_chunk = array(0 => ''); + $selector_chunk_counter = 0; + $counter = 0; + // Have the key value be the running selector count and put split array semi + // back together. + foreach ($split as $value) { + $new_css_chunk[$counter] .= $value; + if (strpos($value, '}') === FALSE) { + ++$selector_chunk_counter; + } + else { + if ($counter + 1 < $selector_chunk_counter) { + $selector_chunk_counter += ($counter - $selector_chunk_counter + 1) / 2; + } + $counter = $selector_chunk_counter; + if (!isset($new_css_chunk[$counter])) { + $new_css_chunk[$counter] = ''; + } + } + } + + // Generate output array in this function. + $css_array = array(); + $keys = array_keys($new_css_chunk); + $counter = 0; + $chunk_counter = 0; + foreach (array_keys($keys) as $key) { + // Get out of loop if at the end of the array. + if (!isset($keys[$key + 1])) { + break; + } + + // Get values, keys and counts. + $this_value = $new_css_chunk[$keys[$key]]; + $this_key = $keys[$key]; + $next_key = $keys[$key + 1]; + $this_selector_count = $next_key - $this_key; + + // Single rule is bigger than the selector limit. + if ($this_selector_count > $selector_limit) { + // Get css rules for these selectors. + $open_bracket_pos = strpos($this_value, "{"); + $css_rule = ' ' . substr($this_value, $open_bracket_pos); + + // Split on selectors. + $split = preg_split('/(\,)/si', $this_value, NULL, PREG_SPLIT_OFFSET_CAPTURE); + $index = 0; + $counter = 0; + while (isset($split[$index][1])) { + // Get starting and ending positions of the selectors given the selector + // limit. + $next_index = $index + $selector_limit - 1; + $start = $split[$index][1]; + if (isset($split[$next_index][1])) { + $end = $split[$next_index][1]; + } + else { + // Last one. + $temp = end($split); + $split_key = key($split); + $counter = $split_key % $selector_limit; + $end_open_bracket_pos = (int) strpos($temp[0], "{"); + $end = $temp[1] + $end_open_bracket_pos; + } + + // Extract substr. + $sub_this_value = substr($this_value, $start, $end - $start - 1) . $css_rule; + + // Save substr. + ++$chunk_counter; + $key_output = $selector_limit; + if (!empty($counter)) { + $key_output = $selector_limit - $counter; + } + $css_array["$chunk_counter $key_output"] = ''; + + if (!isset($css_array[$chunk_counter])) { + $css_array[$chunk_counter] = $sub_this_value; + } + else { + $css_array[$chunk_counter] .= $sub_this_value; + } + + // Move counter. + $index = $next_index; + } + continue; + } + + $counter += $this_selector_count; + if ($counter > $selector_limit) { + $key_output = $counter - $this_selector_count; + $css_array["$chunk_counter $key_output"] = ''; + $counter = $next_key - $this_key; + ++$chunk_counter; + } + if (!isset($css_array[$chunk_counter])) { + $css_array[$chunk_counter] = $this_value; + } + else { + $css_array[$chunk_counter] .= $this_value; + } + } + + // Group into sets smaller than $selector_limit. + return array($media_query, $css_array); +} + +/** + * Write CSS parts to disk; used when CSS selectors in one file is > 4096. + * + * @param string $css + * CSS data to write to disk. + * @param int $overall_split + * Running count of what selector we are from the original file. + * @param array $file_info + * File info array from advagg_get_info_on_file(). + * + * @return array + * Array with advagg_get_info_on_file data and split data; FALSE on failure. + */ +function advagg_create_subfile($css, $overall_split, array $file_info) { + static $parts_uri; + static $parts_path; + if (!isset($parts_uri)) { + list($css_path) = advagg_get_root_files_dir(); + $parts_uri = $css_path[0] . '/parts'; + $parts_path = $css_path[1] . '/parts'; + + // Create the public://advagg_css/parts dir. + file_prepare_directory($parts_uri, FILE_CREATE_DIRECTORY); + + // Make advagg_save_data() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + } + + // Get the path from $file_info['data']. + $uri_path = advagg_get_relative_path($file_info['data']); + if (!file_exists($uri_path) || is_dir($uri_path)) { + return FALSE; + } + + // Write the current chunk of the CSS into a file. + $new_filename = str_ireplace('.css', '.' . $overall_split . '.css', $uri_path); + + // Fix for things that write dynamically to the public file system. + $scheme = file_uri_scheme($new_filename); + if ($scheme) { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { + // Use the wrappers directory path. + $new_filename = $wrapper->getDirectoryPath() . '/' . file_uri_target($new_filename); + } + else { + // If the scheme does not have a wrapper; prefix file with the scheme. + $new_filename = $scheme . '/' . file_uri_target($new_filename); + } + } + + $part_uri = $parts_uri . '/' . $new_filename; + $dirname = drupal_dirname($part_uri); + file_prepare_directory($dirname, FILE_CREATE_DIRECTORY); + $filename_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $parts_uri : $parts_path; + + // Get info on the file that was just created. + $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; + $part['split'] = TRUE; + $part['split_location'] = $overall_split; + $part['split_original'] = $file_info['data']; + + // Overwrite/create file if hash doesn't match. + $hash = drupal_hash_base64($css); + if ($part['content_hash'] !== $hash) { + advagg_save_data($part_uri, $css, TRUE); + $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; + $part['split'] = TRUE; + $part['split_location'] = $overall_split; + $part['split_original'] = $file_info['data']; + } + + return $part; +} + +/** + * Replacement for drupal_build_css_cache() and drupal_build_js_cache(). + * + * @param array $files_to_aggregate + * An array of CSS/JS groups. + * @param string $type + * String; css or js. + * + * @return array + * array of aggregate files. + */ +function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { + if ($type !== 'css' && $type !== 'js') { + return array(); + } + + // Place into biggest grouping possible. + $groups = advagg_generate_groups($files_to_aggregate, $type); + + // Get filenames. + $files = advagg_generate_filenames($groups, $type); + + // Insert/Update Database. + advagg_insert_update_db($files, $type, 1); + // Update atimes for root. + advagg_multi_update_atime($files); + + // Run hooks to modify the aggregate. + // Call hook_advagg_build_aggregate_plans_alter(). + $modified = FALSE; + drupal_alter('advagg_build_aggregate_plans', $files, $modified, $type); + + // If the hook above modified anything, re-insert into database. + if ($modified) { + // Insert/Update Database. + advagg_insert_update_db($files, $type, 0); + // Update atimes for non root. + advagg_multi_update_atime($files); + } + + // Get file paths. + list($css_path, $js_path) = advagg_get_root_files_dir(); + + // Build the plan. + $plans = array(); + + foreach ($files as $agg_filename => $values) { + if ($type === 'css') { + $mixed_media = FALSE; + $media = NULL; + foreach ($values['files'] as $value) { + if (!isset($value['media'])) { + continue; + } + if (is_null($media)) { + $media = $value['media']; + } + if ($media != $value['media']) { + $mixed_media = TRUE; + } + } + } + + $onload = array(); + $onerror = array(); + $attributes = array(); + $onloadcss = array(); + foreach ($values['files'] as &$items) { + // Get onload. + if (!empty($items['onload'])) { + $onload[] = $items['onload']; + } + // Get attributes onload. + if (!empty($items['attributes']['onload'])) { + $onload[] = $items['attributes']['onload']; + unset($items['attributes']['onload']); + } + // Get onerror. + if (!empty($items['onerror'])) { + $onload[] = $items['onerror']; + } + // Get attributes onerror. + if (!empty($items['attributes']['onerror'])) { + $onload[] = $items['attributes']['onerror']; + unset($items['attributes']['onerror']); + } + // Get attributes onloadCSS. + if (!empty($items['onloadCSS'])) { + $onloadcss[] = $items['onloadCSS']; + } + // Get attributes onloadCSS. + if (!empty($items['attributes']['onloadCSS'])) { + $onloadcss[] = $items['attributes']['onloadCSS']; + unset($items['attributes']['onloadCSS']); + } + // Get attributes. + if (!empty($items['attributes'])) { + $attributes += $items['attributes']; + } + } + $onload = implode(';', array_unique(array_filter($onload))); + $onerror = implode(';', array_unique(array_filter($onerror))); + $onloadcss = implode(';', array_unique(array_filter($onloadcss))); + + $first = reset($values['files']); + if (!empty($mixed_media)) { + $first['media'] = 'all'; + } + $url = ($type === 'css') ? $css_path[0] : $js_path[0]; + $path = ($type === 'css') ? $css_path[1] : $js_path[1]; + $plans[$agg_filename] = array( + 'data' => $url . '/' . $agg_filename, + 'media' => isset($first['media']) ? $first['media'] : '', + 'defer' => isset($first['defer']) ? $first['defer'] : '', + 'async' => isset($first['async']) ? $first['async'] : '', + 'onload' => $onload, + 'onerror' => $onerror, + 'browsers' => isset($first['browsers']) ? $first['browsers'] : array(), + 'cache' => isset($first['cache']) ? $first['cache'] : TRUE, + 'type' => $first['type'], + 'items' => $values, + 'filepath' => $path . '/' . $agg_filename, + 'filename' => $agg_filename, + 'attributes' => $attributes, + ); + if (!empty($onloadcss)) { + $plans[$agg_filename]['attributes']['onloadCSS'] = $onloadcss; + } + } + $plans = array_values($plans); + + // Create the aggregate files. + if (variable_get('advagg_pregenerate_aggregate_files', ADVAGG_PREGENERATE_AGGREGATE_FILES)) { + advagg_create_aggregate_files($plans, $type); + } + + // Run hooks to modify the plans. + // Call hook_advagg_build_aggregate_plans_post_alter(). + drupal_alter('advagg_build_aggregate_plans_post', $plans, $type); + + return $plans; +} + +/** + * Create the aggregate if it does not exist; using HTTPRL if possible. + * + * @param array $plans + * An array of aggregate file names. + * @param string $type + * String; css or js. + * + * @return array + * An array of what was done when generating the file. + */ +function advagg_create_aggregate_files(array $plans, $type) { + $filenames = array(); + $return = array(); + foreach ($plans as $plan) { + $filenames[] = $plan['filename']; + } + + // If the httprl module exists and we want to use it. + if (module_exists('httprl') + && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) + && (is_callable('httprl_is_background_callback_capable') + && httprl_is_background_callback_capable() + || !is_callable('httprl_is_background_callback_capable') + ) + ) { + if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + foreach ($filenames as $key => $filename) { + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + if (file_exists($uri)) { + unset($filenames[$key]); + } + } + } + if (!empty($filenames)) { + // Setup callback options array; call function in the background. + $callback_options = array( + array( + 'function' => 'advagg_build_aggregates', + ), + $filenames, $type, + ); + // Queue up the request. + httprl_queue_background_callback($callback_options); + // Execute request. + $return = httprl_send_request(); + } + } + else { + $return = advagg_build_aggregates($filenames, $type); + } + return $return; +} + +/** + * Loads the stylesheet and resolves all @import commands. + * + * Loads a stylesheet and replaces @import commands with the contents of the + * imported file. Use this instead of file_get_contents when processing + * stylesheets. + * + * The returned contents are compressed removing white space and comments only + * when CSS aggregation is enabled. This optimization will not apply for + * color.module enabled themes with CSS aggregation turned off. + * + * @param string $file + * Name of the stylesheet to be processed. + * @param bool $optimize + * Defines if CSS contents should be compressed or not. + * @param array $aggregate_settings + * Array of settings. + * + * @return string + * Contents of the stylesheet, including any resolved @import commands. + */ +function advagg_load_css_stylesheet($file, $optimize = TRUE, array $aggregate_settings = array(), $contents = '') { + $old_base_path = $GLOBALS['base_path']; + + // Change context to that of when this aggregate was created. + advagg_context_switch($aggregate_settings, 0); + + // Get the stylesheets contents. + $contents = advagg_load_stylesheet($file, $optimize, TRUE, $contents); + + // Resolve public:// if needed. + if (!advagg_is_external($file) && file_uri_scheme($file)) { + $file = advagg_get_relative_path($file); + } + + // Get the parent directory of this file, relative to the Drupal root. + $css_base_url = substr($file, 0, strrpos($file, '/')); + + // Handle split css files. + list($css_path) = advagg_get_root_files_dir(); + $parts_path = ((advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $css_path[1]) . '/parts/'; + $url_parts = strpos($css_base_url, $parts_path); + // If this CSS file is actually a part of a previously split larger CSS file, + // don't use it to construct relative paths within the CSS file for + // 'url(...)' bits. + if ($url_parts !== FALSE) { + $css_base_url = substr($css_base_url, $url_parts + strlen($parts_path)); + } + + // Replace the old base path with the one that was passed in. + if (advagg_is_external($css_base_url) || variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $pos = strpos($css_base_url, $old_base_path); + if ($pos !== FALSE) { + $parsed_url = parse_url($css_base_url); + if (!empty($parsed_url['path'])) { + // Remove any double slash in path. + $parsed_url['path'] = str_replace('//', '/', $parsed_url['path']); + + // Get newly recalculated position. + $pos = strpos($parsed_url['path'], $old_base_path); + + // Replace. + if (strpos($parsed_url['path'], '/') !== 0 && $old_base_path === '/') { + // Special case if going to a subdir. + $parsed_url['path'] = $GLOBALS['base_path'] . $parsed_url['path']; + } + else { + $parsed_url['path'] = substr_replace($parsed_url['path'], $GLOBALS['base_path'], $pos, strlen($old_base_path)); + } + + $css_base_url = advagg_glue_url($parsed_url); + } + } + } + + _advagg_build_css_path(array(), $css_base_url . '/', $aggregate_settings); + // Anchor all paths in the CSS with its base URL, ignoring external, + // absolute paths, and urls that start with # or %23 (SVG). + $contents = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"\)]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $contents); + + // Change context back. + advagg_context_switch($aggregate_settings, 1); + + // Return the stylesheets contents. + return $contents; +} + +/** + * Changes context when generating CSS or JS files. + * + * @param array $aggregate_settings + * Array of settings. + * @param int $mode + * Use 0 to change context to what is inside of $aggregate_settings. + * Use 1 to change context back. + */ +function advagg_context_switch(array $aggregate_settings, $mode) { + $original = &drupal_static(__FUNCTION__); + + // Use current $aggregate_settings if none was passed in. + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + + // Call hook_advagg_context_alter(). + drupal_alter('advagg_context', $original, $aggregate_settings, $mode); +} + +/** + * Prefixes all paths within a CSS file for drupal_build_css_cache(). + * + * @param array $matches + * Array of matched items from preg_replace_callback(). + * @param string $base + * Base path. + * @param array $aggregate_settings + * Array of settings. + * + * @return string + * New version of the url() string from the css. + * + * @see _drupal_build_css_path() + * @see https://drupal.org/node/1961340#comment-7735815 + * @see https://drupal.org/node/1514182#comment-7875489 + */ +function _advagg_build_css_path(array $matches, $base = '', array $aggregate_settings = array()) { + $_base = &drupal_static(__FUNCTION__, ''); + $_aggregate_settings = &drupal_static(__FUNCTION__ . '_aggregate_settings', array()); + // Store base path for preg_replace_callback. + if (!empty($base)) { + $_base = $base; + } + if (!empty($aggregate_settings)) { + $_aggregate_settings = $aggregate_settings; + } + // Short circuit if no matches were passed in. + if (empty($matches)) { + return ''; + } + + // Prefix with base. + $url = $_base . $matches[1]; + + // If advagg_file_create_url() is not being used and the $url is local, redo + // the $url taking the base_path into account. + if (!advagg_is_external($url) && variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $new_base_path = $GLOBALS['base_path']; + if (isset($_aggregate_settings['variables']['base_path'])) { + $new_base_path = $_aggregate_settings['variables']['base_path']; + } + // Remove first /. + $new_base_path = ltrim($new_base_path, '/'); + $pos = FALSE; + // See if base_path is in the passed in $_base. + if (!empty($new_base_path)) { + $pos = strpos($_base, $new_base_path); + } + if ($pos !== FALSE) { + $url = substr($_base, $pos) . $matches[1]; + } + else { + $url = $new_base_path . $_base . $matches[1]; + } + } + // Remove '../' segments where possible. + $last = ''; + while ($url != $last) { + $last = $url; + $url = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $url); + } + + // Parse and build back the url without the query and fragment parts. + $parsed_url = parse_url($url); + $base_url = advagg_glue_url($parsed_url, TRUE); + + $query = isset($parsed_url['query']) ? $parsed_url['query'] : ''; + // In the case of certain URLs, we may have simply a '?' character without + // further parameters. parse_url() misses this and leaves 'query' blank, so + // need to this back in. + // See http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax + // for more information. + if ($query != '' || strpos($url, $base_url . '?') === 0) { + $query = '?' . $query; + } + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + $run_file_create_url = FALSE; + if (!variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $run_file_create_url = TRUE; + } + if (empty($parsed_url['host'])) { + $base_url = ltrim($base_url, '/'); + } + $base_url = advagg_file_create_url($base_url, $_aggregate_settings, $run_file_create_url, 'css'); + + return 'url(' . $base_url . $query . $fragment . ')'; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.info b/frontend/drupal/sites/all/modules/advagg/advagg.info new file mode 100644 index 000000000..f2ece0743 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.info @@ -0,0 +1,13 @@ +name = Advanced CSS/JS Aggregation (AdvAgg) +description = Aggregates multiple CSS/JS files in a way that prevents 404 from happening when accessing a CSS or JS file. +package = Advanced CSS/JS Aggregation +core = 7.x +files[] = tests/advagg.test + +configure = admin/config/development/performance/advagg + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.install b/frontend/drupal/sites/all/modules/advagg/advagg.install new file mode 100644 index 000000000..cad60a2e8 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.install @@ -0,0 +1,2868 @@ + array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } + // New install gets a locked admin section. + variable_set('advagg_admin_mode', 0); +} + +/** + * Implements hook_enable(). + */ +function advagg_enable() { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + + // Make sure permissions for dirs are correct. Needed if installed via drush. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $stat_public = stat('public://'); + $stat_css = stat($css_path[0]); + $stat_js = stat($js_path[0]); + if (isset($stat_public['uid'])) { + if (isset($stat_css['uid']) && $stat_public['uid'] != $stat_css['uid']) { + @chown($css_path[0], $stat_public['uid']); + } + if (isset($stat_js['uid']) && $stat_public['uid'] != $stat_js['uid']) { + @chown($stat_js[0], $stat_public['uid']); + } + } + if (isset($stat_public['gid'])) { + if (isset($stat_css['gid']) && $stat_public['gid'] != $stat_css['gid']) { + @chgrp($css_path[0], $stat_public['gid']); + } + if (isset($stat_js['uid']) && $stat_public['gid'] != $stat_js['gid']) { + @chgrp($stat_js[0], $stat_public['gid']); + } + } + if (drupal_is_cli()) { + // Remove advagg and public dirs if empty and running from command line. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files = file_scan_directory($css_path[1], '/.*/'); + if (empty($files)) { + rmdir($css_path[1]); + } + $files = file_scan_directory($js_path[1], '/.*/'); + if (empty($files)) { + rmdir($js_path[1]); + } + $files = file_scan_directory('public://', '/.*/'); + if (empty($files)) { + rmdir('public://'); + } + } + + // Make sure the advagg_flush_all_cache_bins() function is available. + module_load_include('inc', 'advagg', 'advagg'); + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Flush caches. + advagg_flush_all_cache_bins(); + + // Flush menu cache on shutdown. + register_shutdown_function('menu_rebuild'); + + // Set the advagg_needs_update variable if this is a major version update. + if (!db_table_exists('advagg_aggregates_versions')) { + variable_set('advagg_needs_update', TRUE); + } + else { + variable_del('advagg_needs_update'); + } +} + +/** + * Implements hook_disable(). + */ +function advagg_disable() { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + + // Make sure the advagg_flush_all_cache_bins() function is available. + module_load_include('inc', 'advagg', 'advagg'); + module_load_include('inc', 'advagg', 'advagg.cache'); + + // Flush caches. + advagg_flush_all_cache_bins(); + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); + cache_clear_all('*', 'cache_page', TRUE); + + // Make sure the theme_registry: cid is cleared. + register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); +} + +/** + * Implements hook_uninstall(). + */ +function advagg_uninstall() { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + list($css_path, $js_path) = advagg_get_root_files_dir(); + + // Make sure the advagg_flush_all_cache_bins() function is available. + module_load_include('inc', 'advagg', 'advagg.cache'); + // Flush caches. + advagg_flush_all_cache_bins(); + + // Remove variables. + db_delete('variable') + ->condition('name', 'advagg%', 'LIKE') + ->execute(); + + // Remove all files and directories. + file_unmanaged_delete_recursive($css_path[0]); + file_unmanaged_delete_recursive($js_path[0]); + + // Make sure the theme_registry: cid is cleared. + register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); +} + +/** + * Implements hook_schema(). + */ +function advagg_schema() { + // Create cache tables. + $schema['cache_advagg_aggregates'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_aggregates']['fields']['cid']['binary'] = TRUE; + $schema['cache_advagg_aggregates']['description'] = 'Cache table for Advanced CSS/JS Aggregation. Used to keep a cache of the CSS and JS HTML tags.'; + + $schema['cache_advagg_info'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_info']['fields']['cid']['binary'] = TRUE; + $schema['cache_advagg_info']['description'] = 'Cache table for Advanced CSS/JS Aggregation. Used to keep a cache of the db and file info.'; + + // Create database tables. + $schema['advagg_files'] = array( + 'description' => 'Files used in CSS/JS aggregation.', + 'fields' => array( + 'filename' => array( + 'description' => 'Path and filename of the file relative to Drupal webroot.', + 'type' => 'text', + 'size' => 'normal', + 'not null' => TRUE, + ), + 'filename_hash' => array( + 'description' => 'Hash of path and filename. Used to join tables.', + 'type' => 'char', + 'length' => 43, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'content_hash' => array( + 'description' => 'Hash of the file content. Used to see if the file has changed.', + 'type' => 'char', + 'length' => 43, + 'not null' => FALSE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'filetype' => array( + 'description' => 'Filetype.', + 'type' => 'varchar', + 'length' => 8, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + ), + 'filesize' => array( + 'description' => 'The file size in bytes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'linecount' => array( + 'description' => 'The number of lines in the file.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'mtime' => array( + 'description' => 'The time the file was last modified.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'changes' => array( + 'description' => 'This is incremented every time a file changes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'filesize_processed' => array( + 'description' => 'The file size in bytes after minification and compression.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'use_strict' => array( + 'description' => 'If 1 then the js file starts with "use strict";. If 0 then it does not.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'content_hash' => array('content_hash'), + 'filetype' => array('filetype'), + 'filesize' => array('filesize'), + 'use_strict' => array('use_strict'), + ), + 'primary key' => array('filename_hash'), + ); + + $schema['advagg_aggregates'] = array( + 'description' => 'What files are used in what aggregates.', + 'fields' => array( + 'aggregate_filenames_hash' => array( + 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', + 'type' => 'char', + 'length' => 43, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'filename_hash' => array( + 'description' => 'Hash of path and filename.', + 'type' => 'char', + 'length' => 43, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'porder' => array( + 'description' => 'Processing order.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'settings' => array( + 'description' => 'Extra data about this file and how it is used in this aggregate.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'translatable' => TRUE, + 'serialize' => TRUE, + ), + ), + 'indexes' => array( + 'porder' => array('porder'), + 'filename_hash' => array('filename_hash'), + 'aggregate_filenames_hash_porder' => array('aggregate_filenames_hash', 'porder'), + ), + 'primary key' => array('aggregate_filenames_hash', 'filename_hash'), + ); + + $schema['advagg_aggregates_versions'] = array( + 'description' => 'What files are used in what aggregates.', + 'fields' => array( + 'aggregate_filenames_hash' => array( + 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', + 'type' => 'char', + 'length' => 43, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'aggregate_contents_hash' => array( + 'description' => 'Hash of all content_hashes in this aggregate. Simple Version control of the aggregate.', + 'type' => 'char', + 'length' => 43, + 'not null' => TRUE, + 'default' => '', + 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', + ), + 'atime' => array( + 'description' => 'Last access time for this version of the aggregate. Updated every 12 hours.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'root' => array( + 'description' => 'If 1 then it is a root aggregate. 0 means not root aggregate.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'root' => array('root'), + 'atime' => array('atime'), + 'root_atime' => array( + 'root', + 'atime', + ), + ), + 'primary key' => array('aggregate_filenames_hash', 'aggregate_contents_hash'), + ); + + // Copy the variable table and change a couple of things. + $schema['advagg_aggregates_hashes'] = drupal_get_schema_unprocessed('system', 'variable'); + $schema['advagg_aggregates_hashes']['fields']['hash'] = $schema['advagg_aggregates_hashes']['fields']['name']; + $schema['advagg_aggregates_hashes']['fields']['hash']['length'] = 255; + $schema['advagg_aggregates_hashes']['fields']['hash']['description'] = 'The name of the hash.'; + $schema['advagg_aggregates_hashes']['fields']['hash']['binary'] = TRUE; + $schema['advagg_aggregates_hashes']['fields']['settings']['description'] = 'The settings associated with this hash.'; + $schema['advagg_aggregates_hashes']['fields']['settings'] = $schema['advagg_aggregates_hashes']['fields']['value']; + $schema['advagg_aggregates_hashes']['description'] = 'Key value pairs created by AdvAgg. Stores settings used at the time that the aggregate was created.'; + $schema['advagg_aggregates_hashes']['primary key'][0] = 'hash'; + unset($schema['advagg_aggregates_hashes']['fields']['name'], $schema['advagg_aggregates_hashes']['fields']['value']); + + return $schema; +} + +/** + * Upgrade AdvAgg previous versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. + */ +function advagg_update_7200(&$sandbox) { + // Check and see if new tables exist. + $table_names = array_keys(advagg_schema()); + $all_tables_exist = TRUE; + foreach ($table_names as $table_name) { + if (!db_table_exists($table_name)) { + $all_tables_exist = FALSE; + } + } + // Bail if needed DB Tables exist. + if ($all_tables_exist) { + return t('Nothing needed to happen in Advanced CSS/JS Aggregation.'); + } + + // Remove all old advagg variables. + db_delete('variable') + ->condition('name', 'advagg%', 'LIKE') + ->execute(); + + // Remove old schema. + $tables_to_remove = array( + 'cache_advagg', + 'cache_advagg_files_data', + 'cache_advagg_bundle_reuse', + 'advagg_files', + 'advagg_bundles', + ); + foreach ($tables_to_remove as $table_to_remove) { + if (db_table_exists($table_to_remove)) { + db_drop_table($table_to_remove); + } + } + + // Install new schema. + drupal_install_schema('advagg'); + + return t('Upgraded Advanced CSS/JS Aggregation to 7.x-2.x.'); +} + +/** + * Remove Last-Modified Header from .htaccess to fix far future 304's. + */ +function advagg_update_7201(&$sandbox) { + return advagg_install_update_htaccess('Header set Last-Modified'); +} + +/** + * Remove the 480 week Far-Future code from .htaccess (violates RFC 2616 14.21). + */ +function advagg_update_7202(&$sandbox) { + return advagg_install_update_htaccess('290304000'); +} + +/** + * Add forcing of .js files to be application/javascript to follow RFC 4329 7.1. + */ +function advagg_update_7203(&$sandbox) { + return advagg_install_update_htaccess('', 'ForceType'); +} + +/** + * Remove empty temporary files left behind by AdvAgg. + */ +function advagg_update_7204(&$sandbox) { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + + // Get the advagg paths. + $advagg_path = advagg_get_root_files_dir(); + + // Get the top level path. + $top_level = substr($advagg_path[0][0], 0, strpos($advagg_path[0][0], 'advagg_css')); + + // Start timer. + timer_start(__FUNCTION__); + + // Remove empty temp files from public://. + $files = file_scan_directory($top_level, '/file.*/', array( + 'recurse' => FALSE, + 'callback' => 'advagg_install_delete_empty_file_if_stale', + )); + + // Stop timer. + $time = timer_stop(__FUNCTION__); + $time = round($time['time'] / 1000, 4); + + // Output info. + if (count($files) > 0) { + return t('%count temporary files where removed in %time seconds', array( + '%count' => count($files), + '%time' => $time, + )); + } + else { + return t('Nothing needed to be done.'); + } +} + +/** + * Fix incorrect usage of ForceType in .htaccess from update 7203. + */ +function advagg_update_7205(&$sandbox) { + return t('First pattern results: !first Second pattern results: !second', array( + '!first' => advagg_install_update_htaccess('ForceType text/css .js'), + '!second' => advagg_install_update_htaccess('ForceType application/javascript .js'), + )); +} + +/** + * Update the schema making the varchar columns utf8_bin in MySQL. + */ +function advagg_update_7206(&$sandbox) { + $db_type = Database::getConnection()->databaseType(); + $tables_altered = array(); + if ($db_type === 'mysql') { + module_load_include('install', 'advagg', 'advagg'); + $schema = advagg_schema(); + $schema = array_keys($schema); + foreach ($schema as $table_name) { + $table_name = Database::getConnection()->prefixTables('{' . db_escape_table($table_name) . '}'); + $results = db_query("SHOW FULL FIELDS FROM $table_name")->fetchAllAssoc('Field'); + foreach ($results as $row) { + if (stripos($row->Type, 'varchar') !== FALSE && $row->Collation !== 'utf8_bin') { + db_query("ALTER TABLE $table_name MODIFY {$row->Field} {$row->Type} CHARACTER SET utf8 COLLATE utf8_bin"); + $tables_altered[$table_name][] = $row->Field; + } + } + } + } + + if (empty($tables_altered)) { + return t('Nothing needed to happen in AdvAgg.'); + } + + return t('The following columns inside of these database tables where converted to utf8_bin:
    @data', array('@data' => print_r($tables_altered, TRUE))); +} + +/** + * Update schema making the varchar columns char. Change utf8_bin to ascii_bin. + */ +function advagg_update_7207(&$sandbox) { + $tables = array( + 'advagg_aggregates' => array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + // Change varchar to char. + db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); + } + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } + return t('AdvAgg Tables converted from varchar to char and utf8_bin to ascii_bin.'); +} + +/** + * Add an index to the filename_hash column in the advagg_aggregates table. + */ +function advagg_update_7208(&$sandbox) { + if (!db_index_exists('advagg_aggregates', 'filename_hash')) { + db_add_index('advagg_aggregates', 'filename_hash', array('filename_hash')); + return t('Database index added to the filename_hash column of the advagg_aggregates table.'); + } + return t('Nothing needed to be done.'); +} + +/** + * Update schema making it match the definition. + */ +function advagg_update_7209(&$sandbox) { + $tables = array( + 'advagg_aggregates' => array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + // Change varchar to char. + db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); + } + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } + return t('Database schema was adjusted to match what is listed in advagg_schema.'); +} + +/** + * Add filesize_processed field to advagg_files table. + */ +function advagg_update_7210() { + if (!db_field_exists('advagg_files', 'filesize_processed')) { + $spec = array( + 'description' => 'The file size in bytes after minification and compression.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('advagg_files', 'filesize_processed', $spec); + } + return t('The filesize_processed field has been added to the advagg_files table.'); +} + +/** + * Populate the filesize_processed field in the advagg_files table. + */ +function advagg_update_7211(&$sandbox) { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + $types = array('css', 'js'); + + // If first run of this update function then set progress variables. + if (!isset($sandbox['progress'])) { + $count = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filesize_processed', 0) + ->countQuery() + ->execute() + ->fetchField(); + $sandbox['progress'] = 0; + $sandbox['max'] = $count; + } + + // How many items should be processed per pass. + $limit = 20; + + foreach ($types as $type) { + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filesize_processed', 0) + ->condition('filetype', $type) + ->range($sandbox['progress'], $limit) + ->execute(); + foreach ($query as $row) { + $row->filesize_processed = (int) advagg_generate_filesize_processed($row->filename, $type); + if (!empty($row->filesize_processed)) { + $write = (array) $row; + db_merge('advagg_files') + ->key(array( + 'filename_hash' => $write['filename_hash'], + )) + ->fields($write) + ->execute(); + } + } + } + + // Update our progress information. + $sandbox['progress'] += $limit; + // Set the value for finished. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + return t('The filesize_processed field has been populated inside the advagg_files table.'); + } +} + +/** + * Add use_strict field to advagg_files table. + */ +function advagg_update_7212() { + if (!db_field_exists('advagg_files', 'use_strict')) { + $spec = array( + 'description' => 'If 1 then the js file starts with "use strict";. If 0 then it does not.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('advagg_files', 'use_strict', $spec); + } + if (!db_index_exists('advagg_files', 'use_strict')) { + db_add_index('advagg_files', 'use_strict', array('use_strict')); + } + return t('The use_strict field has been added to the advagg_files table.'); +} + +/** + * Populate the use_strict field in the advagg_files table. + */ +function advagg_update_7213(&$sandbox) { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + + // If first run of this update function then set progress variables. + if (!isset($sandbox['progress'])) { + $count = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->countQuery() + ->execute() + ->fetchField(); + $sandbox['progress'] = 0; + $sandbox['max'] = $count; + } + + // How many items should be processed per pass. + $limit = 10; + + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->range($sandbox['progress'], $limit) + ->execute(); + foreach ($query as $row) { + $row->use_strict = (int) advagg_does_js_start_with_use_strict($row->filename); + if (!empty($row->use_strict)) { + $write = (array) $row; + db_merge('advagg_files') + ->key(array( + 'filename_hash' => $write['filename_hash'], + )) + ->fields($write) + ->execute(); + } + } + + // Update our progress information. + $sandbox['progress'] += $limit; + // Set the value for finished. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + return t('The use_strict field has been populated inside the advagg_files table.'); + } +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7214(&$sandbox) { + return advagg_install_update_htaccess('', 'brotli'); +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7215(&$sandbox) { + return advagg_install_update_htaccess('', '%{HTTP:Accept-encoding} gzip'); +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7216(&$sandbox) { + if ($GLOBALS['base_path'] !== '/') { + return advagg_install_update_htaccess('', 'ErrorDocument 404'); + } +} + +/** + * Update migrate the advagg_browser_dns_prefetch variable. + */ +function advagg_update_7217(&$sandbox) { + $advagg_browser_dns_prefetch = variable_get('advagg_browser_dns_prefetch', NULL); + $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', NULL); + $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', NULL); + variable_del('advagg_browser_dns_prefetch'); + if (empty($advagg_browser_dns_prefetch) + || !is_null($advagg_resource_hints_dns_prefetch) + || !is_null($advagg_resource_hints_location) + ) { + return t('Nothing needed to be done.'); + } + drupal_load('module', 'advagg'); + $config_path = advagg_admin_config_root_path(); + + if ($advagg_browser_dns_prefetch == 1) { + variable_set('advagg_resource_hints_location', 1); + variable_set('advagg_resource_hints_dns_prefetch', TRUE); + } + elseif ($advagg_browser_dns_prefetch == 2) { + variable_set('advagg_resource_hints_location', 3); + variable_set('advagg_resource_hints_dns_prefetch', TRUE); + } + else { + return t('Nothing happened.'); + } + return t('Old DNS Prefetch variable transferred to the new variable. Other options are under Resource Hints on the configuration page', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))); +} + +/** + * Update the advagg .htaccess file fixing edge cases with the new rules. + */ +function advagg_update_7218(&$sandbox) { + return advagg_install_update_htaccess('', 'Options +FollowSymLinks'); +} + +/** + * Update the .htaccess file in the advagg directories adding immutable header. + */ +function advagg_update_7219(&$sandbox) { + return advagg_install_update_htaccess('', 'immutable'); +} + +/** + * Update the advagg_files table; use_strict column might have been incorrect. + */ +function advagg_update_7220() { + // Get all files that have use_strict marked. + $filenames = array(); + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $filenames[] = $row->filename; + } + if (empty($filenames)) { + return t('Nothing needed to happen. Good to go!'); + } + + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + + // Force change. + $info = advagg_get_info_on_files($filenames); + foreach ($info as &$value) { + $value['mtime']++; + } + advagg_insert_update_files($info, 'js'); + + // Fix changed record. + advagg_get_info_on_files($filenames); + advagg_insert_update_files($info, 'js'); + + // Detect changes. + $filenames_new = array(); + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $filenames_new[] = $row->filename; + } + + // Output results. + if (count($filenames_new) == count($filenames_new)) { + return t('Nothing needed to happen. Good to go!'); + } + else { + return t('The advagg_files table has been updated; use_strict column has been updated.'); + } +} + +/** + * Add index to aggregate_filenames_hash and porder in advagg_aggregates table. + */ +function advagg_update_7221(&$sandbox) { + if (!db_index_exists('advagg_aggregates', 'aggregate_filenames_hash_porder')) { + db_add_index('advagg_aggregates', 'aggregate_filenames_hash_porder', array('aggregate_filenames_hash', 'porder')); + return t('Database index added to the aggregate_filenames_hash and porder column of the advagg_aggregates table.'); + } + return t('Nothing needed to be done.'); +} + +/** + * Run various checks that are fast. + * + * @param string $phase + * Can be install, update, or runtime. + * + * @return array + * An associative array. + */ +function advagg_install_fast_checks($phase = 'runtime') { + $requirements = array(); + // Ensure translations don't break at install time. + $t = get_t(); + + // Always check these, independent of the current phase. + $function_list = array( + 'rename', + ); + // Check each function to make sure it exists. + foreach ($function_list as $function_name) { + if (!function_exists($function_name)) { + $requirements['advagg_function_' . $function_name] = array( + 'title' => $t('Adv CSS/JS Agg - Function Disabled'), + 'value' => $phase === 'install' ? FALSE : $function_name, + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('%name() is disabled on this server. Please contact your hosting provider or server administrator and see if they can re-enable this function for you.', array( + '!url' => 'http://php.net/' . str_replace('_', '-', $function_name), + '%name' => $function_name, + )), + ); + } + } + // Check to see if any incompatible modules are installed. + if (module_exists('agrcache')) { + $requirements['advagg_module_agrcache'] = array( + 'title' => $t('Adv CSS/JS Agg - Aggregate cache module'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $phase === 'install' ? FALSE : $t('The Aggregate cache module is incompatible with AdvAgg.'), + 'description' => $t('You need to uninstall the agrcache module or uninstall AdvAgg.'), + ); + } + if (module_exists('bundle_aggregation')) { + $requirements['advagg_module_bundle_aggregation'] = array( + 'title' => $t('Adv CSS/JS Agg - Bundle aggregation module'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $phase === 'install' ? FALSE : $t('The Bundle aggregation module is incompatible with AdvAgg.'), + 'description' => $t('You need to uninstall the bundle_aggregation module or uninstall AdvAgg.'), + ); + } + if (module_exists('core_library')) { + $requirements['advagg_module_core_library'] = array( + 'title' => $t('Adv CSS/JS Agg - Core Library module'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $phase === 'install' ? FALSE : $t('The Core Library module is incompatible with AdvAgg.'), + 'description' => $t('You need to uninstall the core_library module or uninstall AdvAgg.'), + ); + } + + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + // Make sure the advagg default values for variable_get are available. + drupal_load('module', 'advagg'); + + // Do the following checks only at runtime. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $config_path = advagg_admin_config_root_path(); + + // Make sure directories are writable. + if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { + $requirements['advagg_css_path_0_prepare_dir'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not created or writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), + ); + } + if (!is_writable($css_path[0])) { + $requirements['advagg_css_path_0_write'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), + ); + } + if (!is_readable($css_path[0])) { + $requirements['advagg_css_path_0_read'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), + ); + } + $css_wrapper = file_stream_wrapper_get_instance_by_uri($css_path[0]); + if ($css_wrapper instanceof DrupalLocalStreamWrapper) { + if (!is_writable($css_path[1])) { + $requirements['advagg_css_path_1_write'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[1])), + ); + } + if (!is_readable($css_path[1])) { + $requirements['advagg_css_path_1_read'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[1])), + ); + } + } + if (!file_prepare_directory($js_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { + $requirements['advagg_js_path_0_prepare_dir'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not created or writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + ); + } + if (!is_writable($js_path[0])) { + $requirements['advagg_js_path_0_write'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + ); + } + if (!is_readable($js_path[0])) { + $requirements['advagg_js_path_0_read'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + ); + } + $js_wrapper = file_stream_wrapper_get_instance_by_uri($js_path[0]); + if ($js_wrapper instanceof DrupalLocalStreamWrapper) { + if (!is_writable($js_path[1])) { + $requirements['advagg_js_path_1_write'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not writable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[1])), + ); + } + if (!is_readable($js_path[1])) { + $requirements['advagg_js_path_1_read'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[1])), + ); + } + } + + if (!variable_get('advagg_skip_enabled_preprocess_check', ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK)) { + // Make sure variables are set correctly. + if (!variable_get('advagg_enabled', ADVAGG_ENABLED)) { + $requirements['advagg_not_on'] = array( + 'title' => $t('Adv CSS/JS Agg - Enabled'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Advanced CSS/JS aggregation is disabled.'), + 'description' => $t('Go to the Advanced CSS/JS aggregation settings page and enable it.', array('@settings' => url($config_path . '/advagg'))), + ); + } + if (!variable_get('preprocess_css', FALSE) || !variable_get('preprocess_js', FALSE)) { + $requirements['advagg_core_off'] = array( + 'title' => $t('Adv CSS/JS Agg - Core Variables'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Core CSS and/or JS aggregation is disabled.'), + 'description' => $t('"Optimize CSS files" and "Optimize JavaScript files" on the performance page should be enabled.', array('@performance' => url('admin/config/development/performance', array('fragment' => 'edit-bandwidth-optimization')))), + ); + } + } + + // Check that the menu router handler is working. + // Paths will vary based on s3fs no_rewrite_cssjs setting. + if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { + // If using s3fs and no_rewrite_cssjs is not set, external paths are needed. + // Use $css_path[0] and $js_path[0] since they contain the scheme. + $menu_path_key = 0; + $menu_css_path = trim(parse_url(file_create_url($css_path[0] . '/test.css'), PHP_URL_PATH)); + if (strpos($menu_css_path, $GLOBALS['base_path']) === 0) { + $menu_css_path = substr($menu_css_path, strlen($GLOBALS['base_path'])); + } + $menu_js_path = trim(parse_url(file_create_url($js_path[0] . '/test.js'), PHP_URL_PATH)); + if (strpos($menu_js_path, $GLOBALS['base_path']) === 0) { + $menu_js_path = substr($menu_js_path, strlen($GLOBALS['base_path'])); + } + } + else { + // Determine paths if not using s3fs, or no_rewrite_cssjs is set. + // Use $css_path[1] and $js_path[1] since they are without schemes. + $menu_path_key = 1; + $menu_css_path = $css_path[1] . '/test.css'; + $menu_js_path = $js_path[1] . '/test.js'; + } + + // Use the paths set above to check menu router handler. + $advagg_async_generation_menu_issue = FALSE; + if (!file_uri_scheme($css_path[$menu_path_key])) { + $item_css = menu_get_item($menu_css_path); + if (empty($item_css['page_callback']) + || strpos($item_css['page_callback'], 'advagg') === FALSE + ) { + $advagg_async_generation_menu_issue = TRUE; + } + } + if (!file_uri_scheme($js_path[$menu_path_key])) { + $item_js = menu_get_item($menu_js_path); + if (empty($item_js['page_callback']) + || strpos($item_js['page_callback'], 'advagg') === FALSE + ) { + $advagg_async_generation_menu_issue = TRUE; + } + } + if ($advagg_async_generation_menu_issue) { + $requirements['advagg_async_generation_menu_issue'] = array( + 'title' => $t('Adv CSS/JS Agg - Async Mode'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Flush your caches.'), + 'description' => $t('You need to flush your menu cache. This can be done at the top of the performance page; under "Clear cache" press the "Clear all caches" button.', array( + '@performance' => url('admin/config/development/performance'), + )), + ); + } + + // Make hook_element_info_alter worked. + $styles_info = element_info('styles'); + $scripts_info = element_info('scripts'); + if (empty($styles_info['#pre_render']) + || !is_array($styles_info['#pre_render']) + || !in_array('advagg_modify_css_pre_render', $styles_info['#pre_render']) + || empty($scripts_info['#pre_render']) + || !is_array($scripts_info['#pre_render']) + || !in_array('advagg_modify_js_pre_render', $scripts_info['#pre_render']) + ) { + if (!empty($scripts_info['#group_callback']) && $scripts_info['#group_callback'] === 'omega_group_js') { + $requirements['advagg_hook_element_info_alter_omega'] = array( + 'title' => $t('Adv CSS/JS Agg - omega theme patch'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Omega theme needs a patch.'), + 'description' => $t('The patch can be found in this issue', array( + '@patch' => 'https://www.drupal.org/files/issues/omega-2492461-1-smarter-element-info-alter.patch', + '@issue' => 'https://www.drupal.org/node/2492461', + )), + ); + } + else { + $requirements['advagg_hook_element_info_alter'] = array( + 'title' => $t('Adv CSS/JS Agg - element_info'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Flush your caches.'), + 'description' => $t('You need to flush your cache_bootstrap cache bin as advagg_hook_element_info_alter() is not working correctly. This can be done near the top of the performance page under Clear cache.
    Styles:

    @styles

    Scripts:

    @scripts

    ', array( + '@performance' => url('admin/config/development/performance'), + '@styles' => print_r($styles_info, TRUE), + '@scripts' => print_r($scripts_info, TRUE), + )), + ); + } + } + + // Make sure some modules have the correct patches installed. + if (module_exists('css_emimage')) { + $file_path = drupal_get_path('module', 'css_emimage'); + if (!file_exists($file_path . '/css_emimage.advagg.inc')) { + $requirements['advagg_module_css_emimage_patch'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Embedded Images module'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The CSS Embedded Images module needs to be updated.'), + 'description' => $t('CSS Embedded Images needs to be upgraded to version 1.3 or higher, the currently installed version is incompatible with AdvAgg.', array('@link' => 'http://drupal.org/project/css_emimage')), + ); + } + } + if (module_exists('labjs')) { + if (!function_exists('labjs_advagg_modify_js_pre_render_alter')) { + $requirements['advagg_module_labjs_patch'] = array( + 'title' => $t('Adv CSS/JS Agg - LAB.js module'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The LAB.js module needs a patch to be compatible with AdvAgg.'), + 'description' => $t('You need to install the latest patch in this issue.', array('@link' => 'http://drupal.org/node/1977122')), + ); + } + } + + // Adjust some modules settings. + $search404_ignore_query = variable_get('search404_ignore_query', 'gif jpg jpeg bmp png'); + if (module_exists('search404') && + (strpos($search404_ignore_query, 'css') === FALSE + || strpos($search404_ignore_query, 'js') === FALSE + ) + ) { + $added_ext = array(); + if (strpos($search404_ignore_query, 'css') === FALSE) { + $added_ext[] = 'css'; + } + if (strpos($search404_ignore_query, 'js') === FALSE) { + $added_ext[] = 'js'; + } + $requirements['advagg_search404_module'] = array( + 'title' => $t('Adv CSS/JS Agg - Search 404 Settings'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP requests to advagg for css/js files may not be generating correctly.'), + 'description' => $t('The Search 404 module is enabled. You need to change the search404_ignore_query setting, also known as "Extensions to abort search" so advagg will work. Go to the Search 404 settings page under the "Advanced settings" fieldgroup and look for the "Extensions to abort search" setting. Add @code to the string that looks like this:

    @old

    so it will then look like this:

    @new

    ', array( + '@config' => url('admin/config/search/search404'), + '@code' => ' ' . implode(' ', $added_ext), + '@old' => $search404_ignore_query, + '@new' => trim($search404_ignore_query) . ' ' . implode(' ', $added_ext), + )), + ); + } + + if (module_exists('securepages') && variable_get('securepages_enable', 0) && function_exists('securepages_match')) { + $test_css = securepages_match($css_path[1] . '/test.css'); + $test_js = securepages_match($js_path[1] . '/test.js'); + if ($test_css === 0 || $test_js === 0) { + $added_paths = array(); + $securepages_ignore = variable_get('securepages_ignore', ''); + if (strpos($securepages_ignore, $css_path[1]) === FALSE) { + $added_paths[] = $css_path[1] . '/*'; + } + if (strpos($securepages_ignore, $js_path[1]) === FALSE) { + $added_paths[] = $js_path[1] . '/*'; + } + if (!empty($added_paths)) { + $requirements['advagg_securepages_module'] = array( + 'title' => $t('Adv CSS/JS Agg - Secure Pages Settings'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Requests to advagg for css/js files may be getting redirected to http on a https page.'), + ); + if (!empty($securepages_ignore)) { + $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

    !code

    to the string that looks like this:

    @old

    so it will then look like this:

    @old
    !code

    ', array( + '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), + '!code' => implode("\n
    ", $added_paths), + '@old' => trim($securepages_ignore), + )); + } + else { + $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

    !code

    to that section.', array( + '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), + '!code' => implode("\n
    ", $added_paths), + )); + } + } + } + } + + // Check that https is correct. + if (empty($GLOBALS['is_https']) && + ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] === 'on') + ) + ) { + $requirements['advagg_is_https_check'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTPS'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The core global $is_https is not TRUE.'), + 'description' => $t('You need to add in this logic near the top your settings.php file:
    @code
    ', array( + '@code' => 'if ((isset($_SERVER[\'HTTPS\']) && strtolower($_SERVER[\'HTTPS\']) == \'on\') + || (isset($_SERVER[\'HTTP_X_FORWARDED_PROTO\']) && $_SERVER[\'HTTP_X_FORWARDED_PROTO\'] == \'https\') + || (isset($_SERVER[\'HTTP_HTTPS\']) && $_SERVER[\'HTTP_HTTPS\'] == \'on\') +) { + $_SERVER[\'HTTPS\'] = \'on\'; +}', + )), + ); + } + + // Make sure $base_url is correct. + // Site is https but $base_url starts with http://. + if (!empty($GLOBALS['is_https']) + && strpos($GLOBALS['base_url'], 'http://') === 0 + ) { + $requirements['advagg_is_https_check'] = array( + 'title' => $t('Adv CSS/JS Agg - $base_url'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The core global $base_url\'s scheme is incorrect.'), + 'description' => $t('You need to add in this logic near the bottom of your settings.php file:

    @code

    ', array( + '@code' => 'if (isset($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on" && isset($base_url)) { + $base_url = str_replace("http://", "https://", $base_url); +}', + )), + ); + } + + return $requirements; +} + +/** + * Implements hook_requirements(). + */ +function advagg_requirements($phase) { + $t = get_t(); + $requirements = advagg_install_fast_checks($phase); + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + + // Make sure outbound http requests will work. + $request = drupal_http_request('https://www.google.com/robots.txt', array('timeout' => 8)); + if (empty($request->data) || $request->code != 200) { + $requirements['advagg_drupal_http_request_failure'] = array( + 'title' => $t('Adv CSS/JS Agg - drupal_http_request test'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('An external request for https://www.google.com/robots.txt could not be fulfilled.'), + 'description' => $t('If drupal_http_request does not work, the tests that AdvAgg performs may not be accurate.'), + ); + } + + // Make sure http requests to advagg will work. + advagg_install_check_via_http($requirements); + + // Check that file writes happen without any errors. + if (empty($requirements)) { + module_load_include("missing.inc", "advagg"); + $current_hash = advagg_get_current_hooks_hash(); + $aggregate_settings = advagg_get_hash_settings($current_hash); + $types = array('css', 'js'); + foreach ($types as $type) { + $filename = $type . ADVAGG_SPACE . 'test_write' . REQUEST_TIME . '.' . $type; + $files = array('misc/farbtastic/farbtastic.' . $type => array()); + list($files_to_save, $errors) = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); + foreach ($files_to_save as $uri => $data) { + @unlink($uri); + } + if (!empty($errors)) { + $requirements['advagg_file_write_error_' . $type] = array( + 'title' => $t('Adv CSS/JS Agg - File Write'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('File write had some issues with %type files.', array('%type' => $type)), + 'description' => $t('Most likely there is an issue with file and/or directory premissions. Error: @error', array( + '@error' => print_r($errors, TRUE), + )), + ); + } + } + } + + // If all requirements have been met, state advagg should be working. + if (empty($requirements)) { + $description = ''; + + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $description .= ' ' . $t('Currently running in development mode.'); + } + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5) { + $aggressive_cache_conflicts = advagg_aggressive_cache_conflicts(); + if (empty($aggressive_cache_conflicts)) { + $description .= ' ' . $t('It appears that there are no incompatible modules, so you should be able to safely use the Aggressive cache. To adjust this setting, go to the AdvAgg: configuration page and under "AdvAgg Cache Settings" select Aggressive and then save.', array( + '@config' => url('admin/config/development/performance/advagg', array( + 'fragment' => 'edit-advagg-cache-level', + )), + )); + } + } + + $requirements['advagg_ok'] = array( + 'title' => $t('Adv CSS/JS Agg'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('OK'), + 'description' => $t('Advanced CSS/JS Aggregator should be working correctly.') . ' ' . $description, + ); + } + + return $requirements; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * Make sure http requests to css/js files work correctly. + * + * @param array $requirements + * Array of requirements used in hook_requirements(). + */ +function advagg_install_check_via_http(array &$requirements) { + // If other checks have not passed, do not test this. + if (!empty($requirements)) { + return; + } + + // Ensure translations don't break at install time. + $t = get_t(); + + // Setup some variables. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $types = array('css', 'js'); + $config_path = advagg_admin_config_root_path(); + + // Get s3fs no_rewrite_cssjs setting. + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); + + // Make sure we get an advagg fast 404. + $mod_url = FALSE; + if (!variable_get('maintenance_mode', FALSE) && !variable_get('advagg_skip_404_check', FALSE)) { + foreach ($types as $type) { + if ($type === 'css') { + $url_path = $css_path[0]; + $file_path = $css_path[1]; + } + elseif ($type === 'js') { + $url_path = $js_path[0]; + $file_path = $js_path[1]; + } + + // Set arguments for drupal_http_request(). + // Make a 404 request to the advagg menu callback. + $url = file_create_url($url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type); + $options = array('timeout' => 8); + + if (empty($url)) { + $filename_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; + $filename = advagg_install_get_first_advagg_file($filename_path, $type); + $url = file_create_url($url_path . '/' . $filename); + $end = strpos($url, $filename); + if ($end !== FALSE) { + $url = substr($url, 0, $end) . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type; + } + else { + $requirements['advagg_self_request'] = array( + 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The uri: %url can not be converted to a url.', array('%url' => $url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type)), + 'description' => $t('If you are using a non default stream wrapper this might be the issue.'), + ); + continue; + } + } + + // Send request. + advagg_install_url_mod($url, $options, $mod_url); + $request = drupal_http_request($url, $options); + + // Try an alt URL if the request code is not positive. + if ($request->code < 0) { + $mod_url = TRUE; + advagg_install_url_mod($url, $options, $mod_url); + $new_request = drupal_http_request($url, $options); + + if ($new_request->code < 0) { + $description = ''; + if (!module_exists('httprl')) { + $description = t('Enabling the HTTP Parallel Request and Threading Library module might be able to fix this as AdvAgg will use HTTPRL to build the URL if it is enabled.', array('!httprl' => 'https://drupal.org/project/httprl')); + } + $requirements['advagg_self_request'] = array( + 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP loopback requests to this server are returning a non positive response code of %code', array('%code' => $new_request->code)), + 'description' => $t('If you have manually verified that AdvAgg is working correctly you can set the advagg_skip_404_check variable to TRUE in your settings.php. Editing the servers hosts file so the host name points to the localhost might also fix this (127.0.0.1 !hostname). To manually check go to @url, view the source (press ctrl+u on your keyboard) and check for this string @string. If that string is in the source, you can safely add this to your settings.php file @code', array( + '!hostname' => $_SERVER['HTTP_HOST'], + '@url' => $url, + '@string' => '', + '@code' => '$conf[\'advagg_skip_404_check\'] = TRUE;', + )) . ' ' . $description, + ); + // Skip the rest of the advagg checks as they will all fail. + $types = array(); + break; + } + else { + $request = $new_request; + } + } + + // Try request without https. + if ($request->code == 0 && stripos($request->error, 'Error opening socket ssl://') !== FALSE) { + $url = advagg_force_http_path($url); + $request = drupal_http_request($url, $options); + } + + // Try request to 127.0.0.1. + if ($request->code == 0 && stripos($request->error, 'getaddrinfo failed') !== FALSE) { + $parts = @parse_url($url); + if ($parts['host'] !== '127.0.0.1') { + $options['headers']['Host'] = $parts['host']; + $parts['host'] = '127.0.0.1'; + $url = advagg_glue_url($parts); + $request = drupal_http_request($url, $options); + } + } + + // Check response. Report an error if + // - Not a 404 OR + // - No data returned OR + // - Headers do not contain "x-advagg" AND + // - Body does not contain "advagg_missing_fast404". + if ($request->code != 404 + || empty($request->data) + || (empty($request->headers['x-advagg']) + && strpos($request->data, '') === FALSE + ) + ) { + // Fast 404 check. + $url_path_404 = parse_url($url, PHP_URL_PATH); + $exclude_paths = variable_get('404_fast_paths_exclude', FALSE); + $fast_404_html = variable_get('404_fast_html', '404 Not Found

    Not Found

    The requested URL "@path" was not found on this server.

    '); + // Replace @path in the variable with the page path. + $fast_404_html = trim(strtr($fast_404_html, array('@path' => check_plain($url_path_404)))); + if (!empty($request->data) + && $fast_404_html == trim($request->data) + && !empty($exclude_paths) + && strpos($exclude_paths, 'advagg_') === FALSE + ) { + $pos_a = strpos($exclude_paths, '(?:styles)'); + $pos_b = strpos($exclude_paths, '(?:styles|'); + if ($exclude_paths === '/\/(?:styles)\//') { + $description = $t('Change it from %value to /\/(?:styles|advagg_(cs|j)s)\//', array( + '%value' => $exclude_paths, + )); + } + elseif ($pos_a !== FALSE) { + $description = $t('Change it from %value to %code ', array( + '%value' => $exclude_paths, + '%code' => str_replace('(?:styles)', '(?:styles|advagg_(cs|j)s)', $exclude_paths), + )); + } + elseif ($pos_b !== FALSE) { + $description = $t('Change it from %value to %code ', array( + '%value' => $exclude_paths, + '%code' => str_replace('(?:styles|', '(?:styles|advagg_(cs|j)s|', $exclude_paths), + )); + } + else { + $description = $t('Add in advagg_(cs|j)s into the regex. Current value: %value', array( + '%value' => $exclude_paths, + )); + } + $requirements['advagg_404_fast_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Fast 404: HTTP Request'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'description' => $t('If you have fast 404 enabled in your settings.php file, you need to change the 404_fast_paths_exclude setting so advagg will work.') . ' ' . $description, + ); + } + elseif (module_exists('fast_404') + && defined('FAST_404_EXT_CHECKED') + && !in_array('/advagg_', variable_get('fast_404_string_whitelisting', array())) + && strpos(variable_get('fast_404_exts', '/^(?!robots).*\.(txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'), $type) !== FALSE + ) { + $requirements['advagg_fast_404_module_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Fast 404: HTTP Request'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'description' => $t('The fast 404 module is enabled. You need to change the fast_404_string_whitelisting setting so advagg will work. In your settings.php file add in the code below:
    @code
    ', + array('@code' => '$conf[\'fast_404_string_whitelisting\'][] = \'/advagg_\';')), + ); + } + elseif (module_exists('stage_file_proxy') + && variable_get('stage_file_proxy_origin', NULL) + && strpos(advagg_file_get_contents(drupal_get_path('module', 'stage_file_proxy') . '/stage_file_proxy.module'), 'advagg') === FALSE + ) { + // Stage File Proxy patch is missing. + $requirements['advagg_stage_file_proxy_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Fast 404: HTTP Request'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'description' => $t('If you have the Stage File Proxy module enabled, make sure this patch has been applied.', array( + '@patch' => 'https://drupal.org/node/1977170#comment-7331810', + '@module' => 'https://drupal.org/project/stage_file_proxy', + )), + ); + } + elseif (!variable_get('clean_url', 0)) { + $requirements['advagg_clean_url'] = array( + 'title' => $t('Adv CSS/JS Agg - Clean URLs'), + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Go to the clean URL settings page and enable Clean URLs.', array( + '@settings' => url('admin/config/search/clean-urls'), + )), + ); + } + elseif ($request->code == 401) { + $requirements['advagg_set_user_pass'] = array( + 'title' => $t('Adv CSS/JS Agg - Set Basic Auth'), + 'value' => $t('HTTP requests to advagg for @type files are not getting through.', array('@type' => $type)), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Authorization is required when accessing your site. In order to test that the @type files are working you will need to add the following code in your settings.php file:

    @code1
    @code2

    filling in the correct username and password needed to access this site.', array( + '@code1' => '$conf[\'advagg_auth_basic_user\'] = \'\'; ', + '@code2' => '$conf[\'advagg_auth_basic_pass\'] = \'\';', + '@type' => $type, + )), + ); + } + elseif ($request->code == 403) { + $requirements['advagg_' . $type . '_server_permissions'] = array( + 'title' => $t('Adv CSS/JS Agg - Webserver can not access files'), + 'value' => $t('HTTP requests to advagg for @type files are not getting through.', array('@type' => $type)), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Your webserver can not access @type advagg files. This is usually a server permissions issue. Raw request info:
    @request
    ', array( + '@type' => $type, + '@request' => var_export($request, TRUE), + )), + ); + } + elseif (stripos($request->data, 'nginx')) { + $config_location = ''; + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $config_location = ' ' . $t('You might be able to find the nginx configuration file by running
    @command_1
    or
    @command_2
    ', array( + '@command_1' => 'ps -o args -C nginx', + '@command_2' => 'nginx -t', + )); + } + $requirements['advagg_' . $type . '_nginx_config'] = array( + 'title' => $t('Adv CSS/JS Agg - Nginx not sending 404 to Drupal.'), + 'value' => $t('HTTP requests to advagg for @type files are not getting through.', array('@type' => $type)), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Your nginx webserver is not sending 404s to drupal. Please make sure that your nginx configuration has something like this in it:

    @code

    Note that @drupal (last line of code above) might be @rewrite or @rewrites depending on your servers configuration. If there are image style rules in your Nginx configuration add this right below that. !config_location Raw request info:
    @request
    ', array( + '@request' => var_export($request, TRUE), + '@code' => ' +### +### advagg_css and advagg_js support +### +location ~* files/advagg_(?:css|js)/ { + gzip_static on; + access_log off; + expires max; + add_header ETag ""; + add_header Cache-Control "max-age=31449600, no-transform, public"; + try_files $uri $uri/ @drupal; +}', + '!config_location' => $config_location, + )), + ); + } + elseif (!advagg_install_htaccess_errordocument($type)) { + $parsed_base_url = parse_url($GLOBALS['base_url']); + if (isset($parsed_base_url['scheme'])) { + unset($parsed_base_url['scheme']); + } + if ($type === 'css') { + $location = $css_path[1] . '/.htaccess'; + } + if ($type === 'js') { + $location = $js_path[1] . '/.htaccess'; + } + $requirements['advagg_' . $type . '_errordoc_404'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through. The .htaccess needs to be rebuilt.'), + 'description' => $t('The .htaccess file generated by AdvAgg has the incorrect errordoc location. This can happen if Drush is used incorrectly or if the site has been moved to a different directory structure. If you are currently using drush this is how to access it correctly:

    @drush

    Odds are you will need to fix the errordoc location. Go to the AdvAgg: Operations page and under Regenerate .htaccess files press the Recreate htaccess files button. If you wish to manually edit the file go to the @htaccess_loc file and make sure the following line is in there near the top and any other ErrorDocument 404 statements have been removed.

    @code

    ', array( + '@drush' => 'drush --root=' . DRUPAL_ROOT . '/ --uri=' . advagg_glue_url($parsed_base_url) . ' ', + '@url' => url($config_path . '/advagg/operations', array('fragment' => 'edit-htaccess')), + '@htaccess_loc' => $location, + '@code' => "ErrorDocument 404 {$GLOBALS['base_path']}index.php", + )), + ); + } + elseif (!is_null($s3fs_no_rewrite_cssjs) + && !empty($s3fs_no_rewrite_cssjs) + && !empty($request->headers['server']) + && $request->headers['server'] === 'AmazonS3' + ) { + $severity = REQUIREMENT_WARNING; + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { + $severity = REQUIREMENT_ERROR; + } + // S3 doesn't do origin pull. + $requirements['advagg_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => $severity, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'description' => $t('AdvAgg will issue a request for a file that does not exist inside of the AdvAgg directory. If AdvAgg sends a 404, everything is ok; if something else sends a 404 then that means that AdvAgg will not be able to generate an aggregate if it is missing as something else is handling the 404 before AdvAgg has a chance to do it. If you are reading this, it means that something else is handling the 404 before AdvAgg can. In this case the s3fs Advanced Configuration Option "Don\'t render proxied CSS/JS file paths" should be disabled. Raw request info:
    @request
    ', array( + '@request' => var_export($request, TRUE), + '@url' => url('admin/config/media/s3fs', array('fragment' => 'edit-s3fs-no-rewrite-cssjs')), + )), + ); + } + else { + // Menu callback failed. + $requirements['advagg_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through.'), + 'description' => $t('AdvAgg will issue a request for a file that does not exist inside of the AdvAgg directory. If AdvAgg sends a 404, everything is ok; if something else sends a 404 then that means that AdvAgg will not be able to generate an aggregate if it is missing as something else is handling the 404 before AdvAgg has a chance to do it. If you are reading this, it means that something else is handling the 404 before AdvAgg can. In some cases this can sometimes be a false report; go here: @url and check if the source (press ctrl+u on your keyboard) has an html comment that says "advagg_missing_fast404"; if it does, this is a false report, add this $conf[\'advagg_skip_404_check\'] = TRUE; to your settings.php file. Raw request info:
    @request
    ', array( + '@request' => var_export($request, TRUE), + '@url' => $url, + )), + ); + } + } + } + } + elseif (variable_get('maintenance_mode', FALSE)) { + $requirements['advagg_maintenance_mode'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t("HTTP requests to advagg's 404 handler can not be tested currently."), + 'description' => $t('This can not be tested while the site is in maintenance mode', array('@maintenance' => url('admin/config/development/maintenance'))), + ); + } + + // Check gzip encoding. + foreach ($types as $type) { + if ($type === 'css') { + $url_path = $css_path[0]; + $file_path = $css_path[1]; + } + elseif ($type === 'js') { + $url_path = $js_path[0]; + $file_path = $js_path[1]; + } + // Get filename. + $filename_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; + $filename = advagg_install_get_first_advagg_file($filename_path, $type); + + // Skip if filename is empty. + if (empty($filename)) { + continue; + } + + $urls = array(); + $url = file_create_url($url_path . '/' . $filename); + if (empty($url)) { + continue; + } + $urls[] = $url; + if (module_exists('cdn')) { + // Get CDN defaults. + $blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT); + $auth_blacklist = variable_get(CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE, CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT); + // Set CDN blacklists to be empty. + $GLOBALS['conf'][CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = ''; + $GLOBALS['conf'][CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE] = ''; + // Create URL. + $urls[] = file_create_url($url_path . '/' . $filename); + // Set CDN blacklist back to the original value. + $GLOBALS['conf'][CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = $blacklist; + $GLOBALS['conf'][CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE] = $auth_blacklist; + } + $urls = array_unique($urls); + + // Set arguments for drupal_http_request(). + $options = array( + 'headers' => array( + 'Accept-Encoding' => 'gzip, deflate', + ), + 'version' => '1.0', + '#advagg_path' => "{$file_path}/{$filename}", + 'timeout' => 8, + ); + // Test http 1.0. + $old_requirements = $requirements; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + // Test http 1.1 + // If Drupal version is >= 7.22 and httprl_override_core exists. + if (defined('VERSION') && floatval(VERSION) >= 7.22 && is_callable('httprl_override_core')) { + $old = variable_get('drupal_http_request_function', FALSE); + $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; + + // Only test 1.1; 1.0 is rarely used these days. + $requirements = $old_requirements; + + $options['version'] = '1.1'; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + $GLOBALS['conf']['drupal_http_request_function'] = $old; + } + + if (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && variable_get('advagg_brotli', ADVAGG_BROTLI) + ) { + // Set arguments for drupal_http_request(). + $options = array( + 'headers' => array( + 'Accept-Encoding' => 'br', + ), + 'version' => '1.0', + 'timeout' => 8, + ); + // Test http 1.0. + $old_requirements = $requirements; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + // Test http 1.1 + // If Drupal version is >= 7.22 and httprl_override_core exists. + if (defined('VERSION') && floatval(VERSION) >= 7.22 && is_callable('httprl_override_core')) { + $old = variable_get('drupal_http_request_function', FALSE); + $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; + + // Only test 1.1; 1.0 is rarely used these days. + $requirements = $old_requirements; + + $options['version'] = '1.1'; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + $GLOBALS['conf']['drupal_http_request_function'] = $old; + } + } + } +} + +/** + * Make sure http requests to css/js files work correctly. + * + * @param array $requirements + * Array of requirements used in hook_requirements(). + * @param array $urls + * Array of urls. + * @param array $options + * Options array to pass to drupal_http_request(). + * @param bool $mod_url + * Set to TRUE if an alt URL was used. + * @param string $type + * String: css or js. + * @param string $url_path + * The url path to the file. + * @param string $file_path + * File path to the file. + * @param string $filename + * Name of the file. + */ +function advagg_install_chk_urls(array &$requirements, array $urls, array $options, $mod_url, $type, $url_path, $file_path, $filename) { + // Ensure translations don't break at install time. + $t = get_t(); + + list($css_path, $js_path) = advagg_get_root_files_dir(); + $config_path = advagg_admin_config_root_path(); + + $options += array( + 'timeout' => 8, + ); + + $is_apache = FALSE; + if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== FALSE || function_exists('apache_get_modules')) { + $is_apache = TRUE; + $mod_headers = advagg_install_apache_mod_loaded('mod_headers'); + $mod_rewrite = advagg_install_apache_mod_loaded('mod_rewrite'); + $mod_expires = advagg_install_apache_mod_loaded('mod_expires'); + } + foreach ($urls as $url) { + $key = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + // Make sure the URL contains a schema. + if (strpos($url, 'http') !== 0) { + if ($GLOBALS['is_https']) { + $url = 'https:' . $url; + } + else { + $url = 'http:' . $url; + } + } + + // Before sending the request when using s3fs, check if the file exists. + if (module_exists('s3fs') && !file_exists($url_path . '/' . $filename)) { + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { + $httprl_message = 'This may be due to an issue with the HTTPRL module or its configuration. '; + } + else { + $httprl_message = ''; + } + $requirements['advagg_' . $type . '_missing' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - file does not exist'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Unable to find %type files.', array('%type' => $type)), + 'description' => $t('The AdvAgg database records and S3 files are not in sync. The file referenced in the database to perform a test cannot be found in the S3 file system. @httprl_messageFile URL:
    @file
    ', array( + '@httprl_message' => $httprl_message, + '@file' => $url, + )), + ); + continue; + } + + // Send request. + advagg_install_url_mod($url, $options, $mod_url); + $request = drupal_http_request($url, $options); + $encoding_type = 'gzip'; + if (!empty($request->options['headers']['Accept-Encoding']) && strpos($request->options['headers']['Accept-Encoding'], 'br') !== FALSE) { + $encoding_type = 'br'; + } + + if (!variable_get('advagg_skip_gzip_check', ADVAGG_SKIP_GZIP_CHECK)) { + // Check response. Report an error if + // - Not a 200. + // - Headers do not contain "content-encoding". + // - content-encoding is not gzip, deflate or br. + if ($request->code != 200 + || empty($request->headers['content-encoding']) + || ($request->headers['content-encoding'] !== 'gzip' + && $request->headers['content-encoding'] !== 'deflate' + && $request->headers['content-encoding'] !== 'br' + ) + ) { + // Gzip failed. + if (!variable_get('advagg_gzip', ADVAGG_GZIP) + && $encoding_type === 'gzip' + ) { + // Recommend that gzip be turned on. + $requirements['advagg_' . $type . '_gzip' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - gzip'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Gzip is failing for %type files.', array('%type' => $type)), + 'description' => $t('Try enabling on the "Create .gz files" setting on the Advanced CSS/JS Aggregation Configuration page', array( + '@advagg' => url($config_path . '/advagg'), + '%type' => $type, + )), + ); + } + elseif (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && !variable_get('advagg_brotli', ADVAGG_BROTLI) + && $encoding_type === 'br' + ) { + // Recommend that br be turned on. + $requirements['advagg_' . $type . '_br' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - brotli'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Brotli is failing for %type files.', array('%type' => $type)), + 'description' => $t('Try enabling on the "Create .br files" setting on the Advanced CSS/JS Aggregation Configuration page', array( + '@advagg' => url($config_path . '/advagg'), + '%type' => $type, + )), + ); + } + else { + // If not apache skip this. + $apache_module_missing = FALSE; + if ($is_apache) { + if ($mod_headers === FALSE || $mod_rewrite === FALSE) { + $apache_module_missing = TRUE; + if ($mod_headers === FALSE) { + $requirements['advagg_mod_headers' . $key . '_' . $encoding_type] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_headers" is not available. Enable mod_headers for Apache if at all possible. This is causing @encoding to fail.', array( + '!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html', + '@encoding' => $encoding_type, + )), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + } + if ($mod_rewrite === FALSE) { + $requirements['advagg_mod_rewrite' . $key . '_' . $encoding_type] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_rewrite" is not available. You must enable mod_rewrite for Apache. This is causing @encoding to fail.', array( + '!link' => 'http://httpd.apache.org/docs/current/mod/mod_rewrite.html', + '@encoding' => $encoding_type, + )), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Apache module "mod_rewrite" is not installed.'), + ); + } + } + } + if (!$apache_module_missing) { + // Check via external service. + $ext_url = 'http://checkgzipcompression.com/?url=' . urlencode($url); + if ($encoding_type === 'br') { + $ext_url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; + } + $external_compression_request = drupal_http_request($ext_url, array( + 'timeout' => 7, + 'headers' => array('Connection' => 'close'), + )); + if (!empty($external_compression_request->data)) { + if ($encoding_type === 'br') { + if (stripos($external_compression_request->data, '') !== FALSE) { + preg_match("/(.*)<\/strong>/siU", $external_compression_request->data, $title_matches); + if (stripos($title_matches[1], 'Negative') === FALSE) { + $external_test_results = 1; + } + else { + $external_test_results = -1; + } + } + else { + $external_test_results = 0; + } + } + elseif (stripos($external_compression_request->data, '') !== FALSE) { + preg_match("/<title>(.*)<\/title>/siU", $external_compression_request->data, $title_matches); + if (stripos($title_matches[1], 'gzip') === FALSE) { + $external_test_results = 0; + } + elseif (stripos($title_matches[1], 'not gzip') === FALSE) { + $external_test_results = 1; + } + else { + $external_test_results = -1; + } + } + } + if (!isset($external_test_results) || $external_test_results !== 1) { + if ($request->code != 200) { + $rewritebase = advagg_htaccess_rewritebase(); + if (!empty($rewritebase)) { + if ($type === 'css') { + $rewritebase_advagg = advagg_htaccess_rewritebase($css_path[1]); + } + if ($type === 'js') { + $rewritebase_advagg = advagg_htaccess_rewritebase($js_path[1]); + } + } + $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); + if ($request->code == 307 && !empty($rewritebase) && empty($rewritebase_advagg) && empty($advagg_htaccess_rewritebase)) { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. The RewriteBase option should be set on the <a href="@url">configuration page</a> under "Obscure Options" look for "AdvAgg RewriteBase Directive in .htaccess files". Raw request info: <pre>@request</pre>', array( + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + '@url' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-htaccess-rewritebase')), + )), + ); + } + else { + if (module_exists('s3fs') && ($request->code == 307 || $request->code == -2)) { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - Redirect Loop'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('S3fs issue. Proxy is not setup correctly for %type.', array( + '%type' => $type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. Apache proxy settings for httpd.conf: <p><code>!httpd</code></p>. Raw request info: <pre>@request</pre>', array( + '!httpd' => nl2br(str_replace(' ', '  ', htmlentities(advagg_install_s3fs_proxy_settings($type)))), + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + )), + ); + } + else { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. @encoding can not be tested. Raw request info: <pre>@request</pre>', array( + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + )), + ); + } + } + } + elseif (empty($request->data)) { + $url = 'http://checkgzipcompression.com/?url=' . urlencode($url); + if ($encoding_type === 'br') { + $url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; + } + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('No data was returned from your server; this @encoding test can not be done locally. Error: @error - @message You can manually test if @encoding is working by <a href="@urlGZ">going here</a> and seeing if @encoding is enabled. You can turn this warning off by adding this to your settings.php file: <code>@skipcode</code>', array( + '@error' => $request->code, + '@message' => isset($request->error) ? $request->error : '', + '@url' => $url, + '@skipcode' => '$conf[\'advagg_skip_gzip_check\'] = TRUE;', + '@encoding' => $encoding_type, + )), + ); + } + else { + // Recommend servers configuration be adjusted. + $request->data = '...'; + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('The web servers configuration will need to be adjusted. In most cases make sure that the webroots .htaccess file still contains this section "Rules to correctly serve gzip compressed CSS and JS files". Also check in the <a href="@readme">readme</a>, under Troubleshooting. Certain default web server configurations (<a href="!nginx">nginx</a>) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to <a href="!cloudfront">add metadata</a> to the .gz files. There are some other options if using <a href="!so">cloudfront</a>. Raw request info: <pre>@request</pre>', array( + '!nginx' => 'http://www.cdnplanet.com/blog/gzip-nginx-cloudfront/', + '@request' => print_r($request, TRUE), + '!cloudfront' => 'http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#CompressedS3', + '!so' => 'http://stackoverflow.com/questions/5442011/serving-gzipped-css-and-javascript-from-amazon-cloudfront-via-s3', + '@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'), + )), + ); + } + } + } + } + } + elseif ($request->code == 200 + && !empty($request->headers['content-encoding']) + && !empty($request->data) + && ($request->headers['content-encoding'] === 'gzip' + || $request->headers['content-encoding'] === 'deflate' + || $request->headers['content-encoding'] === 'br' + )) { + // Do the first level of decoding if not already done. + if (!isset($request->chunk_size)) { + if ($request->headers['content-encoding'] === 'gzip') { + $request->data = @gzinflate(substr($request->data, 10)); + } + elseif ($request->headers['content-encoding'] === 'deflate') { + $request->data = @gzinflate($request->data); + } + elseif ($request->headers['content-encoding'] === 'br' && is_callable('brotli_uncompress')) { + $request->data = @brotli_uncompress($request->data); + } + } + // Check for double gzip compression. + $contents = @file_get_contents($options['#advagg_path']); + if ($contents !== $request->data && (@gzinflate(substr($request->data, 10)) !== FALSE || @gzinflate($request->data) !== FALSE)) { + $config_path = advagg_admin_config_root_path(); + $description = ''; + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { + $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .gz files setting.', array( + '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), + )); + } + else { + $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( + '%type' => $type, + )); + if (isset($request->headers['content-type'])) { + $description .= ' ' . $t('The content type is: %type.', array( + '%type' => $request->headers['content-type'], + )); + } + } + $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - gzip'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Double gzip encoding detected for %type files.', array('%type' => $type)), + 'description' => $description, + ); + } + if ($contents !== $request->data && (is_callable('brotli_uncompress') && @brotli_uncompress($request->data) !== FALSE)) { + $config_path = advagg_admin_config_root_path(); + $description = ''; + if (variable_get('advagg_brotli', ADVAGG_BROTLI)) { + $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .br files setting.', array( + '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), + )); + } + else { + $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( + '%type' => $type, + )); + if (isset($request->headers['content-type'])) { + $description .= ' ' . $t('The content type is: %type.', array( + '%type' => $request->headers['content-type'], + )); + } + } + $requirements['advagg_' . $type . '_br' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - br'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Double br encoding detected for %type files.', array('%type' => $type)), + 'description' => $description, + ); + } + } + } + $content_type = $type; + if ($content_type === 'js') { + $content_type = 'javascript'; + } + + if ($request->code == 200) { + $matches = array(); + if (!empty($request->headers['x-advagg']) + && preg_match('/Generated file at (\\d+)/is', $request->headers['x-advagg'], $matches) + && $matches[1] + 30 > REQUEST_TIME + ) { + if (!file_exists($file_path . '/' . $filename)) { + $requirements['advagg_' . $type . '_file_write_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Can not write to the filesystem'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The request for a file was served by Drupal instead of the web server (like Apache).'), + 'description' => $t('Something is preventing writes to your filesystem from working.'), + ); + } + else { + $requirements['advagg_' . $type . '_loopback_issue' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Incorrect readings'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The request for a file was served by Drupal instead of the web server (like Apache).'), + 'description' => $t('It means that AdvAgg can not test for Far-Future headers internally and you will need to use an external tool in order to do so. Warnings given or not given about AdvAgg Expires, Cache-Control, and If-Modified-Since might be incorrect. In the <a href="@readme">readme</a>, under Troubleshooting try the Far-Future recommendations.', array('@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'))), + ); + } + } + + if ($type === 'css' + && (empty($request->headers['content-type']) + || strpos($request->headers['content-type'], 'text/' . $content_type) === FALSE + ) + ) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_type' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Content-Type'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The wrong Content-Type is being sent by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. Was looking for <code>@typematch</code>, actually got <code>@received</code>.', array( + '@typematch' => 'text/' . $content_type, + '@received' => isset($request->headers['content-type']) ? $request->headers['content-type'] : 'NULL', + )), + ); + } + + if ($type === 'js' + && (empty($request->headers['content-type']) + || (strpos($request->headers['content-type'], 'application/' . $content_type) === FALSE + && strpos($request->headers['content-type'], 'application/x-' . $content_type) === FALSE + ) + ) + ) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_type' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Content-Type'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The wrong Content-Type is being sent by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. Was looking for <code>@typematch</code>, actually got <code>@received</code>. You might need to apply the drupal core patch located here <a href="@url">@url</a>.', array( + '@url' => 'https://drupal.org/node/2193333#comment-8469991', + '@typematch' => 'application/' . $content_type, + '@received' => isset($request->headers['content-type']) ? $request->headers['content-type'] : 'NULL', + )), + ); + } + + // Test far future headers. + $apache_module_missing = FALSE; + if (!variable_get('advagg_skip_far_future_check', ADVAGG_SKIP_FAR_FUTURE_CHECK) + && empty($_SERVER['PANTHEON_ENVIRONMENT']) + && empty($_SERVER['PANTHEON_SITE_NAME']) + ) { + // Make sure the expires header is at least set to 1 month + // (2628000 seconds) in the future. + if (!empty($request->headers['expires']) + && strtotime($request->headers['expires']) < time() + 2628000 + ) { + // Recommend servers configuration be adjusted. + if ($is_apache && $mod_headers === FALSE) { + $apache_module_missing = TRUE; + } + else { + $requirements['advagg_' . $type . '_expires' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Expires'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The expires header being sent by your web server is not at least 1 month in the future.'), + 'description' => $t('The web servers configuration should be adjusted only for AdvAgg files. Was looking for a second counter over 2,628,000 (1 month), actually got <code>@received</code> (@expires). You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', array( + '@received' => isset($request->headers['expires']) ? number_format(strtotime($request->headers['expires']) - REQUEST_TIME) : 'NULL', + '@expires' => isset($request->headers['expires']) ? $request->headers['expires'] : 'NULL', + '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', + )), + ); + } + } + // Make sure the cache-control header max age value is at least set to + // 1 month (2628000 seconds) in the future. + $matches = array(); + if (empty($request->headers['cache-control']) + || !preg_match('/\s*max-age\s*=\s*(\\d+)\s*/is', $request->headers['cache-control'], $matches) + || $matches[1] < 2628000 + ) { + // Recommend servers configuration be adjusted. + if ($is_apache && $mod_headers === FALSE) { + $apache_module_missing = TRUE; + } + else { + $requirements['advagg_' . $type . '_cache_control' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Cache-Control'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t("The cache-control's max-age header being sent by your web server is not at least 1 month in the future."), + 'description' => $t('The web servers configuration should be adjusted only for AdvAgg files. Was looking that the max-age second counter is over 2,628,000 (1 month), actually got <code>@received</code>. You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', array( + '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', + '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', + )), + ); + } + } + } + + // Handle missing apache modules. + if ($apache_module_missing && $is_apache && $mod_headers === FALSE) { + $requirements['advagg_mod_headers_far_future_headers_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_headers" is not available. Enable <a href="!link">mod_headers</a> for Apache if at all possible. This is causing far-future headers to not be sent correctly.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html')), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + if ($mod_expires === FALSE) { + $requirements['advagg_mod_headers_far_future_expires_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Apache'), + 'description' => $t('The Apache module "mod_expires" is not available. Enable <a href="!link">mod_expires</a> for Apache if at all possible. This is causing far-future headers to not be sent correctly.', array('!link' => 'http://httpd.apache.org/docs/current/mod/mod_expires.html')), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + } + } + + // Test 304. + if (!variable_get('advagg_skip_304_check', ADVAGG_SKIP_304_CHECK) + && empty($_SERVER['PANTHEON_ENVIRONMENT']) + && empty($_SERVER['PANTHEON_SITE_NAME']) + ) { + $etag_works = FALSE; + if (isset($request->headers['etag'])) { + // Send an Etag header and see if the web server returns a 304. + $url = file_create_url($url_path . '/' . $filename); + $if_modified_options = $options; + $if_modified_options['headers'] = array( + 'Accept-Encoding' => 'gzip, deflate, br', + 'If-None-Match' => $request->headers['etag'], + ); + if (!empty($request->options['headers']['Accept-Encoding'])) { + $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; + } + + // Send request. + advagg_install_url_mod($url, $if_modified_options, $mod_url); + $request_304 = drupal_http_request($url, $if_modified_options); + if ($request_304->code != 304) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_304' . $key . '_etag'] = array( + 'title' => $t('Adv CSS/JS Agg - Etag'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The If-None-Match (Etag) header is being ignored by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( + '@code' => $request_304->code, + )), + ); + } + else { + $etag_works = TRUE; + } + } + if (isset($request->headers['last-modified'])) { + // Send a If-Modified-Since header and see if the web server returns a + // 304. + $url = file_create_url($url_path . '/' . $filename); + $if_modified_options = $options; + $if_modified_options['headers'] = array( + 'Accept-Encoding' => 'gzip, deflate, br', + 'If-Modified-Since' => $request->headers['last-modified'], + ); + if (!empty($request->options['headers']['Accept-Encoding'])) { + $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; + } + + // Send request. + advagg_install_url_mod($url, $if_modified_options, $mod_url); + $request_304 = drupal_http_request($url, $if_modified_options); + if ($request_304->code != 304) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_304' . $key . '_last_modified'] = array( + 'title' => $t('Adv CSS/JS Agg - If-Modified-Since'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The If-Modified-Since (Last-Modified) header is being ignored by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( + '@code' => $request_304->code, + )), + ); + } + elseif (isset($requirements['advagg_' . $type . '_304' . $key . '_etag'])) { + // Last-Modified works, Etag is broken. 304s are working. Don't warn + // user. + unset($requirements['advagg_' . $type . '_304' . $key . '_etag']); + } + } + if ($etag_works && isset($requirements['advagg_' . $type . '_304' . $key . '_last_modified'])) { + // Etag works, Last-Modified is broken. 304s are working. Don't warn + // user. + unset($requirements['advagg_' . $type . '_304' . $key . '_last_modified']); + } + + // Both the Last-Modified and Etag header are missing. + if (empty($request->headers['last-modified']) && empty($request->headers['etag'])) { + // Get path to advagg .htaccess file. + $files = array( + $file_path . '/.htaccess', + DRUPAL_ROOT . '/.htaccess', + ); + + // Check for bad .htaccess files. + $bad_config_found = FALSE; + foreach ($files as $count => $file) { + if (!file_exists($file)) { + continue; + } + $contents = advagg_file_get_contents($file); + if (strpos($contents, 'Header unset Last-Modified') !== FALSE) { + $bad_config_found = TRUE; + $requirements['advagg_' . $type . '_last-modified_' . $key . $count] = array( + 'title' => $t('Adv CSS/JS Agg - Last-Modified'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The Last-Modified header is not being sent out by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header. Remove "Header unset Last-Modified" inside this file to fix the issue: @file', array('@file' => $file)), + ); + } + } + + // Recommend servers configuration be adjusted. + if (!$bad_config_found) { + $requirements['advagg_' . $type . '_last-modified_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Last-Modified'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The Last-Modified header is not being sent out by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header and/or an Etag header. If you can not get the Last-Modified header to work, you can try using ETags. Inside <code>@htaccess</code> right before <code>@line1</code> at the bottom of the file add in <code>@line2</code>. You will also need to remove the <code>@line3</code> line further up.', array( + '@htaccess' => $file_path . '/.htaccess', + '@line1' => '</FilesMatch>', + '@line2' => 'FileETag MTime Size', + '@line3' => 'Header unset ETag', + )), + ); + } + } + } + } + } +} + +/** + * Given a advagg path this will return the first aggregate it can find. + * + * @param string $directory + * Path to the advagg css/js dir. + * @param string $type + * String: css or js. + * + * @return string + * Returns aggregate filename or an empty string on failure. + */ +function advagg_install_get_first_advagg_file($directory, $type) { + module_load_include('inc', 'advagg', 'advagg.missing'); + $filename = ''; + + // Get contents of the advagg directory. + $scanned_directory = @scandir($directory); + // Bailout here if the advagg directory is empty. + if (empty($scanned_directory)) { + // Get a file that will generate from the database. + return advagg_generate_advagg_filename_from_db($type); + } + // Filter list. + $blacklist = array('..', '.', '.htaccess', 'parts'); + $scanned_directory = array_diff($scanned_directory, $blacklist); + + // Make the last file empty. + $scanned_directory[] = ''; + + foreach ($scanned_directory as $key => $filename) { + // Skip if filename is not long enough. + $len = strlen($filename); + if ($len < 91 + strlen(ADVAGG_SPACE) * 3) { + continue; + } + + // See if this uri contains .gz near the end of it. + $pos = strripos($filename, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); + if (!empty($pos)) { + // If this is a .gz file skip. + if ($pos == $len - 3) { + continue; + } + } + + $gzip_filename = $scanned_directory[$key + 1]; + $br_filename = $scanned_directory[$key + 1]; + if (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && variable_get('advagg_brotli', ADVAGG_BROTLI) + ) { + $gzip_filename = $scanned_directory[$key + 2]; + // Skip if the next file does not have a .br extension. + // This can occur if: + // - File is not .br compressed, or, + // - Using s3fs module and only .br compression is set. In + // this case, the advagg_advadgg_save_aggregate_alter() + // function will not add a file extension. + if (strcmp($filename . '.br', $br_filename) !== 0 + && (!module_exists('s3fs') + || (module_exists('s3fs') && variable_get('advagg_gzip', ADVAGG_GZIP)))) { + continue; + } + } + else { + // Skip if the next file is a .br file. + if (strcmp($filename . '.br', $br_filename) === 0) { + continue; + } + } + + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { + // Skip if the next file does not have a .gz extension. + // This can occur if: + // - File is not .gz compressed, or, + // - Using s3fs module and either: + // - Only .gz compression option is set or, + // - Both .gz and .br compression options are set. In + // this case, the advagg_advagg_save_aggregate_alter() + // function creates a .gz file by default. + if (strcmp($filename . '.gz', $gzip_filename) !== 0 && !module_exists('s3fs')) { + continue; + } + } + else { + // Skip if the next file is a .gz file. + if (strcmp($filename . '.gz', $gzip_filename) === 0) { + continue; + } + } + + $data = advagg_get_hashes_from_filename(basename($filename)); + if (is_array($data)) { + list($type, $aggregate_filenames_hash, $aggregate_contents_hash) = $data; + + // Get a list of files. + $files = advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash); + + if (!empty($files)) { + // All checked passed above, break out of loop. + break; + } + } + } + + if (empty($filename)) { + return advagg_generate_advagg_filename_from_db($type); + } + return $filename; +} + +/** + * Modify $url and $options before making the HTTP request. + * + * @param string $url + * Full URL. + * @param array $options + * Options array for drupal_http_request(). + * @param bool $mod_url + * Set to TRUE to try and modify the $url variable. + */ +function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { + // Set the $options array. + // Set all timeouts to 8 seconds. + $options += array( + 'timeout' => 8, + 'dns_timeout' => 8, + 'connect_timeout' => 8, + 'ttfb_timeout' => 8, + ); + // Set connection to closed to prevent keep-alive from causing a timeout. + $options['headers']['Connection'] = 'close'; + // Set referrer to current page. + $options['headers']['Referer'] = $GLOBALS['base_root'] . request_uri(); + + $parts = @parse_url($url); + if (!is_array($parts)) { + return; + } + + // Check if this is a protocol relative url, if so add a artificial scheme so + // that advagg_glue_url() will produce a proper absolute url. That will work + // with drupal_http_request(). + if (!isset($parts['scheme']) && substr($url, 0, 2) == '//') { + global $base_url; + $parts['scheme'] = @parse_url($base_url, PHP_URL_SCHEME); + } + // Pass along user/pass in the URL. + $advagg_auth_basic_user = variable_get('advagg_auth_basic_user', ADVAGG_AUTH_BASIC_USER); + if (module_exists('shield')) { + $parts['user'] = variable_get('shield_user', ''); + $parts['pass'] = variable_get('shield_pass', ''); + } + elseif (isset($_SERVER['AUTH_TYPE']) + && $_SERVER['AUTH_TYPE'] == 'Basic' + ) { + $parts['user'] = $_SERVER['PHP_AUTH_USER']; + $parts['pass'] = $_SERVER['PHP_AUTH_PW']; + } + elseif (isset($_SERVER['HTTP_AUTHORIZATION']) + && strpos($_SERVER['HTTP_AUTHORIZATION'], 'Basic ') === 0 + ) { + $user_pass = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 5))); + $parts['user'] = $user_pass[0]; + $parts['pass'] = $user_pass[1]; + } + elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) + && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Basic ') === 0 + ) { + $user_pass = explode(':', base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 5))); + $parts['user'] = $user_pass[0]; + $parts['pass'] = $user_pass[1]; + } + elseif (!empty($advagg_auth_basic_user)) { + $parts['user'] = $advagg_auth_basic_user; + $parts['pass'] = variable_get('advagg_auth_basic_pass', ADVAGG_AUTH_BASIC_PASS); + } + + if ($mod_url) { + if (function_exists('httprl_build_url_self')) { + // Remove the base_path from path. + $path = substr($parts['path'], strlen($GLOBALS['base_path'])); + $new_url = httprl_build_url_self($path); + } + else { + if (!empty($_SERVER['HTTP_HOST'])) { + if ($parts['host'] != $_SERVER['HTTP_HOST']) { + $parts['host'] = $_SERVER['HTTP_HOST']; + } + } + elseif (!empty($_SERVER['SERVER_NAME'])) { + if ($parts['host'] != $_SERVER['SERVER_NAME']) { + $parts['host'] = $_SERVER['SERVER_NAME']; + } + } + elseif (!empty($_SERVER['SERVER_ADDR'])) { + if ($parts['host'] != $_SERVER['SERVER_ADDR']) { + $parts['host'] = $_SERVER['SERVER_ADDR']; + } + } + else { + $parts['host'] = '127.0.0.1'; + } + } + } + + if (empty($new_url)) { + $new_url = advagg_glue_url($parts); + } + $url = $new_url; +} + +/** + * Checks to see if an apache module is enabled. + * + * @param string $mod + * Name of an apache module. + * + * @return bool + * TRUE if it exists; FALSE if it does not; NULL if it can not be determined. + */ +function advagg_install_apache_mod_loaded($mod) { + $sapi_type = php_sapi_name(); + if (substr($sapi_type, 0, 3) == 'cgi' || $sapi_type == 'fpm-fcgi') { + // NULL returned, apache_get_modules and phpinfo can not be called. + return NULL; + } + if (is_callable('apache_get_modules')) { + $mods = apache_get_modules(); + if (in_array($mod, $mods)) { + // Return TRUE, module exists. + return TRUE; + } + } + elseif (is_callable('phpinfo') && FALSE === strpos(ini_get('disable_functions'), 'phpinfo')) { + // Use static so we don't run phpinfo multiple times. + $phpinfo = &drupal_static(__FUNCTION__); + if (empty($phpinfo)) { + // Use phpinfo to get the info if apache_get_modules doesn't exist. + ob_start(); + phpinfo(8); + $phpinfo = ob_get_clean(); + } + if (FALSE !== strpos($phpinfo, $mod)) { + // Return TRUE, module exists. + return TRUE; + } + } + else { + // NULL returned, apache_get_modules and phpinfo can not be called. + return NULL; + } + return FALSE; +} + +/** + * Convert the table to the specified collation. + * + * @param string $table_name + * Perform the operation on this table. + * @param array $fields + * An array of field names. + * @param string $collation + * The db collation to change to table columns to. + * @param array $schema_fields + * An array of field definitions. + * + * @return array + * Returns an array of tables and column names. + */ +function advagg_install_change_table_collation($table_name, array $fields, $collation, array $schema_fields) { + $db_type = Database::getConnection()->databaseType(); + // Skip if not MySQL. + if ($db_type !== 'mysql') { + return FALSE; + } + $table_name = Database::getConnection()->prefixTables('{' . db_escape_table($table_name) . '}'); + $results = db_query("SHOW FULL FIELDS FROM $table_name")->fetchAllAssoc('Field'); + $db_schema = Database::getConnection()->schema(); + foreach ($results as $row) { + if (!in_array($row->Field, $fields)) { + continue; + } + + $charset = strtolower(substr($collation, 0, strpos($collation, '_'))); + $query = "ALTER TABLE $table_name CHANGE `{$row->Field}` `{$row->Field}` {$row->Type}"; + $query .= " CHARACTER SET $charset COLLATE $collation"; + + if (isset($schema_fields[$row->Field]['not null'])) { + if ($schema_fields[$row->Field]['not null']) { + $query .= ' NOT NULL'; + } + else { + $query .= ' NULL'; + } + } + + // $schema_fields[$row->Field]['default'] can be NULL, so we explicitly + // check for the key here. + if (isset($schema_fields[$row->Field]) + && is_array($schema_fields[$row->Field]) + && array_key_exists('default', $schema_fields[$row->Field]) + ) { + $default = $schema_fields[$row->Field]['default']; + if (is_string($default)) { + $default = "'" . $default . "'"; + } + elseif (!isset($default)) { + $default = 'NULL'; + } + $query .= ' DEFAULT ' . $default; + } + + if (empty($schema_fields[$row->Field]['not null']) && !isset($schema_fields[$row->Field]['default'])) { + $query .= ' DEFAULT NULL'; + } + + // Add column comment. + if (!empty($schema_fields[$row->Field]['description'])) { + $query .= ' COMMENT ' . $db_schema->prepareComment($schema_fields[$row->Field]['description'], 255); + } + + db_query($query); + } +} + +/** + * Callback to delete files if size == 0 and modified more than 60 seconds ago. + * + * @param string $uri + * Location of the file to check. + */ +function advagg_install_delete_empty_file_if_stale($uri) { + // Set stale file threshold to 60 seconds. + if (filesize($uri) < 3 && REQUEST_TIME - filemtime($uri) > 60) { + file_unmanaged_delete($uri); + } +} + +/** + * Callback to delete files if size == 0 and modified more than 60 seconds ago. + * + * @param string $has_string + * If the .htaccess file contains this string it will be removed and + * recreated. + * @param string $does_not_have_string + * If the .htaccess file does not contains this string it will be removed & + * recreated. + * + * @return string + * Translated string indicating what was done. + */ +function advagg_install_update_htaccess($has_string = '', $does_not_have_string = '') { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Check for old .htaccess files. + $something_done = FALSE; + foreach ($files as $type => $uri) { + if (!file_exists($uri)) { + unset($files[$type]); + continue; + } + $contents = advagg_file_get_contents($uri); + // Remove old .htaccess file if it has this string. + if (!empty($has_string) && strpos($contents, $has_string) !== FALSE) { + drupal_unlink($uri); + $something_done = TRUE; + } + // Remove old .htaccess file if it does not have this string. + if (!empty($does_not_have_string) && strpos($contents, $does_not_have_string) === FALSE) { + drupal_unlink($uri); + $something_done = TRUE; + } + } + + // Create the new .htaccess file. + $new_htaccess = FALSE; + if (!empty($files) && $something_done && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + $new_htaccess = TRUE; + } + } + + // Output info. + if ($something_done) { + if ($new_htaccess) { + return t('Removed the old .htaccess file and put in a new one.'); + } + else { + return t('Removed the old .htaccess file.'); + } + } + else { + return t('Nothing needed to be done.'); + } +} + +/** + * See if the .htaccess file uses the RewriteBase directive. + * + * @param string $type + * Either css or js. + * + * @return bool + * FALSE if the ErrorDocument 404 statement is incorrect. + */ +function advagg_install_htaccess_errordocument($type) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type === 'css') { + $location = $css_path[1] . '/.htaccess'; + } + if ($type === 'js') { + $location = $js_path[1] . '/.htaccess'; + } + $good = TRUE; + + // Get the location of the 404 error doc. + if (is_readable($location)) { + $htaccess = advagg_file_get_contents($location); + $matches = array(); + $found = preg_match_all('/\\n\s*ErrorDocument\s*404\s*(.*)/i', $htaccess, $matches); + if ($found && !empty($matches[0])) { + $matches[1] = array_map('trim', $matches[1]); + $location = array_pop($matches[1]); + } + } + else { + return $good; + } + // If it's pointing to the wrong place or doesn't exist return FALSE. + if (!empty($location) && $location !== "{$GLOBALS['base_path']}index.php") { + $good = FALSE; + } + if (empty($location) && $GLOBALS['base_path'] !== '/') { + $good = FALSE; + } + + return $good; +} + +/** + * Create proxy settings. + * + * @return string + * Apache httpd.conf settings for the s3fs module. + */ +function advagg_install_s3fs_proxy_settings() { + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $position = strpos($css_path[0], '://'); + $dir = ''; + if ($position !== FALSE) { + $dir = substr($css_path[0], $position + 3); + } + $css_target = str_replace($dir, '', file_create_url($css_path[0])); + $position = strpos($js_path[0], '://'); + $dir = ''; + if ($position !== FALSE) { + $dir = substr($js_path[0], $position + 3); + } + $js_target = str_replace($dir, '', file_create_url($js_path[0])); + $scheme = parse_url($js_target, PHP_URL_SCHEME); + + $config = ''; + $extra = ''; + if ($scheme === 'http') { + $port = '80'; + } + elseif ($scheme === 'https') { + $port = '443'; + $extra = " SSLProxyEngine on\n"; + } + $config .= "<VirtualHost *:$port>\n"; + $config .= " ProxyRequests Off\n"; + $config .= $extra; + $config .= " <Proxy *>\n"; + $config .= " Order deny,allow\n"; + $config .= " Allow from all\n"; + $config .= " </Proxy>\n"; + $config .= " ProxyTimeout 4\n"; + $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; + $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; + $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; + $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; + $config .= " ProxyErrorOverride On\n"; + $config .= " ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; + $config .= "</VirtualHost>\n"; + + return $config; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.make.example b/frontend/drupal/sites/all/modules/advagg/advagg.make.example new file mode 100644 index 000000000..1971310e1 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.make.example @@ -0,0 +1,50 @@ +; Rename this file to advagg.make to have it included within a drush make +; command. + +api = 2 +core = 7.x + +; Contrib module: libraries +; --------------------------------------- +projects[libraries][version] = "2" + +; JavaScript libraries +; --------------------------------------- + +; Library: CSSLint. +libraries[csslint][download][type] = git +libraries[csslint][download][url] = https://github.com/CSSLint/csslint + +; Library: fontfaceobserver. +libraries[fontfaceobserver][download][type] = git +libraries[fontfaceobserver][download][url] = https://github.com/bramstein/fontfaceobserver.git + +; Library: jshint. +libraries[jshint][download][type] = git +libraries[jshint][download][url] = https://github.com/jshint/jshint + +; Library: JShrink. +libraries[JShrink][download][type] = git +libraries[JShrink][download][url] = https://github.com/tedious/JShrink + +; Library: JSMinPlus. +libraries[jsminplus][download][type] = git +libraries[jsminplus][download][url] = https://github.com/JSMinPlus/JSMinPlus +libraries[jsminplus][directory_name] = "jsminplus" + +; Library: jspacker. +libraries[jspacker][download][type] = git +libraries[jspacker][download][url] = https://github.com/tholu/php-packer +libraries[jspacker][directory_name] = "jspacker" + +; Library: jsqueeze. +libraries[jsqueeze][download][type] = git +libraries[jsqueeze][download][url] = https://github.com/tchwork/jsqueeze + +; Library: loadCSS. +libraries[loadCSS][download][type] = git +libraries[loadCSS][download][url] = https://github.com/filamentgroup/loadCSS + +; Library: YUI-CSS-compressor-PHP-port. +libraries[YUI-CSS-compressor-PHP-port][download][type] = git +libraries[YUI-CSS-compressor-PHP-port][download][url] = https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.missing.inc b/frontend/drupal/sites/all/modules/advagg/advagg.missing.inc new file mode 100644 index 000000000..2bd1dddbd --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.missing.inc @@ -0,0 +1,1657 @@ +<?php + +/** + * @file + * Advanced CSS/JS aggregation module. + * + * Functions used to generate a file given the filename. + */ + +/** + * Menu Callback; generates a missing CSS/JS file. + */ +function advagg_missing_aggregate($input = '') { + // Do not stop processing this request. + ignore_user_abort(TRUE); + + // Generate missing file. + $msg = advagg_missing_generate($input); + + if (module_exists('jquery_update')) { + $arg = arg(); + $filename = array_pop($arg); + $filename = explode('?', $filename); + $filename = array_shift($filename); + if (strpos($filename, 'min.map') !== FALSE && strpos($filename, 'jquery') !== FALSE) { + // Get filename from request. + $wrong_pattern = t('Wrong pattern.'); + if ($msg === $wrong_pattern) { + $version = variable_get('jquery_update_jquery_version', '1.10'); + $trueversion = '1.9.1'; + switch ($version) { + case '1.9': + $trueversion = '1.9.1'; + break; + + case '1.10': + $trueversion = '1.10.2'; + break; + + case '1.11': + $trueversion = '1.11.2'; + break; + + case '2.1': + $trueversion = '2.1.4'; + break; + } + $url = "https://cdn.jsdelivr.net/gh/jquery/jquery@$trueversion/jquery.min.map"; + drupal_goto($url, array('external' => TRUE), 301); + } + } + } + + // If here send out fast 404. + advagg_missing_fast404($msg); +} + +/** + * Generates a missing CSS/JS file and send it to client. + * + * @return string + * text if bundle couldn't be generated. + */ +function advagg_missing_generate($input = '') { + // Make sure we are not in a redirect loop. + $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; + if ($redirect_counter > 5) { + watchdog('advagg', 'This request could not generate correctly. Loop detected. Request data: %info', array('%info' => $_GET['q'])); + return t('In a loop.'); + } + + // Get filename from request. + $arg = arg(); + $filename = array_pop($arg); + $filename = explode('?', $filename); + $filename = array_shift($filename); + + // Quit ASAP if filename does not match the AdvAgg pattern. + $data = advagg_get_hashes_from_filename($filename); + if (is_string($data) || !is_array($data)) { + // Try again with the function input. + $filename = $input; + $data1 = advagg_get_hashes_from_filename($filename); + if (is_string($data) || !is_array($data)) { + return "$data $data1"; + } + else { + $data = $data1; + } + } + + // Check to see if the file exists. + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($data[0] === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($data[0] === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + + if (file_exists($uri) && filesize($uri) >= 0) { + // File does exist and filesize is bigger than zero, 307 to it. + $uri = advagg_generate_location_uri($filename, $data[0], $data[3]); + ++$redirect_counter; + $uri .= '?redirect_counter=' . $redirect_counter; + header('Location: ' . $uri, TRUE, 307); + exit(); + } + + // Get lock so only one process will do the work. + $lock_name = 'advagg_' . $filename; + $uri = $GLOBALS['base_path'] . $_GET['q']; + $created = FALSE; + $files_to_save = array(); + if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { + $return = advagg_missing_create_file($filename, FALSE, $data); + if (!is_array($return)) { + return $return; + } + else { + list($files_to_save, $type) = $return; + $created = TRUE; + } + } + elseif (lock_acquire($lock_name, 10) || $redirect_counter > 4) { + if ($redirect_counter > 4) { + $return = advagg_missing_create_file($filename, TRUE, $data); + } + else { + $return = advagg_missing_create_file($filename, FALSE, $data); + } + lock_release($lock_name); + if (!is_array($return)) { + return $return; + } + else { + list($files_to_save, $type) = $return; + $created = TRUE; + } + } + else { + // Wait for another request that is already doing this work. + // We choose to block here since otherwise the router item may not + // be available in menu_execute_active_handler() resulting in a 404. + lock_wait($lock_name, 10); + if (file_exists($uri) && filesize($uri) > 0) { + $type = $data[0]; + $created = TRUE; + } + } + + // Redirect and try again on failure. + if (empty($created)) { + $uri = advagg_generate_location_uri($filename, $data[0], $data[3]); + ++$redirect_counter; + $uri .= '?redirect_counter=' . $redirect_counter; + header('Location: ' . $uri, TRUE, 307); + exit(); + } + + if ($redirect_counter > 4) { + watchdog('advagg', 'One of the alter hooks failed when generating this file: %uri. Thus this file was created without any alter hooks.', array('%uri' => $uri), WATCHDOG_ERROR); + } + + // Output file's contents if creating the file was successful. + // This function will call exit. + advagg_missing_send_saved_file($files_to_save, $uri, $created, $filename, $type, $redirect_counter, $data[3]); +} + +/** + * Given the filename, type, and settings, create absolute URL for 307 redirect. + * + * Due to url inbound alter we can not trust that the redirect will work if + * using $GLOBALS['base_path'] . $_GET['q']. Generate the uri as if it was + * going to be embedded in the html. + * + * @param string $filename + * Just the filename no path information. + * @param string $type + * String: css or js. + * @param array $aggregate_settings + * Array of settings. + * + * @return string + * String pointing to the URI of the file. + */ +function advagg_generate_location_uri($filename, $type, array $aggregate_settings = array()) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type === 'css') { + $uri_307 = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri_307 = $js_path[0] . '/' . $filename; + } + + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + + // 307s need to be absolute. RFC 2616 14.30. + $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] = FALSE; + $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; + + // Make advagg_context_switch available. + module_load_include('inc', 'advagg', 'advagg'); + advagg_context_switch($aggregate_settings, 0); + $uri_307 = advagg_file_create_url($uri_307, $aggregate_settings); + advagg_context_switch($aggregate_settings, 1); + + return $uri_307; +} + +/** + * Send the css/js file to the client. + * + * @param array $files_to_save + * Array of filenames and data. + * @param string $uri + * Requested filename. + * @param bool $created + * If file was created in a different thread. + * @param string $filename + * Just the filename no path information. + * @param string $type + * String: css or js. + * @param array $aggregate_settings + * Array of settings. Used to generate the 307 redirect location. + */ +function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $filename, $type, $redirect_counter, array $aggregate_settings = array()) { + // Send a 304 if this is a repeat request. + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= REQUEST_TIME) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } + + $return_compressed_br = FALSE; + $return_compressed_gz = FALSE; + // Negotiate whether to use gzip compression. + if (!empty($_SERVER['HTTP_ACCEPT_ENCODING'])) { + if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'br') !== FALSE) { + $return_compressed_br = TRUE; + } + if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { + $return_compressed_gz = TRUE; + } + } + header('Vary: Accept-Encoding', FALSE); + + if (!empty($created)) { + if ($return_compressed_br && file_exists($uri . '.br') && filesize($uri . '.br') > 0) { + $uri .= '.br'; + } + elseif ($return_compressed_gz && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) { + $uri .= '.gz'; + } + if (!isset($files_to_save[$uri]) && file_exists($uri) && filesize($uri)) { + // Do not use advagg_file_get_contents here. + $files_to_save[$uri] = (string) @file_get_contents($uri); + } + } + + // Make sure zlib.output_compression does not compress the output. + ini_set('zlib.output_compression', '0'); + header('Vary: Accept-Encoding', FALSE); + // Clear the output buffer. + if (ob_get_level()) { + ob_end_clean(); + } + // Set generic far future headers. + if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { + advagg_missing_set_farfuture_headers(); + } + // Return compressed content if we can. + if ($return_compressed_br || $return_compressed_gz) { + foreach ($files_to_save as $uri => $data) { + // See if this uri contains .br near the end of it. + $pos = strripos($uri, '.br', 91 + strlen(ADVAGG_SPACE) * 3); + if (!empty($pos)) { + $len = strlen($uri); + if ($pos == $len - 3) { + // .br file exists, send it out. + header('Content-Encoding: br'); + break; + } + } + + // See if this uri contains .gz near the end of it. + $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); + if (!empty($pos)) { + $len = strlen($uri); + if ($pos == $len - 3) { + // .gz file exists, send it out. + header('Content-Encoding: gzip'); + break; + } + } + } + } + else { + $data = trim(reset($files_to_save)); + } + + // Output file and exit. + if (!empty($data)) { + $strlen = strlen($data); + // Send a 304 if this is a repeat request. + if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + $etags = explode(' ', $_SERVER['HTTP_IF_NONE_MATCH']); + if ($etags[0] < REQUEST_TIME + 31 * 24 * 60 * 60 + && isset($etags[1]) + && $etags[1] == $strlen + ) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } + } + + // Send out a 200 OK status. + $default = ADVAGG_HTTP_200_CODE; + if (module_exists('httprl') + && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) + && (is_callable('httprl_is_background_callback_capable') + && httprl_is_background_callback_capable() + || !is_callable('httprl_is_background_callback_capable') + ) + ) { + // Use 203 instead of 200 if HTTPRL is being used. + $default = 203; + } + $number = variable_get('advagg_http_200_code', $default); + header("{$_SERVER['SERVER_PROTOCOL']} $number OK"); + + // Insure the Last-Modified header is set so 304's work correctly. + if (file_exists($uri) && $filemtime = @filemtime($uri)) { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', $filemtime)); + // Etags generation in php is broken due to millisecond precision for the + // files mtime; apache has it, php does not. + } + else { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME)); + } + // Set the Expires date 1 month into the future. + if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { + header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 31 * 24 * 60 * 60)); + } + // Also send an etag out. + header('Etag: ' . REQUEST_TIME . ' ' . $strlen); + + if ($type === 'css') { + header("Content-Type: text/css"); + } + elseif ($type === 'js') { + header("Content-Type: application/javascript; charset=UTF-8"); + } + header('X-AdvAgg: Generated file at ' . REQUEST_TIME); + + print $data; + exit(); + } + else { + // Redirect and try again on failure. + $uri = advagg_generate_location_uri($filename, $type, $aggregate_settings); + ++$redirect_counter; + $uri .= '?redirect_counter=' . $redirect_counter; + header('Location: ' . $uri, TRUE, 307); + exit(); + } +} + +/** + * Set various headers so the browser will cache the file for a long time. + */ +function advagg_missing_set_farfuture_headers() { + // Hat tip to the CDN module for the far future headers. + // + // Browsers that implement the W3C Access Control specification might refuse + // to use certain resources such as fonts if those resources violate the + // same-origin policy. Send a header to explicitly allow cross-domain use of + // those resources. This is called Cross-Origin Resource Sharing, or CORS. + header("Access-Control-Allow-Origin: *"); + // Remove all previously set Cache-Control headers, because we're going to + // override it. Since multiple Cache-Control headers might have been set, + // simply setting a new, overriding header isn't enough: that would only + // override the *last* Cache-Control header. Yay for PHP! + if (function_exists('header_remove')) { + header_remove('Cache-Control'); + header_remove('ETag'); + header_remove('Set-Cookie'); + } + else { + header('Cache-Control:'); + header('Cache-Control:'); + header('ETag:'); + header('ETag:'); + header('Set-Cookie:'); + header('Set-Cookie:'); + } + // Set a far future Cache-Control header (52 weeks), which prevents + // intermediate caches from transforming the data and allows any + // intermediate cache to cache it, since it's marked as a public resource. + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + header('Cache-Control: max-age=31449600, no-transform, public, immutable'); + } + else { + header('Cache-Control: max-age=31449600, no-transform, public'); + } +} + +/** + * Given a filename create that file. + * + * @param string $filename + * Just the filename no path information. + * @param bool $no_alters + * (optional) Set to TRUE to do the bare amount of processing on the file. + * @param mixed $data + * (optional) Output from advagg_get_hashes_from_filename(). + * + * @return mixed + * On failure a string saying why it failed. + * On success the $files_to_save array. + */ +function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array()) { + // Option to still delever the file if fatal error. + register_shutdown_function("advagg_missing_fatal_handler", $filename); + + if (empty($data)) { + $data = advagg_get_hashes_from_filename($filename); + } + if (is_array($data)) { + list($type, $aggregate_filenames_hash, $aggregate_contents_hash, $aggregate_settings) = $data; + } + else { + return $data; + } + + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + + // Set no alters if this is the last chance of generating the aggregate. + if ($no_alters) { + $aggregate_settings['settings']['no_alters'] = TRUE; + } + + // Get a list of files. + $files = advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash); + if (empty($files)) { + return t('Hashes do not match database.'); + } + + // Save aggregate file. + list($files_to_save, $errors) = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); + // Update atime. + advagg_multi_update_atime(array( + array( + 'aggregate_filenames_hash' => $aggregate_filenames_hash, + 'aggregate_contents_hash' => $aggregate_contents_hash, + ), + )); + // Make sure .htaccess file exists in the advagg dir. + if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + advagg_htaccess_check_generate($files_to_save, $type); + } + + // Return data. + return array( + $files_to_save, + $type, + $aggregate_filenames_hash, + $aggregate_contents_hash, + $aggregate_settings, + $files, + $errors, + ); +} + +/** + * Generate .htaccess rules and place them in advagg dir. + * + * @param array $files_to_save + * Array of files that where saved. + * @param string $type + * String: css or js. + * @param bool $force + * (Optional) force recreate the .htaccess file. + * + * @return array + * Empty array if not errors happened, list of errors if the write had any + * issues. + */ +function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FALSE) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $content_type = $type; + if ($content_type === 'js') { + $content_type = 'javascript'; + $advagg_dir = basename($js_path[1]); + } + elseif ($content_type === 'css') { + $advagg_dir = basename($css_path[1]); + } + $type_upper = strtoupper($type); + $data = "\n"; + + // Some hosting companies do not allow "FollowSymLinks" but will support + // "SymLinksIfOwnerMatch". + if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH)) { + $data .= "Options +SymLinksIfOwnerMatch\n"; + } + else { + $data .= "Options +FollowSymLinks\n"; + } + if ($GLOBALS['base_path'] !== '/') { + $data .= "ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; + } + // See if RewriteBase is needed. + $advagg_htaccess_rewritebase = trim(variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE)); + if (!empty($advagg_htaccess_rewritebase) && !empty($advagg_dir)) { + $rewrite_base_rule = str_replace('//', '/', $advagg_htaccess_rewritebase . '/' . $advagg_dir); + $data .= "RewriteBase $rewrite_base_rule\n"; + } + + $data .= "\n"; + $data .= "<IfModule mod_rewrite.c>\n"; + $data .= " RewriteEngine on\n"; + $data .= " <IfModule mod_headers.c>\n"; + $data .= " # Serve brotli compressed ${type_upper} files if they exist and the client accepts br.\n"; + $data .= " RewriteCond %{HTTP:Accept-encoding} br\n"; + $data .= " RewriteCond %{REQUEST_FILENAME}\.br -s\n"; + $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.br [QSA]\n"; + if ($type === 'css') { + $data .= " RewriteRule \.${type}\.br$ - [T=text/${content_type},E=no-gzip:1]\n"; + } + else { + $data .= " RewriteRule \.${type}\.br$ - [T=application/${content_type},E=no-gzip:1]\n"; + } + $data .= "\n"; + $data .= " <FilesMatch \"\.${type}\.br$\">\n"; + $data .= " # Serve correct encoding type.\n"; + $data .= " Header set Content-Encoding br\n"; + $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; + $data .= " Header append Vary Accept-Encoding\n"; + $data .= " </FilesMatch>\n"; + $data .= "\n"; + $data .= " # Serve gzip compressed ${type_upper} files if they exist and the client accepts gzip.\n"; + $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n"; + $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n"; + $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.gz [QSA]\n"; + if ($type === 'css') { + $data .= " RewriteRule \.${type}\.gz$ - [T=text/${content_type},E=no-gzip:1]\n"; + } + else { + $data .= " RewriteRule \.${type}\.gz$ - [T=application/${content_type},E=no-gzip:1]\n"; + } + $data .= "\n"; + $data .= " <FilesMatch \"\.${type}\.gz$\">\n"; + $data .= " # Serve correct encoding type.\n"; + $data .= " Header set Content-Encoding gzip\n"; + $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; + $data .= " Header append Vary Accept-Encoding\n"; + $data .= " </FilesMatch>\n"; + $data .= " </IfModule>\n"; + $data .= "</IfModule>\n"; + $data .= "\n"; + $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz|\.br)?\">\n"; + $data .= " # No mod_headers. Apache module headers is not enabled.\n"; + $data .= " <IfModule !mod_headers.c>\n"; + $data .= " # No mod_expires. Apache module expires is not enabled.\n"; + $data .= " <IfModule !mod_expires.c>\n"; + $data .= " # Use ETags.\n"; + $data .= " FileETag MTime Size\n"; + $data .= " </IfModule>\n"; + $data .= " </IfModule>\n"; + $data .= "\n"; + $data .= " # Use Expires Directive if apache module expires is enabled.\n"; + $data .= " <IfModule mod_expires.c>\n"; + $data .= " # Do not use ETags.\n"; + $data .= " FileETag None\n"; + $data .= " # Enable expirations.\n"; + $data .= " ExpiresActive On\n"; + $data .= " # Cache all aggregated ${type} files for 52 weeks after access (A).\n"; + $data .= " ExpiresDefault A31449600\n"; + $data .= " </IfModule>\n"; + $data .= "\n"; + $data .= " # Use Headers Directive if apache module headers is enabled.\n"; + $data .= " <IfModule mod_headers.c>\n"; + $data .= " # Do not use etags for cache validation.\n"; + $data .= " Header unset ETag\n"; + $data .= " # Serve correct content type.\n"; + if ($type === 'css') { + $data .= " Header set Content-Type text/${content_type}\n"; + } + else { + $data .= " Header set Content-Type application/${content_type}\n"; + } + $data .= " <IfModule !mod_expires.c>\n"; + $data .= " # Set a far future Cache-Control header to 52 weeks.\n"; + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public, immutable\"\n"; + } + else { + $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n"; + } + $data .= " </IfModule>\n"; + $data .= " <IfModule mod_expires.c>\n"; + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $data .= " Header append Cache-Control \"no-transform, public, immutable\"\n"; + } + else { + $data .= " Header append Cache-Control \"no-transform, public\"\n"; + } + $data .= " </IfModule>\n"; + $data .= " </IfModule>\n"; + if ($type === 'css') { + $data .= " ForceType text/${content_type}\n"; + } + else { + $data .= " ForceType application/${content_type}\n"; + } + $data .= "</FilesMatch>\n"; + + $errors = array(); + foreach (array_keys($files_to_save) as $uri) { + $dir = dirname($uri); + $htaccess_file = $dir . '/.htaccess'; + if (!$force && file_exists($htaccess_file)) { + continue; + } + + $errors = advagg_save_data($htaccess_file, $data, $force); + } + return $errors; +} + +/** + * Given a filename return the type and 2 hashes. + * + * @param string $filename + * Just the filename no path information. + * @param bool $skip_hash_settings + * Allows for the skipping of db lookup for required file hooks. + * + * @return mixed + * On failure a string saying why it failed. + * On success array($ext, $aggregate_hash, $files_hash). + */ +function advagg_get_hashes_from_filename($filename, $skip_hash_settings = FALSE) { + // Verify requested filename has the correct pattern. + if (!advagg_match_file_pattern($filename)) { + return t('Wrong pattern.'); + } + + // Get the extension. + $ext = substr($filename, strpos($filename, '.', 131 + strlen(ADVAGG_SPACE) * 3) + 1); + + // Set extraction points. + if ($ext === 'css') { + $aggregate_filenames_start = 3 + strlen(ADVAGG_SPACE); + $aggregate_contents_start = 46 + strlen(ADVAGG_SPACE) * 2; + $hooks_hashes_start = 89 + strlen(ADVAGG_SPACE) * 3; + } + elseif ($ext === 'js') { + $aggregate_filenames_start = 2 + strlen(ADVAGG_SPACE); + $aggregate_contents_start = 45 + strlen(ADVAGG_SPACE) * 2; + $hooks_hashes_start = 88 + strlen(ADVAGG_SPACE) * 3; + } + else { + return t('Wrong file type.'); + } + + // Extract info from wanted filename. + $aggregate_filenames_hash = substr($filename, $aggregate_filenames_start, 43); + $aggregate_contents_hash = substr($filename, $aggregate_contents_start, 43); + $hooks_hashes_value = substr($filename, $hooks_hashes_start, 43); + + $aggregate_settings = array(); + if (!$skip_hash_settings) { + // Verify that the hooks hashes is valid. + $aggregate_settings = advagg_get_hash_settings($hooks_hashes_value); + if (empty($aggregate_settings)) { + if (!variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { + return t('Bad hooks hashes value.'); + } + elseif (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'File @filename has an empty aggregate_settings variable; the 3rd hash is incorrect.', array('@filename' => $filename), WATCHDOG_DEBUG); + } + } + } + + return array( + $ext, + $aggregate_filenames_hash, + $aggregate_contents_hash, + $aggregate_settings, + ); +} + +/** + * Get the files that belong inside of this aggregate. + * + * @param string $type + * String: css or js. + * @param string $aggregate_filenames_hash + * Hash of the groupings of files. + * @param string $aggregate_contents_hash + * Hash of the files contents. + * + * @return array + * List of files in the order they should be included. + */ +function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash) { + // Create main query for the advagg_aggregates_versions table. + $query = db_select('advagg_aggregates_versions', 'aav') + ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash) + ->condition('aav.aggregate_contents_hash', $aggregate_contents_hash); + // Create join query for the advagg_aggregates table. + $subquery_aggregates = $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = + aav.aggregate_filenames_hash AND aa.aggregate_filenames_hash = :aggregate_filenames_hash', + array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); + // Create join query for the advagg_files table. + $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND + af.filetype = :type AND af.filesize > 0', array(':type' => $type)); + // Select fields and ordering of the query; add in query comment as well. + $query = $query->fields('af', array('filename')) + ->fields($subquery_aggregates, array('settings')) + ->orderBy('porder', 'ASC'); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + // Add in files that are included in this aggregate. + $files = array(); + foreach ($results as $value) { + $files[$value->filename] = unserialize($value->settings); + } + + // Try again with weak file verification. + if (empty($files) && variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Filehash @filename of type @type has an aggregate_contents_hash variable; the 2rd hash is incorrect.', array( + '@filename' => $aggregate_filenames_hash, + '@type' => $type, + ), WATCHDOG_DEBUG); + } + + // Create main query for the advagg_aggregates_versions table. + $query = db_select('advagg_aggregates_versions', 'aav') + ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash); + // Create join query for the advagg_aggregates table. + $subquery_aggregates = $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = + aav.aggregate_filenames_hash AND aa.aggregate_filenames_hash = :aggregate_filenames_hash', + array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); + // Create join query for the advagg_files table. + $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND + af.filetype = :type AND af.filesize > 0', array(':type' => $type)); + // Select fields and ordering of the query; add in query comment as well. + $query = $query->fields('af', array('filename')) + ->fields($subquery_aggregates, array('settings')) + ->orderBy('porder', 'ASC'); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + // Add in files that are included in this aggregate. + $files = array(); + foreach ($results as $value) { + $files[$value->filename] = unserialize($value->settings); + } + } + return $files; +} + +/** + * Given a list of files, grab their contents and glue it into one big string. + * + * @param array $files + * Array of filenames. + * @param array $aggregate_settings + * Array of settings. + * @param string $aggregate_filename + * Filename of the aggregeate. + * + * @return string + * String containing all the files. + */ +function advagg_get_css_aggregate_contents(array $files, array $aggregate_settings, $aggregate_filename = '') { + $write_aggregate = TRUE; + // Check if CSS compression is enabled. + $optimize = TRUE; + if (!empty($aggregate_settings['settings']['no_alters'])) { + $optimize = FALSE; + } + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $optimize = FALSE; + } + + module_load_include('inc', 'advagg', 'advagg'); + $info_on_files = advagg_load_files_info_into_static_cache(array_keys($files)); + + $data = ''; + if (!empty($files)) { + $media_changes = FALSE; + $last_media = NULL; + foreach ($files as $settings) { + if (!isset($settings['media'])) { + continue; + } + if (is_null($last_media)) { + $last_media = $settings['media']; + continue; + } + if ($settings['media'] !== $last_media) { + $media_changes = TRUE; + break; + } + } + if ($media_changes) { + $global_file_media = 'all'; + } + else { + $global_file_media = $last_media; + } + + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Media_queries + $media_types = array( + 'all', + 'aural', + 'braille', + 'handheld', + 'print', + 'projection', + 'screen', + 'tty', + 'tv', + 'embossed', + ); + + $import_statements = array(); + module_load_include('inc', 'advagg', 'advagg'); + $original_settings = array($optimize, $aggregate_settings); + foreach ($files as $file => $settings) { + $media_changes = FALSE; + if (!isset($settings['media'])) { + $settings['media'] = ''; + } + + if ($settings['media'] !== $global_file_media) { + $media_changes = TRUE; + } + + list($optimize, $aggregate_settings) = $original_settings; + // Allow other modules to modify aggregate_settings optimize. + // Call hook_advagg_get_css_file_contents_pre_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_css_file_contents_pre', $file, $optimize, $aggregate_settings); + } + if (is_readable($file)) { + // Get the files contents. + $file_contents = (string) @advagg_file_get_contents($file); + // Get a hash of the file's contents. + $file_contents_hash = drupal_hash_base64($file_contents); + $cid = 'advagg:file:' . advagg_drupal_hash_base64($file); + if (empty($info_on_files[$cid]['content_hash'])) { + // If hash was not in the cache, get it from the DB. + $results = db_select('advagg_files', 'af') + ->fields('af', array('content_hash', 'filename_hash')) + ->condition('filename', $file) + ->execute(); + foreach ($results as $row) { + $info_on_files['advagg:file:' . $row->filename_hash]['content_hash'] = $row->content_hash; + } + } + if (isset($info_on_files[$cid]) == FALSE || $info_on_files[$cid]['content_hash'] !== $file_contents_hash) { + // If the content hash doesn't match don't write the file. + $write_aggregate = advagg_missing_file_not_readable($file, $aggregate_filename, FALSE); + } + $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings, $file_contents); + } + else { + // File is not readable. + $write_aggregate = advagg_missing_file_not_readable($file, $aggregate_filename, TRUE); + } + + // Allow other modules to modify this files contents. + // Call hook_advagg_get_css_file_contents_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_css_file_contents', $contents, $file, $aggregate_settings); + } + + if ($media_changes) { + $media_blocks = advagg_parse_media_blocks($contents); + $contents = ''; + + $file_has_type = FALSE; + if (!empty($settings['media'])) { + foreach ($media_types as $media_type) { + if (stripos($settings['media'], $media_type) !== FALSE) { + $file_has_type = TRUE; + break; + } + } + } + + foreach ($media_blocks as $css_rules) { + if (strpos($css_rules, '@media') !== FALSE) { + // Get start and end of the rules for this media query block. + $start = strpos($css_rules, '{'); + if ($start === FALSE) { + continue; + } + $end = strrpos($css_rules, '}'); + if ($end === FALSE) { + continue; + } + + // Get current media queries for this media block. + $media_rules = substr($css_rules, 6, $start - 6); + // Get everything else besides top level media query. + $css_selectors_rules = substr($css_rules, $start + 1, $end - ($start + 1)); + + // Add in main media rule if needed. + if (!empty($settings['media']) + && strpos($media_rules, $settings['media']) === FALSE + && $settings['media'] !== $global_file_media + ) { + $rule_has_type = FALSE; + if ($file_has_type) { + foreach ($media_types as $media_type) { + if (stripos($media_rules, $media_type) !== FALSE) { + $rule_has_type = TRUE; + break; + } + } + } + if (!$rule_has_type) { + $media_rules = $settings['media'] . ' and ' . $media_rules; + } + } + } + else { + $media_rules = $settings['media']; + $css_selectors_rules = $css_rules; + } + $media_rules = trim($media_rules); + + // Pul all @font-face defentions inside the @media declaration above. + $font_face_string = ''; + $font_blocks = advagg_parse_media_blocks($css_selectors_rules, '@font-face'); + $css_selectors_rules = ''; + foreach ($font_blocks as $rules) { + if (strpos($rules, '@font-face') !== FALSE) { + $font_face_string .= "\n {$rules}"; + } + else { + $css_selectors_rules .= $rules; + } + } + $css_selectors_rules = str_replace("\n", "\n ", $css_selectors_rules); + $font_face_string = str_replace("\n", "\n ", $font_face_string); + + // Wrap css in dedicated media query if it differs from the global + // media query and there actually are media rules. + if (!empty($media_rules) && $media_rules !== $global_file_media) { + $output = "{$font_face_string} \n@media {$media_rules} {\n {$css_selectors_rules} \n}"; + } + else { + $output = "{$font_face_string} \n {$css_selectors_rules}"; + } + + $contents .= trim($output); + } + + } + // Per the W3C specification at + // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules + // must proceed any other style, so we move those to the top. + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $contents, $matches); + $contents = preg_replace($regexp, '', $contents); + // Add the import statements with the media query of the current file. + $import_media = isset($settings['media']) ? $settings['media'] : ''; + $import_media = trim($import_media); + $import_statements[] = array($import_media, $matches[0]); + + // Close any open comment blocks. + $contents .= "\n/*})'\"*/\n"; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $contents .= "\n/* Above code came from $file */\n\n"; + } + + $data .= $contents; + } + + // Add import statements to the top of the stylesheet. + $import_string = ''; + foreach ($import_statements as $values) { + if ($media_changes) { + foreach ($values[1] as $statement) { + $import_string .= str_replace(';', $values[0] . ';', $statement); + } + } + else { + $import_string .= implode('', $values[1]); + } + } + $data = $import_string . $data; + } + + // Allow other modules to modify this aggregates contents. + // Call hook_advagg_get_css_aggregate_contents_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_css_aggregate_contents', $data, $files, $aggregate_settings); + } + return array($data, $write_aggregate); +} + +/** + * Given a list of files, grab their contents and glue it into one big string. + * + * @param array $files + * Array of filenames. + * @param array $aggregate_settings + * Array of settings. + * @param string $aggregate_filename + * Filename of the aggregeate. + * + * @return string + * String containing all the files. + */ +function advagg_get_js_aggregate_contents(array $files, array $aggregate_settings, $aggregate_filename = '') { + $write_aggregate = TRUE; + $data = ''; + + module_load_include('inc', 'advagg', 'advagg'); + $info_on_files = advagg_load_files_info_into_static_cache(array_keys($files)); + + if (!empty($files)) { + // Build aggregate JS file. + foreach ($files as $filename => $settings) { + $contents = ''; + // Append a ';' and a newline after each JS file to prevent them from + // running together. Also close any comment blocks. + if (is_readable($filename)) { + $file_contents = (string) @advagg_file_get_contents($filename); + $file_contents_hash = drupal_hash_base64($file_contents); + $cid = 'advagg:file:' . advagg_drupal_hash_base64($filename); + if (empty($info_on_files[$cid]['content_hash'])) { + $results = db_select('advagg_files', 'af') + ->fields('af', array('content_hash', 'filename_hash')) + ->condition('filename', $filename) + ->execute(); + foreach ($results as $row) { + $info_on_files['advagg:file:' . $row->filename_hash]['content_hash'] = $row->content_hash; + } + } + if (isset($info_on_files[$cid]['content_hash']) && $info_on_files[$cid]['content_hash'] !== $file_contents_hash) { + // If the content hash doesn't match don't write the file. + $write_aggregate = advagg_missing_file_not_readable($filename, $aggregate_filename, FALSE); + } + + // Make sure that the file is ended properly. + $file_contents .= "\n;/*})'\"*/\n"; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $file_contents .= "/* Above code came from $filename */\n\n"; + } + $contents .= $file_contents; + } + else { + // File is not readable. + $write_aggregate = advagg_missing_file_not_readable($filename, $aggregate_filename, TRUE); + } + // Allow other modules to modify this files contents. + // Call hook_advagg_get_js_file_contents_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings); + } + // Make sure that the file is ended properly. + if (!empty($contents)) { + $contents .= ";/*})'\"*/\n"; + } + $data .= $contents; + } + } + + // Allow other modules to modify this aggregates contents. + // Call hook_advagg_get_js_aggregate_contents_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_js_aggregate_contents', $data, $files, $aggregate_settings); + } + return array($data, $write_aggregate); +} + +/** + * Let other modules know that this file couldn't be found. + * + * @param string $filename + * Filename of the missing file. + * @param string $aggregate_filename + * Filename of the aggregate that is trying to be generated. + * @param bool $fs_read_failure + * Set to TRUE if the file system couldn't be read. + */ +function advagg_missing_file_not_readable($filename, $aggregate_filename = '', $fs_read_failure = FALSE) { + $write_aggregate = FALSE; + $config_path = advagg_admin_config_root_path(); + list($css_path, $js_path) = advagg_get_root_files_dir(); + + // Get cache of this report. + $cid = 'advagg:file_issue:' . drupal_hash_base64($filename); + $cache = cache_get($cid, 'cache_advagg_info'); + + // Let other modules know about this missing file. + // Call hook_advagg_missing_root_file(). + module_invoke_all('advagg_missing_root_file', $aggregate_filename, $filename, $cache); + + // Report to watchdog if this is not cached and it does not start in the + // public dir and the advagg dirs. + if (empty($cache) + && strpos($filename, 'public://') !== 0 + && strpos($filename, $css_path[1]) !== 0 + && strpos($filename, $js_path[1]) !== 0 + ) { + if ($fs_read_failure) { + watchdog('advagg', 'Reading from the file system failed. This can sometimes happen during a deployment and/or a clear cache operation. Filename: %file Aggregate Filename: %aggregate. If this continues to happen go to the <a href="@operations">Operations page</a> and under Drastic Measures - Reset the AdvAgg Files table click the Truncate advagg_files button.', array( + '%file' => $filename, + '%aggregate' => $aggregate_filename, + '@operations' => url('admin/config/development/performance/advagg/operations', array('fragment' => 'edit-reset-advagg-files')), + ), WATCHDOG_WARNING); + } + else { + watchdog('advagg', 'The content hash for %file does not match the stored content hash from the database. Please <a href="@url">flush the advagg cache</a> under Smart Cache Flush. This can sometimes happen during a deployment. Filename: %file Aggregate Filename: %aggregate', array( + '%file' => $filename, + '%aggregate' => $aggregate_filename, + '@url' => url($config_path . '/advagg/operations', array( + 'fragment' => 'edit-smart-flush', + )), + ), WATCHDOG_WARNING); + } + cache_set($cid, TRUE, 'cache_advagg_info', CACHE_TEMPORARY); + } + elseif (!empty($cache) && $cache->created < (REQUEST_TIME - variable_get('advagg_file_read_failure_timeout', ADVAGG_FILE_READ_FAILURE_TIMEOUT))) { + // Write the aggregate if it's been in a failure state for over 30 minutes. + $write_aggregate = TRUE; + } + return $write_aggregate; +} + +/** + * Save an aggregate given a filename, the files included in it, and the type. + * + * @param string $filename + * Just the filename no path information. + * @param array $files + * Array of filenames. + * @param string $type + * String: css or js. + * @param array $aggregate_settings + * Array of settings. + * + * @return array + * array($files_to_save, $errors). + */ +function advagg_save_aggregate($filename, array $files, $type, array $aggregate_settings = array()) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $uri = ''; + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + + // Allow other modules to alter the location, files included, and settings. + if (empty($aggregate_settings['settings']['no_alters'])) { + // Call hook_advagg_save_aggregate_pre_alter(). + drupal_alter('advagg_save_aggregate_pre', $uri, $files, $aggregate_settings); + } + + // Build the aggregates contents. + $contents = ''; + if ($type === 'css') { + list($contents, $write_aggregate) = advagg_get_css_aggregate_contents($files, $aggregate_settings, $filename); + } + elseif ($type === 'js') { + list($contents, $write_aggregate) = advagg_get_js_aggregate_contents($files, $aggregate_settings, $filename); + } + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $contents = "/* This aggregate contains the following files:\n" . implode(",\n", array_keys($files)) . ". */\n\n" . $contents; + } + + // List of files to save. + $files_to_save = array( + $uri => $contents, + ); + + // Allow other modules to alter the contents and add new files to save. + // Call hook_advagg_save_aggregate_alter(). + $other_parameters = array($files, $type); + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings, $other_parameters); + } + + $errors = array(); + if ($write_aggregate) { + foreach ($files_to_save as $uri => $data) { + $errors = advagg_save_data($uri, $data); + if (!file_exists($uri) || filesize($uri) == 0) { + if ($type === 'css') { + $full_dir = DRUPAL_ROOT . '/' . $css_path[1]; + } + elseif ($type === 'js') { + $full_dir = DRUPAL_ROOT . '/' . $js_path[1]; + } + $free_space = @disk_free_space($full_dir); + if ($free_space !== FALSE && strlen($data) > $free_space) { + watchdog('advagg', 'Write to file system failed. Disk is full. %uri. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), WATCHDOG_ALERT); + } + elseif (!is_writable($full_dir)) { + watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), WATCHDOG_ERROR); + } + else { + watchdog('advagg', 'Write to file system failed. %uri. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), WATCHDOG_ERROR); + } + // If the file is empty, remove it. Serving via drupal is better than an + // empty aggregate being served. + if (file_exists($uri) && filesize($uri) == 0) { + @unlink($uri); + } + } + } + } + return array($files_to_save, $errors); +} + +/** + * Save data to a file. + * + * This will use the rename operation ensuring atomic file operations. + * + * @param string $uri + * A string containing the destination location. This must be a stream wrapper + * URI. + * @param string $data + * A string containing the contents of the file. + * @param bool $overwrite + * (optional) Bool, set to TRUE to overwrite a file. + * + * @return array + * Empty array if not errors happened, list of errors if the write had any + * issues. + */ +function advagg_save_data($uri, $data, $overwrite = FALSE) { + $t = get_t(); + $errors = array(); + // Clear the stat cache. + module_load_include('inc', 'advagg', 'advagg'); + advagg_clearstatcache($uri); + + // Prepare dir if needed. + $dir = dirname($uri); + $dir_good = file_prepare_directory($dir, FILE_CREATE_DIRECTORY); + if (!$dir_good) { + $errors[1] = $t('The directory for @file can not be created or is not writable.', array('@file' => $uri)); + return $errors; + } + + // File already exists. + if (!$overwrite && file_exists($uri) && filesize($uri) > 0) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'File @uri exists and overwrite is false.', array('@uri' => $uri), WATCHDOG_DEBUG); + } + $errors[2] = $t('File (@file) already exits.', array('@file' => $uri)); + return $errors; + } + + // If data is empty, write a space. + if (empty($data)) { + $data = ' '; + } + + // Perform the replace operation. Since there could be multiple processes + // writing to the same file, the best option is to create a temporary file in + // the same directory and then rename it to the destination. A temporary file + // is needed if the directory is mounted on a separate machine; thus ensuring + // the rename command stays local and atomic. + // + // Get a temporary filename in the destination directory. + $dir = $uri_dir = drupal_dirname($uri) . '/'; + + // Corect the bug with drupal_tempnam where it doesn't pass subdirs to + // tempnam() if the dir is a stream wrapper. + $scheme = file_uri_scheme($uri_dir); + if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { + $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); + if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { + $wrapper_dir_path = $wrapper->getDirectoryPath(); + if (!empty($wrapper_dir_path)) { + $dir = $wrapper_dir_path . '/' . substr($uri_dir, strlen($scheme . '://')); + $uri = $dir . substr($uri, strlen($uri_dir)); + } + } + } + + // Get the extension of the original filename and append it to the temp file + // name. Preserves the mime type in different stream wrapper implementations. + $parts = pathinfo($uri); + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Creating URI @uri', array('@uri' => $uri), WATCHDOG_DEBUG); + watchdog('advagg-debug', 'File Parts <pre>@parts</pre>', array('@parts' => print_r($parts, TRUE)), WATCHDOG_DEBUG); + } + $extension = '.' . $parts['extension']; + if ($extension === '.gz' || $extension === '.br') { + $parts = pathinfo($parts['filename']); + $extension = '.' . $parts['extension'] . $extension; + } + + // Create temp filename. + $temporary_file = $dir . 'advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . $extension; + + // Save to temporary filename in the destination directory. + $filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE); + + if ($filepath) { + // Perform the rename operation. + if (!advagg_rename($filepath, $uri)) { + // Unlink and try again for windows. Rename on windows does not replace + // the file if it already exists. + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Rename failed. @to', array('@to' => $uri), WATCHDOG_WARNING); + } + @unlink($uri); + // Remove temporary_file if rename failed. + if (!advagg_rename($filepath, $uri)) { + $errors[20] = $t('Renaming the filename (@incorrect) to (@correct) failed.', array('@incorrect' => $filepath, '@correct' => $uri)); + + @unlink($filepath); + if (file_exists($filepath)) { + $errors[22] = $t('unlinking @file failed.', array('@file' => $filepath)); + } + watchdog('advagg', 'Rename 4 failed. Current: %current Target: %target', array( + '%current' => $filepath, + '%target' => $uri, + ), WATCHDOG_ERROR); + } + } + + // Check the filesize. + $file_size = @filesize($uri); + $expected_size = _advagg_string_size_in_bytes($data); + if ($file_size === 0) { + // Zero byte file. + $errors[26] = $t('Write successful, but the file is empty. @file', array('@file' => $filepath)); + watchdog('advagg', 'Write successful, but the file is empty. Target: target. The empty file has been removed. If this error continues, performance will be greatly degraded.', array( + '%target' => $uri, + ), WATCHDOG_ERROR); + // Better to serve straight from Drupal than have a broken file. + @unlink($uri); + } + elseif ($file_size > 0 && $file_size != $expected_size) { + // Data written to disk doesn't match. + $errors[28] = $t('Write successful, but the file is the wrong size. @file Expected size is @expected_size, actual size is @file_size', array( + '@file' => $uri, + '@expected_size' => $expected_size, + '@file_size' => $file_size, + )); + watchdog('advagg', 'Write successful, but the file is the wrong size. %file Expected size is %expected_size, actual size is %file_size. The broken file has been removed. If this error continues, performance will be greatly degraded.', array( + '%file' => $uri, + '%expected_size' => $expected_size, + '%file_size' => $file_size, + ), WATCHDOG_ERROR); + // Better to serve straight from Drupal than have a broken file. + @unlink($uri); + } + } + else { + $errors[24] = $t('Write failed. @file', array('@file' => $temporary_file)); + watchdog('advagg', 'Write failed. Target: %target', array( + '%target' => $temporary_file, + ), WATCHDOG_ERROR); + } + // Cleanup leftover files. + if (file_exists($temporary_file)) { + @unlink($temporary_file); + } + if (file_exists($filepath)) { + @unlink($filepath); + } + return $errors; +} + +/** + * Given a string, what is the size that it should be as a file? + * + * @param string $string + * Input data to be sized in bytes. + * @link http://stackoverflow.com/a/3511239/231914. + * + * @return int + * Number of bytes this string uses. + */ +function _advagg_string_size_in_bytes($string) { + if (function_exists('mb_strlen')) { + return mb_strlen($string, '8bit'); + } + else { + return strlen($string); + } +} + +/** + * Rename; fallback to copy delete if this fails. + * + * @param string $source + * A string containing the source location. + * @param string $destination + * A string containing the destination location. + * + * @return mixed + * Destination string on success, FALSE on failure. + */ +function advagg_rename($source, $destination) { + $real_source = drupal_realpath($source); + $real_source = $real_source ? $real_source : $source; + $real_destination = drupal_realpath($destination); + $real_destination = $real_destination ? $real_destination : $destination; + + // Try php rename. + if (!@rename($real_source, $real_destination)) { + // Try drupal move. + if (!file_unmanaged_move($source, $destination)) { + // Try file scheme's rename method if it exists. + $fs_wrapper = file_stream_wrapper_get_instance_by_scheme(file_uri_scheme($source)); + if (!$fs_wrapper || !method_exists($fs_wrapper, 'rename') || !$fs_wrapper->rename($source, $destination)) { + return FALSE; + } + } + } + + return $destination; +} + +/** + * Send out a fast 404 and exit. + * + * @param string $msg + * (optional) Small message reporting why the file didn't get created. + */ +function advagg_missing_fast404($msg = '') { + drupal_page_is_cacheable(FALSE); + + // Strip new lines & separators and limit header message to 512 characters. + $msg = substr(preg_replace("/[^\w\. ]+/", "", $msg), 0, 512); + + // Add in headers if possible. + if (!headers_sent()) { + header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); + header('X-AdvAgg: Failed validation. ' . $msg); + } + + // Output fast 404 message and exit. + print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n"; + print '<html xmlns="http://www.w3.org/1999/xhtml">'; + print '<head><title>404 Not Found'; + print '

    Not Found

    '; + print '

    The requested URL was not found on this server.

    '; + print '

    Home

    '; + print ''; + print ''; + exit(); +} + +/** + * Read the atime value for the given aggregate. + * + * @param string $aggregate_filenames_hash + * Hash of the groupings of files. + * @param string $aggregate_contents_hash + * Hash of the files contents. + * @param string $uri + * URI pointing to the aggregate file. + * + * @return mixed + * File atime or FALSE if not found. + */ +function advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri) { + // Try to use the cache to avoid hitting the database with a select query. + $cache_id = 'advagg:db:' . $aggregate_filenames_hash . ADVAGG_SPACE . $aggregate_contents_hash; + $cache = cache_get($cache_id, 'cache_advagg_info'); + if ($cache) { + // If the atime in the cache is less than 12 hours old, use that. + if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) { + return $cache->data['atime']; + } + } + + // Try to get the atime from the DB. + $atime = db_select('advagg_aggregates_versions', 'aav') + ->fields('aav', array('atime')) + ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash) + ->condition('aav.aggregate_contents_hash', $aggregate_contents_hash) + ->execute() + ->fetchField(); + if (!empty($atime)) { + return $atime; + } + + // Return the atime from disk as a last resort. + if (file_exists($uri)) { + return fileatime($uri); + } + // No atime was found, return FALSE. + return FALSE; +} + +/** + * Split up as CSS string by @media queries. + * + * @param string $css + * String of CSS. + * @param string $starting_string + * What to look for when starting to parse the string. + * + * @return array + * array of css with only media queries. + * + * @see http://stackoverflow.com/a/14145856/125684 + */ +function advagg_parse_media_blocks($css, $starting_string = '@media') { + $media_blocks = array(); + $start = 0; + $last_start = 0; + + // Using the string as an array throughout this function. + // http://php.net/types.string#language.types.string.substr + while (($start = strpos($css, $starting_string, $start)) !== FALSE) { + // Stack to manage brackets. + $s = array(); + + // Get the first opening bracket. + $i = strpos($css, "{", $start); + + // If $i is false, then there is probably a css syntax error. + if ($i === FALSE) { + continue; + } + + // Push bracket onto stack. + array_push($s, $css[$i]); + // Move past first bracket. + ++$i; + + // Find the closing bracket for the @media statement. But ensure we don't + // overflow if there's an error. + while (!empty($s) && isset($css[$i])) { + // If the character is an opening bracket, push it onto the stack, + // otherwise pop the stack. + if ($css[$i] === "{") { + array_push($s, "{"); + } + elseif ($css[$i] === "}") { + array_pop($s); + } + ++$i; + } + + // Get CSS before @media and store it. + if ($last_start != $start) { + $insert = trim(substr($css, $last_start, $start - $last_start)); + if (!empty($insert)) { + $media_blocks[] = $insert; + } + } + // Cut @media block out of the css and store. + $media_blocks[] = trim(substr($css, $start, $i - $start)); + // Set the new $start to the end of the block. + $start = $i; + $last_start = $start; + } + + // Add in any remaining css rules after the last @media statement. + if (strlen($css) > $last_start) { + $insert = trim(substr($css, $last_start)); + if (!empty($insert)) { + $media_blocks[] = $insert; + } + } + + return $media_blocks; +} + +/** + * Given a filename create that file; usually works if PHP goes fatal. + * + * @param string $filename + * Just the filename no path information. + * + * @return mixed + * On failure a string saying why it failed. + * On success the $files_to_save array. + */ +function advagg_missing_fatal_handler($filename) { + static $counter = 0; + // Bail out if there is no error. + $error = error_get_last(); + if ($error === NULL) { + return; + } + + $counter++; + // Bail out if this is still in a loop. + if ($counter > 2) { + return; + } + + // Bail out if the file already exists. + $data = advagg_get_hashes_from_filename($filename); + $type = $data[0]; + list($css_path, $js_path) = advagg_get_root_files_dir(); + $uri = ''; + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + if (file_exists($uri)) { + return; + } + + // Generate the file with no alters. + set_time_limit(0); + $return = advagg_missing_create_file($filename, TRUE); + if (is_array($return) && !headers_sent()) { + $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; + // 307 if headers have not been sent yet. + $uri = advagg_generate_location_uri($filename, $data[0], $data[3]); + ++$redirect_counter; + $uri .= '?redirect_counter=' . $redirect_counter; + header('Location: ' . $uri, TRUE, 307); + exit(); + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg.module b/frontend/drupal/sites/all/modules/advagg/advagg.module new file mode 100644 index 000000000..2b497ef05 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg.module @@ -0,0 +1,6977 @@ +' . $readme . ''; + } + } + else { + $output = '
    ' . $readme . '
    '; + } + return $output; + } +} + +/** + * Implements hook_block_view_alter(). + */ +function advagg_block_view_alter(&$data, $block) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + if (empty($data) || empty($data['content'])) { + return; + } + + $block_info = $block->module . ':' . $block->delta; + $prefix = ""; + $suffix = ""; + if (is_string($data['content'])) { + $data['content'] = $prefix . $data['content'] . $suffix; + } + else { + if (!isset($data['content']['#prefix'])) { + $data['content']['#prefix'] = ''; + } + $data['content']['#prefix'] .= $prefix; + if (!isset($data['content']['#suffix'])) { + $data['content']['#suffix'] = ''; + } + $data['content']['#suffix'] .= $suffix; + } +} + +/** + * Implements hook_views_pre_render(). + */ +function advagg_views_pre_render(&$view) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + $info = "{$view->name}:{$view->current_display}"; + $prefix = ""; + $suffix = ""; + if (!isset($view->attachment_before)) { + $view->attachment_before = ''; + } + $view->attachment_before .= $prefix; + if (!isset($view->attachment_after)) { + $view->attachment_after = ''; + } + $view->attachment_after .= $suffix; +} + +/** + * Implements hook_panels_pre_render(). + */ +function advagg_panels_pre_render($panels_display, &$renderer) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + $info = "{$panels_display->layout}:{$panels_display->css_id}"; + $prefix = ""; + $suffix = ""; + if (!isset($renderer->prefix)) { + $renderer->prefix = ''; + } + $renderer->prefix .= $prefix; + if (!isset($renderer->suffix)) { + $renderer->suffix = ''; + } + $renderer->suffix .= $suffix; +} + +/** + * Implements hook_url_inbound_alter(). + * + * Inbound URL rewrite helper. If host includes subdomain, rewrite URI and + * internal path if necessary. + */ +function advagg_url_inbound_alter(&$path, $original_path, $path_language) { + // Do nothing if this has been disabled. + if (!variable_get('advagg_url_inbound_alter', ADVAGG_URL_INBOUND_ALTER)) { + return; + } + + // Setup static so we only need to run the logic once. + $already_ran = &drupal_static(__FUNCTION__); + if (!isset($already_ran)) { + $already_ran = array(); + } + $request_path = request_path(); + + // Set the path again if we already did this alter. + if (array_key_exists($request_path, $already_ran)) { + $path = $already_ran[$request_path]; + return; + } + + // If requested path was for an advagg file but now it is something else + // switch is back to the advagg file. + if (!empty($path) + && $path != $request_path + && advagg_match_file_pattern($request_path) + ) { + // Get the advagg paths. + $advagg_path = advagg_get_root_files_dir(); + + // Get the top level path. + $top_level = substr($advagg_path[0][1], 0, strpos($advagg_path[0][1], 'advagg_css')); + + // Only change if it's an exact match. + $start = strpos($request_path, $top_level . 'advagg_'); + if ($start === 0) { + // Set path to correct advagg path. + $path = substr($request_path, $start); + $already_ran[$request_path] = $path; + } + else { + // Put all languages prefixes into an array. + $language_list = language_list(); + $prefixes = array(); + foreach ($language_list as $lang) { + if ($lang->enabled && !empty($lang->prefix) && strpos($request_path, $lang->prefix) !== FALSE) { + $prefixes[$lang->prefix] = $lang->prefix; + } + } + if (!empty($prefixes)) { + // Remove all enabled languages prefixes from the beginning of the path. + $substr_to_shrink = substr($request_path, 0, $start); + foreach ($prefixes as $prefix) { + $substr_to_shrink = str_replace($prefix . '/', '', $substr_to_shrink); + } + // Set path to correct advagg path. + $path = $substr_to_shrink . substr($request_path, $start); + $already_ran[$request_path] = $path; + } + } + } +} + +/** + * Implements hook_hook_info(). + */ +function advagg_hook_info() { + // List of hooks that can be inside of *.advagg.inc files. + // All advagg hooks except for: + // advagg_current_hooks_hash_array_alter + // advagg_hooks_implemented_alter + // advagg_get_root_files_dir_alter + // because these 3 hooks are used on most requests. + $advagg_hooks = array( + 'advagg_get_css_file_contents_pre_alter', + 'advagg_get_css_file_contents_alter', + 'advagg_get_js_file_contents_alter', + 'advagg_get_css_aggregate_contents_alter', + 'advagg_get_js_aggregate_contents_alter', + 'advagg_save_aggregate_pre_alter', + 'advagg_save_aggregate_alter', + 'advagg_build_aggregate_plans_alter', + 'advagg_build_aggregate_plans_post_alter', + 'advagg_css_groups_alter', + 'advagg_js_groups_alter', + 'advagg_modify_css_pre_render_alter', + 'advagg_modify_js_pre_render_alter', + 'advagg_changed_files', + 'advagg_removed_aggregates', + 'advagg_scan_for_changes', + 'advagg_get_info_on_files_alter', + 'advagg_context_alter', + 'advagg_missing_root_file', + ); + $hooks = array(); + foreach ($advagg_hooks as $hook) { + $hooks[$hook] = array('group' => 'advagg'); + } + return $hooks; +} + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_module_implements_alter(&$implementations, $hook) { + // Move advagg_theme_registry_alter to the top. + if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) { + $item = array('advagg' => $implementations['advagg']); + unset($implementations['advagg']); + $implementations = array_merge($item, $implementations); + } + + // Move advagg_ajax_render_alter to the top. + if ($hook === 'ajax_render_alter' && array_key_exists('advagg', $implementations)) { + $item = array('advagg' => $implementations['advagg']); + unset($implementations['advagg']); + $implementations = array_merge($item, $implementations); + } + + // Move advagg_element_info_alter to the bottom. + if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + } + + // Replace locale_js_alter with _advagg_locale_js_alter. + if ($hook === 'js_alter' && array_key_exists('locale', $implementations)) { + unset($implementations['locale']); + $implementations['_advagg_locale'] = FALSE; + } + + // Move advagg_file_url_alter to the bottom. + if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + } + + if ($hook === 'requirements') { + // Move advagg_requirements to the bottom. + if (array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + } + // Move advagg_css_cdn to the bottom. + if (array_key_exists('advagg_css_cdn', $implementations)) { + $item = $implementations['advagg_css_cdn']; + unset($implementations['advagg_css_cdn']); + $implementations['advagg_css_cdn'] = $item; + } + // Move advagg_css_compress to the bottom. + if (array_key_exists('advagg_css_compress', $implementations)) { + $item = $implementations['advagg_css_compress']; + unset($implementations['advagg_css_compress']); + $implementations['advagg_css_compress'] = $item; + } + // Move advagg_js_cdn to the bottom. + if (array_key_exists('advagg_js_cdn', $implementations)) { + $item = $implementations['advagg_js_cdn']; + unset($implementations['advagg_js_cdn']); + $implementations['advagg_js_cdn'] = $item; + } + // Move advagg_js_compress to the bottom. + if (array_key_exists('advagg_js_compress', $implementations)) { + $item = $implementations['advagg_js_compress']; + unset($implementations['advagg_js_compress']); + $implementations['advagg_js_compress'] = $item; + } + } + + // Move advagg_cron to the bottom. + if ($hook === 'cron' && array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + } +} + +/** + * Implements hook_js_alter(). + * + * This is a locking wrapper for locale_js_alter(). + */ +function _advagg_locale_js_alter(&$js) { + // If the variable is empty then get the latest variable from the database. + $name = 'javascript_parsed'; + $parsed = variable_get($name, array()); + if (empty($parsed)) { + $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchAllKeyed()); + if (!empty($variables[$name])) { + $GLOBALS['conf'][$name] = $variables[$name]; + } + } + + // See if locale_js_alter() needs to do anything. + $dir = 'public://' . variable_get('locale_js_directory', 'languages'); + $new_files = FALSE; + // See if a rebuild of the translation file for the current language is + // needed. + if (!empty($parsed['refresh:' . $GLOBALS['language']->language])) { + $new_files = TRUE; + } + // Check for new js source files. + if (empty($new_files)) { + foreach ($js as $item) { + if ($item['type'] === 'file' + && !in_array($item['data'], $parsed) + && substr($item['data'], 0, strlen($dir)) != $dir + ) { + $new_files = TRUE; + break; + } + } + } + if (empty($new_files)) { + // No new files to manage, just add in available i18n files. + advagg_locale_js_add_translations($js, $dir); + // Exit function. + return; + } + + $count = 0; + while (!lock_acquire('locale_js_alter', 10)) { + ++$count; + // If we've waited over 3 times then skip. + if ($count > 3) { + lock_release('locale_js_alter'); + // Add in available i18n files. + advagg_locale_js_add_translations($js, $dir); + + // Disable saving to the cache as translations might be missing. + drupal_page_is_cacheable(FALSE); + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) { + $GLOBALS['conf']['advagg_cache_level'] = 0; + } + return; + } + + // Wait for the lock to be available. + lock_wait('locale_js_alter'); + } + + try { + // Run the alter. + locale_js_alter($js); + } + catch (PDOException $e) { + // If it fails we don't care, javascript_parsed is either already written or + // it will happen again on the next request. + // Still log it if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } + } + lock_release('locale_js_alter'); +} + +/** + * Implements hook_system_info_alter(). + */ +function advagg_system_info_alter(&$info, $file, $type) { + $config_path = &drupal_static(__FUNCTION__); + // Get advagg config path. + if (empty($config_path)) { + $config_path = advagg_admin_config_root_path(); + } + + // Replace advagg path. + if (!empty($info['configure']) + && strpos($info['configure'], '/advagg') !== FALSE + && ((!empty($info['dependencies']) + && is_array($info['dependencies']) + && in_array('advagg', $info['dependencies']) + ) || $file->name === 'advagg') + ) { + $pos = strpos($info['configure'], '/advagg') + 7; + $substr = substr($info['configure'], 0, $pos); + $info['configure'] = str_replace($substr, $config_path . '/advagg', $info['configure']); + } +} + +/** + * Implements hook_permission(). + */ +function advagg_permission() { + return array( + 'bypass advanced aggregation' => array( + 'title' => t('bypass advanced aggregation'), + 'description' => t('User can use URL query strings to bypass AdvAgg.'), + ), + ); +} + +/** + * Implements hook_file_url_alter(). + */ +function advagg_file_url_alter(&$original_uri) { + // Do nothing if URI does not contain /advagg_ + // OR file does not have the correct pattern. + if (strpos($original_uri, '/advagg_') === FALSE || !advagg_match_file_pattern($original_uri)) { + return; + } + + // CDN fix. + // Do nothing if + // in maintenance_mode + // CDN module does not exist + // CDN far future is disabled + // CDN mode is not basic + // URI does not contain cdn/farfuture/. + if (variable_get('maintenance_mode', FALSE) + || !module_exists('cdn') + || !variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT) + || variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC + || strpos($original_uri, 'cdn/farfuture/') === FALSE + ) { + return; + } + + // Remove cdn/farfuture/BASE64/prefix:value/ from the URI. + $original_uri = preg_replace('/cdn\/farfuture\/[A-Za-z0-9-_]{43}\/[A-Za-z]+\:[A-Za-z0-9-_]+\//', '', $original_uri); +} + +/** + * Implements hook_menu(). + */ +function advagg_menu() { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $file_path = drupal_get_path('module', 'advagg'); + $config_path = advagg_admin_config_root_path(); + + $path_defined = FALSE; + if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { + $external_css = trim(parse_url(str_replace('/test.css', '/%', file_create_url($css_path[0] . '/test.css')), PHP_URL_PATH)); + if (strpos($external_css, $GLOBALS['base_path']) === 0) { + $external_css = substr($external_css, strlen($GLOBALS['base_path'])); + } + $external_js = trim(parse_url(str_replace('/test.js', '/%', file_create_url($js_path[0] . '/test.js')), PHP_URL_PATH)); + if (strpos($external_js, $GLOBALS['base_path']) === 0) { + $external_js = substr($external_js, strlen($GLOBALS['base_path'])); + } + $items[$external_css] = array( + 'title' => "Generate CSS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + // Allow anyone to access these public css files. + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $items[$external_js] = array( + 'title' => "Generate JS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + // Allow anyone to access these public js files. + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $path_defined = TRUE; + } + + if (!$path_defined) { + $items[$css_path[1] . '/%'] = array( + 'title' => "Generate CSS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + // Allow anyone to access these public css files. + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $items[$js_path[1] . '/%'] = array( + 'title' => "Generate JS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + // Allow anyone to access these public js files. + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + } + + // If mutiple paths are symlinked to the same location; allow advagg to handle + // those addtional locations. + $advagg_additional_generate_paths = variable_get('advagg_additional_generate_paths', array()); + if (!empty($advagg_additional_generate_paths)) { + foreach ($advagg_additional_generate_paths as $path) { + $items[$path] = array( + 'title' => "Generate CSS/JS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + // Allow anyone to access these public css files. + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + } + } + + $items[$config_path . '/default'] = array( + 'title' => 'Performance', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'file path' => drupal_get_path('module', 'system'), + 'weight' => -10, + ); + $items[$config_path . '/advagg'] = array( + 'title' => 'Advanced CSS/JS Aggregation', + 'description' => 'Configuration for Advanced CSS/JS Aggregation.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + 'weight' => 1, + ); + $items[$config_path . '/advagg/config'] = array( + 'title' => 'Configuration', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items[$config_path . '/advagg/info'] = array( + 'title' => 'Information', + 'description' => 'More detailed information about advagg.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_admin_info_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + 'weight' => 18, + ); + $items[$config_path . '/advagg/operations'] = array( + 'title' => 'Operations', + 'description' => 'Flush caches, set the bypass cookie, take drastic actions.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_admin_operations_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + 'weight' => 20, + ); + return $items; +} + +/** + * Implements hook_cron(). + * + * This will be ran once a day at most. + */ +function advagg_cron($bypass_time_check = FALSE) { + // @param bool $bypass_time_check + // Set to TRUE to skip the 24 hour check. + // + // Execute once a day (24 hours). + if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > (REQUEST_TIME - variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY))) { + return array(); + } + variable_set('advagg_cron_timestamp', REQUEST_TIME); + + // Flush the cache_advagg_info cache bin. + cache_clear_all(NULL, 'cache_advagg_info'); + + $return = array(); + // Clear out all stale advagg aggregated files. + module_load_include('inc', 'advagg', 'advagg.cache'); + $return[] = advagg_delete_stale_aggregates(); + + // Delete all empty aggregated files. + $return[] = advagg_delete_empty_aggregates(); + + // Delete orphaned aggregates. + $return[] = advagg_delete_orphaned_aggregates(); + + // Remove aggregates that include missing files. + $return[] = advagg_remove_missing_files_from_db(); + + // Remove unused aggregates. + $return[] = advagg_remove_old_unused_aggregates(); + + // Remove expired locks from the semaphore database table. + $return[] = advagg_cleanup_semaphore_table(); + + // Remove old temp files. + $return[] = advagg_remove_temp_files(); + + // Refresh all locale files. + $return[] = advagg_refresh_all_locale_files(); + + // Update libraries data. + advagg_get_remote_libraries_versions(TRUE); + + return $return; +} + +/** + * Implements hook_flush_caches(). + */ +function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) { + // * @param bool $all_bins + // * TRUE: Get all advagg cache bins. + // * @param bool $push_new_changes + // * FALSE: Do not scan for changes. + // + // Send back a blank array if aav table doesn't exist. + if (!db_table_exists('advagg_aggregates_versions')) { + return array(); + } + + // Scan for and push new changes. + module_load_include('inc', 'advagg', 'advagg.cache'); + if ($push_new_changes) { + advagg_push_new_changes(); + } + + // Get list of cache bins to clear. + $bins = array('cache_advagg_aggregates'); + if ($all_bins) { + $bins[] = 'cache_advagg_info'; + } + return $bins; +} + +/** + * Implements hook_element_info_alter(). + */ +function advagg_element_info_alter(&$type) { + // Replace drupal_pre_render_styles with advagg_pre_render_styles. + $type['styles']['#items'] = array(); + if (!isset($type['styles']['#pre_render'])) { + $type['styles']['#pre_render'] = array(); + } + $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); + if ($key !== FALSE) { + $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles'; + } + else { + $type['styles']['#pre_render'][] = 'advagg_pre_render_styles'; + } + // Allow for other code to easily change the render with alter hooks. + $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render'; + $type['styles']['#group_callback'] = 'drupal_group_css'; + // Swap in our own aggregation callback. + $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css'; + $type['styles']['#type'] = 'styles'; + + // Replace drupal_pre_render_scripts with advagg_pre_render_scripts. + $type['scripts']['#items'] = array(); + if (!isset($type['scripts']['#pre_render'])) { + $type['scripts']['#pre_render'] = array(); + } + $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']); + $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']); + $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']); + if ($key_drupal !== FALSE) { + $type['scripts']['#pre_render'][$key_drupal] = 'advagg_pre_render_scripts'; + } + elseif ($key_omega !== FALSE) { + $type['scripts']['#pre_render'][$key_omega] = 'advagg_pre_render_scripts'; + } + elseif ($key_aurora !== FALSE) { + $type['scripts']['#pre_render'][$key_aurora] = 'advagg_pre_render_scripts'; + } + else { + $type['scripts']['#pre_render'][] = 'advagg_pre_render_scripts'; + } + // Allow for other code to easily change the render with alter hooks. + $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render'; + $type['scripts']['#group_callback'] = 'advagg_group_js'; + // Swap in our own aggregation callback. + $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js'; + $type['scripts']['#type'] = 'scripts'; + + // Copy html_tag to html_script_tag. + $type['html_script_tag'] = $type['html_tag']; + $type['html_script_tag']['#theme'] = 'html_script_tag'; + $type['html_script_tag']['#type'] = 'html_script_tag'; +} + +/** + * Implements hook_theme_registry_alter(). + * + * Replace template_process_html with _advagg_process_html. + */ +function advagg_theme_registry_alter(&$theme_registry) { + if (!isset($theme_registry['html'])) { + return; + } + + // Replace core's process function with our own. + $index = array_search('template_process_html', $theme_registry['html']['process functions']); + if ($index !== FALSE) { + $theme_registry['html']['process functions'][$index] = '_advagg_process_html'; + } + else { + // Put AdvAgg at the bottom if we can't find the replacement. + $theme_registry['html']['process functions'][] = '_advagg_process_html'; + } + + // Copy html_tag to html_script_tag. + $theme_registry['html_script_tag'] = $theme_registry['html_tag']; + $theme_registry['html_script_tag']['function'] = 'theme_html_script_tag'; + + // Fix imce_page. + if (isset($theme_registry['imce_page'])) { + $advagg_path = drupal_get_path('module', 'advagg'); + $imce_path = drupal_get_path('module', 'imce'); + if (strpos($theme_registry['imce_page']['path'], $imce_path) !== FALSE) { + $theme_registry['imce_page']['path'] = $advagg_path . '/tpl'; + } + } +} + +/** + * Implements hook_ajax_render_alter(). + */ +function advagg_ajax_render_alter(&$commands) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + + // Do not run hook if advagg_ajax_render_alter is FALSE. + if (!variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER)) { + return; + } + + // Conditionally adds the default Drupal/jQuery libraries to the page. + // @see http://drupal.org/node/1279226 + if (function_exists('drupal_add_js_page_defaults')) { + drupal_add_js_page_defaults(); + } + + // Get Core JS. + list(, $core_scripts_header, $core_scripts_footer, $items, $settings) = advagg_build_ajax_js_css(); + + // Get AdvAgg JS. + $scripts_header = $scripts_footer = ''; + if (!empty($items['js'])) { + $scripts_footer_array = advagg_get_js('footer', $items['js'], TRUE); + // Function advagg_pre_render_scripts() gets called here. + $scripts_footer = drupal_render($scripts_footer_array); + $scripts_header_array = advagg_get_js('header', $items['js'], TRUE); + // Function advagg_pre_render_scripts() gets called here. + $scripts_header = drupal_render($scripts_header_array); + } + + // Remove core JS. + foreach ($commands as $key => $values) { + // Skip if not an array or not a command. + if (!is_array($values) || empty($values['command'])) { + continue; + } + + if ($values['command'] === 'settings' + && is_array($values['settings']) + && !empty($values['merge']) + ) { + // Remove JS settings. + unset($commands[$key]); + continue; + } + if ($values['command'] === 'insert' + && is_null($values['settings']) + && $values['method'] === 'prepend' + && $values['data'] == $core_scripts_header + ) { + // Remove JS header. + unset($commands[$key]); + continue; + } + if ($values['command'] === 'insert' + && is_null($values['settings']) + && $values['method'] === 'append' + && $values['data'] == $core_scripts_footer + ) { + // Remove JS footer. + unset($commands[$key]); + continue; + } + } + + // Add in AdvAgg JS. + $extra_commands = array(); + if (!empty($scripts_header)) { + $extra_commands[] = ajax_command_prepend('head', $scripts_header); + } + if (!empty($scripts_footer)) { + $extra_commands[] = ajax_command_append('body', $scripts_footer); + } + if (!empty($extra_commands)) { + $commands = array_merge($extra_commands, $commands); + } + if (!empty($settings)) { + array_unshift($commands, ajax_command_settings(advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($settings['data'], 'is_array'))), TRUE)); + } +} + +/** + * Implements hook_preprocess_page(). + */ +function advagg_preprocess_page() { + // Scan for changes to any CSS/JS files if in development mode. + advagg_scan_filesystem_for_changes_live(); +} + +/** + * Implements hook_preprocess_html(). + * + * Add in rendering IE meta tag if "combine CSS" is enabled. + */ +function advagg_preprocess_html() { + // http://www.phpied.com/conditional-comments-block-downloads/#update + // Prevent conditional comments from stalling css downloads. + $fix_blocking_css_ie = array( + '#weight' => '-999999', + '#type' => 'markup', + '#markup' => "\n", + ); + // Add markup for IE conditional comments to head. + drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie'); + + // Do not force IE rendering mode if "combine CSS" is disabled. + if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { + return; + } + + // Send IE meta tag to force IE rendering mode header. + $x_ua_compatible = 'IE=edge'; + if (variable_get('advagg_chrome_header_enabled', ADVAGG_CHROME_HEADER_ENABLED)) { + $x_ua_compatible .= ',chrome=1'; + } + drupal_add_http_header('X-UA-Compatible', $x_ua_compatible); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Give advice on how to temporarily disable css/js aggregation. + */ +function advagg_form_system_performance_settings_alter(&$form, &$form_state) { + module_load_include('admin.inc', 'advagg'); + advagg_admin_system_performance_settings_form($form, $form_state); +} + +/** + * Implements hook_js_alter(). + */ +function advagg_js_alter(&$js) { + if (module_exists('admin_menu')) { + // Fix for admin menu; put JS in footer. + $path = drupal_get_path('module', 'admin_menu'); + $filename = $path . '/admin_menu.js'; + if (isset($js[$filename])) { + $js[$filename]['scope'] = 'footer'; + } + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @defgroup 3rd_party_hooks 3rd party hook implementations + * @{ + * Hooks that are not apart of core or AdvAgg. + */ + +/** + * Implements hook_cron_alter(). + */ +function advagg_cron_alter(&$data) { + // Run this cron job every 2 minutes. + if (isset($data['advagg_js_compress_cron'])) { + $data['advagg_js_compress_cron']['rule'] = '*/2 * * * *'; + } + // Run this cron job every 5 minutes. + if (isset($data['advagg_relocate_cron'])) { + $data['advagg_relocate_cron']['rule'] = '*/5 * * * *'; + } + // Run this cron job every day. + if (isset($data['advagg_cron'])) { + $data['advagg_cron']['rule'] = '0 0 * * *'; + } +} + +/** + * Implements hook_password_policy_force_change_allowed_paths_alter(). + */ +function advagg_password_policy_force_change_allowed_paths_alter(&$allowed_paths) { + $advagg_items = advagg_menu(); + foreach ($advagg_items as $path => $attributes) { + if (!empty($attributes['page callback']) && $attributes['page callback'] === 'advagg_missing_aggregate') { + $allowed_paths[] = str_replace('/%', '/*', $path); + } + } +} + +/** + * Implements hook_s3fs_upload_params_alter(). + * + * Set headers for advagg files. + */ +function advagg_s3fs_upload_params_alter(&$upload_params) { + // Get advagg dir. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $scheme = file_uri_scheme($css_path[1]); + if ($scheme) { + $css_path_dir = parse_url($css_path[1]); + $css_path_dir = str_replace("$scheme://", '', $css_path[1]); + } + else { + $css_path_dir = ltrim($css_path[1], '/'); + } + $scheme = file_uri_scheme($js_path[1]); + if ($scheme) { + $js_path_dir = parse_url($js_path[1]); + $js_path_dir = str_replace("$scheme://", '', $js_path_dir[1]); + } + else { + $js_path_dir = ltrim($js_path[1], '/'); + } + + // Get file type in advagg dir, css or js. + $type = ''; + if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $css_path_dir) !== FALSE) { + $type = 'css'; + } + if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $js_path_dir) !== FALSE) { + $type = 'js'; + } + if ($js_path_dir === $css_path_dir && !empty($type)) { + $pathinfo = pathinfo($upload_params['Key']); + if ($pathinfo['extension'] === 'gz') { + $pathinfo = pathinfo($pathinfo['filename']); + } + $type = $pathinfo['extension']; + } + if (empty($type)) { + // Only change advagg files. + return; + } + + // Cache control is 52 weeeks. + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public, immutable'; + } + else { + $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public'; + } + // Expires in 365 days. + $upload_params['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 365 * 24 * 60 * 60); + + // The extension is .css or .js. + $pathinfo = pathinfo($upload_params['Key']); + if ($pathinfo['extension'] === $type) { + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { + // Set gzip. + $upload_params['ContentEncoding'] = 'gzip'; + } + elseif (variable_get('advagg_brotli', ADVAGG_BROTLI)) { + // Set br. + $upload_params['ContentEncoding'] = 'br'; + } + } +} + +/** + * Return s3fs configuration settings and values. + * + * @param string $key + * A specific key available in the s3fs configuration. NULL by default. + * + * @return array|string|null + * The full s3fs configuration settings, value of a specific key, + * or NULL if s3fs and the function do not exist. + */ +function advagg_get_s3fs_config($key = NULL) { + if (module_exists('s3fs') && is_callable('_s3fs_get_config')) { + $s3fs_config = _s3fs_get_config(); + return (empty($key)) ? $s3fs_config : $s3fs_config[$key]; + } + else { + return NULL; + } +} + +/** + * Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty. + * + * If this needs to be accessed in a loop, it is more efficient to call + * advagg_get_s3fs_config() once from outside of the loop. An example + * can be seen in the advagg_install_check_via_http function. + * + * @param bool $is_set + * Check if no_write_cssjs field is set (TRUE) or empty (FALSE). + * + * @return bool + * TRUE or FALSE is returned based on evaluating the field. If + * s3fs_config returns a NULL, evaluate the function to FALSE. + * + * @see advagg_get_s3fs_config() + */ +function advagg_s3fs_evaluate_no_rewrite_cssjs($is_set = TRUE) { + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); + if (!is_null($s3fs_no_rewrite_cssjs)) { + return ($is_set) ? !empty($s3fs_no_rewrite_cssjs) : empty($s3fs_no_rewrite_cssjs); + } + else { + return FALSE; + } +} + +/** + * Implements hook_admin_menu_cache_info(). + * + * Add in a cache flush for advagg. + */ +function advagg_admin_menu_cache_info() { + if (variable_get('advagg_enabled', ADVAGG_ENABLED)) { + $caches['advagg'] = array( + 'title' => t('Adv CSS/JS Agg'), + 'callback' => 'advagg_admin_flush_cache', + ); + return $caches; + } +} + +/** + * Implements hook_admin_menu_output_alter(). + * + * Add in a cache flush for advagg. + */ +function advagg_admin_menu_output_alter(array &$content) { + if (variable_get('advagg_enabled', ADVAGG_ENABLED)) { + // Remove default core aggregation link. + unset($content['icon']['icon']['flush-cache']['assets']); + } +} + +/** + * Implements hook_anonymous_login_paths_alter(). + */ +function advagg_anonymous_login_paths_alter(&$paths) { + // Exclude advagg css/js paths. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $paths['exclude'][] = $css_path[1] . '/*'; + $paths['exclude'][] = $js_path[1] . '/*'; +} + +/** + * Implements hook_pre_flush_all_caches(). + */ +function advagg_pre_flush_all_caches() { + static $run_once; + + if (!isset($run_once)) { + $run_once = TRUE; + // Only invoked by registry_rebuild. + module_load_include('admin.inc', 'advagg'); + // Truncate the advagg_files table. + advagg_admin_truncate_advagg_files(); + } +} + +/** + * @} End of "defgroup 3rd_party_hooks". + */ + +/** + * Only the alter part of locale_js_alter(), not the parsing part. + * + * @param array $javascript + * An array with all JavaScript code. Defaults to the default + * JavaScript array for the given scope. + * @param string $dir + * String pointing to the public locale_js_directory. + */ +function advagg_locale_js_add_translations(array &$javascript, $dir) { + // Add the translation JavaScript file to the page. + if (!empty($GLOBALS['language']->javascript)) { + // Add the translation JavaScript file to the page. + $file = $dir . '/' . $GLOBALS['language']->language . '_' . $GLOBALS['language']->javascript . '.js'; + $javascript[$file] = drupal_js_defaults($file); + } +} + +/** + * Callback for pre_render so elements can be modified before they are rendered. + * + * @param array $elements + * A render array containing: + * - #items: The JavaScript items as returned by drupal_add_js() and + * altered by drupal_get_js(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of JavaScript tags. + */ +function advagg_modify_js_pre_render(array $elements) { + // Get the children elements. + $children = array_intersect_key($elements, array_flip(element_children($elements))); + + // Allow other modules to modify $children and $elements before they are + // rendered. + // Call hook_advagg_modify_js_pre_render_alter() + drupal_alter('advagg_modify_js_pre_render', $children, $elements); + + // Remove old children elements. + foreach ($children as $key => $value) { + if (isset($elements[$key])) { + unset($elements[$key]); + } + } + // Add in new children elements. + $elements += $children; + return $elements; +} + +/** + * Callback for pre_render so elements can be modified before they are rendered. + * + * @param array $elements + * A render array containing: + * - #items: The CSS items as returned by drupal_add_css() and + * altered by drupal_get_css(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of JavaScript tags. + */ +function advagg_modify_css_pre_render(array $elements) { + if (!advagg_enabled()) { + return $elements; + } + + // Put children elements into a reference array. + $children = array(); + foreach ($elements as $key => &$value) { + if ($key !== '' && is_string($key) && (0 === strpos($key, '#'))) { + continue; + } + $children[$key] = &$value; + } + unset($value); + + // Allow other modules to modify $children and $elements before they are + // rendered. + // Call hook_advagg_modify_css_pre_render_alter() + drupal_alter('advagg_modify_css_pre_render', $children, $elements); + return $elements; +} + +/** + * Default callback to aggregate CSS files and inline content. + * + * Having the browser load fewer CSS files results in much faster page loads + * than when it loads many small files. This function aggregates files within + * the same group into a single file unless the site-wide setting to do so is + * disabled (commonly the case during site development). To optimize download, + * it also compresses the aggregate files by removing comments, whitespace, and + * other unnecessary content. Additionally, this functions aggregates inline + * content together, regardless of the site-wide aggregation setting. + * + * @param array $css_groups + * An array of CSS groups as returned by drupal_group_css(). This function + * modifies the group's 'data' property for each group that is aggregated. + * + * @see drupal_aggregate_css() + * @see drupal_group_css() + * @see drupal_pre_render_styles() + * @see system_element_info() + */ +function _advagg_aggregate_css(array &$css_groups) { + if (!advagg_enabled()) { + return drupal_aggregate_css($css_groups); + } + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $GLOBALS['_advagg']['debug']['css_groups_before'][] = $css_groups; + } + + $preprocess_css = advagg_file_aggregation_enabled('css'); + + // Allow other modules to modify $css_groups right before it is processed. + // Call hook_advagg_css_groups_alter(). + drupal_alter('advagg_css_groups', $css_groups, $preprocess_css); + + // For each group that needs aggregation, aggregate its items. + $files_to_aggregate = array(); + // Allow for inline CSS to be between aggregated files. + $gap_counter = 0; + foreach ($css_groups as $key => $group) { + switch ($group['type']) { + // If a file group can be aggregated into a single file, do so, and set + // the group's data property to the file path of the aggregate file. + case 'file': + if ($group['preprocess'] && $preprocess_css) { + $files_to_aggregate[$gap_counter][$key] = $group; + } + else { + ++$gap_counter; + } + break; + + // Aggregate all inline CSS content into the group's data property. + case 'inline': + ++$gap_counter; + $css_groups[$key]['data'] = ''; + foreach ($group['items'] as $item) { + $css_groups[$key]['data'] .= advagg_load_stylesheet_content($item['data'], $item['preprocess']); + } + break; + + // Create a gap for external CSS. + case 'external': + ++$gap_counter; + break; + } + } + + if (!empty($files_to_aggregate)) { + $hooks_hash = advagg_get_current_hooks_hash(); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $css_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); + $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { + $plans = $cache->data; + } + else { + module_load_include('inc', 'advagg', 'advagg'); + $plans = advagg_build_aggregate_plans($files_to_aggregate, 'css'); + if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) { + cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + } + $css_groups = advagg_merge_plans($css_groups, $plans); + } + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $GLOBALS['_advagg']['debug']['css_groups_after'][] = $css_groups; + } +} + +/** + * Default callback to aggregate JavaScript files. + * + * Having the browser load fewer JavaScript files results in much faster page + * loads than when it loads many small files. This function aggregates files + * within the same group into a single file unless the site-wide setting to do + * so is disabled (commonly the case during site development). To optimize + * download, it also compresses the aggregate files by removing comments, + * whitespace, and other unnecessary content. + * + * @param array $js_groups + * An array of JavaScript groups as returned by drupal_group_js(). For each + * group that is aggregated, this function sets the value of the group's + * 'data' key to the URI of the aggregate file. + * + * @see drupal_group_js() + * @see drupal_pre_render_scripts() + */ +function _advagg_aggregate_js(array &$js_groups) { + if (!advagg_enabled()) { + if (function_exists('drupal_aggregate_js')) { + return drupal_aggregate_js($js_groups); + } + else { + return; + } + } + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $GLOBALS['_advagg']['debug']['js_groups_before'][] = $js_groups; + } + + $preprocess_js = advagg_file_aggregation_enabled('js'); + + // Allow other modules to modify $js_groups right before it is processed. + // Call hook_advagg_js_groups_alter(). + drupal_alter('advagg_js_groups', $js_groups, $preprocess_js); + + // For each group that needs aggregation, aggregate its items. + $files_to_aggregate = array(); + // Only aggregate when the site is configured to do so, and not during an + // update. + $gap_counter = 0; + if ($preprocess_js) { + // Set boolean to TRUE if all JS in footer. + $all_in_footer = FALSE; + if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 2) { + $all_in_footer = TRUE; + } + foreach ($js_groups as $key => &$group) { + switch ($group['type']) { + // If a file group can be aggregated into a single file, do so, and set + // the group's data property to the file path of the aggregate file. + case 'file': + if (!empty($group['preprocess'])) { + // Special handing for when all JS is in the footer. + if ($all_in_footer && $group['scope'] === 'footer' && $group['group'] > 9000) { + ++$gap_counter; + $all_in_footer = FALSE; + } + $files_to_aggregate[$gap_counter][$key] = $group; + } + else { + ++$gap_counter; + } + break; + + // Create a gap for inline JS. + case 'inline': + ++$gap_counter; + break; + + // Create a gap for external JS. + case 'external': + ++$gap_counter; + break; + } + } + unset($group); + } + + if (!empty($files_to_aggregate)) { + $hooks_hash = advagg_get_current_hooks_hash(); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $js_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); + $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { + $plans = $cache->data; + } + else { + module_load_include('inc', 'advagg', 'advagg'); + $plans = advagg_build_aggregate_plans($files_to_aggregate, 'js'); + if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) { + cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + } + $js_groups = advagg_merge_plans($js_groups, $plans); + } + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $GLOBALS['_advagg']['debug']['js_groups_after'][] = $js_groups; + } +} + +/** + * Builds the arrays needed for css rendering and caching. + * + * @param bool $skip_alter + * (Optional) If set to TRUE, this function skips calling drupal_alter() on + * css, useful for the aggressive cache. + * + * @return array + * Array contains the 2 arrays used for css. + */ +function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) { + // Get the raw CSS variable. + $raw_css = drupal_add_css(); + // Process and Sort css. + $full_css = advagg_get_css($raw_css, $skip_alter); + // Add attached js to drupal_add_js() function. + if (!empty($full_css['#attached'])) { + drupal_process_attached($full_css); + // Remove #attached since it's been added to the javascript array now. + unset($full_css['#attached']); + } + return array($raw_css, $full_css); +} + +/** + * Builds the arrays needed for js rendering and caching. + * + * @param bool $skip_alter + * (Optional) If set to TRUE, this function skips calling drupal_alter() on + * js, useful for the aggressive cache. + * + * @return array + * Array contains the 3 arrays used for javascript. + */ +function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) { + // Get the raw JS variable. + $javascript = drupal_add_js(); + // Process and Sort JS. + $full_javascript = advagg_get_full_js($javascript, $skip_alter); + // Get scopes used in the js. + $scopes = advagg_get_js_scopes($full_javascript); + // Add JS to the header and footer of the page. + $js_scope_array = array(); + $js_scope_settings_array = array(); + foreach ($scopes as $scope => $use) { + if (!$use) { + // If the scope is not being used, skip it. + continue; + } + // advagg_get_js() will sort the JavaScript so that it appears in the + // correct order. + $scripts = advagg_get_js($scope, $full_javascript); + if (isset($scripts['#items']['settings'])) { + // Get the js settings. + $js_scope_settings_array[$scope]['settings'] = $scripts['#items']['settings']; + // Exclude JS Settings from the array; we'll add it back later. + $scripts['#items']['settings'] = array(); + } + $js_scope_array[$scope] = $scripts; + } + + // Fix settings; if more than 1 is set, use the largest one. + if (count($js_scope_settings_array) > 1) { + $max = -1; + $max_scope = ''; + foreach ($js_scope_settings_array as $scope => $settings) { + $count = count($settings); + $max = max($max, $count); + if ($max == $count) { + $max_scope = $scope; + } + } + + foreach ($js_scope_settings_array as $scope => $settings) { + if ($scope !== $max_scope) { + unset($js_scope_settings_array[$scope]); + } + } + } + return array($javascript, $js_scope_settings_array, $js_scope_array); +} + +/** + * Returns TRUE if the CSS is being loaded via JavaScript. + * + * @param object $css_cache + * Cache object from cache_get(). + * + * @return bool + * TRUE if CSS loaded via JS. FALSE if not. + */ +function advagg_css_in_js($css_cache = NULL) { + if (module_exists('advagg_mod') + && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER) + ) { + return TRUE; + } + if (module_exists('css_delivery') + && css_delivery_enabled() + ) { + return TRUE; + } + // Critical css added by another means. + if (!empty($css_cache->data[1]['#items'])) { + foreach ($css_cache->data[1]['#items'] as $values) { + if (!empty($values['critical-css'])) { + return TRUE; + } + } + } + return variable_get('advagg_css_in_js', ADVAGG_CSS_IN_JS); +} + +/** + * Given the full css and js scope array return back the render cache. + * + * @param array $full_css + * Array from advagg_get_css() with #attached removed because it was built by + * _advagg_build_css_arrays_for_rendering(). + * @param array $js_scope_array + * Array built from iterations of advagg_get_js() inside of + * _advagg_build_js_arrays_for_rendering(). + * + * @return array + * Array containing the $css_cache, $js_cache, $css_cache_id, $js_cache_id. + */ +function advagg_get_render_cache(array $full_css, array $js_scope_array) { + $cids = array(); + $css_cache_id = ''; + $js_cache_id = ''; + + // Get advagg hash. + $hooks_hash = advagg_get_current_hooks_hash(); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + if (advagg_file_aggregation_enabled('css')) { + // Generate css cache id. + $cids[] = $css_cache_id = 'advagg:css:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($full_css)); + } + if (advagg_file_aggregation_enabled('js')) { + // Generate js cache id. + $cids[] = $js_cache_id = 'advagg:js:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($js_scope_array)); + } + + if (!empty($cids)) { + // Get the cached data. + $cached_data = cache_get_multiple($cids, 'cache_advagg_aggregates'); + + // Set variables from the cache. + if (isset($cached_data[$css_cache_id])) { + $css_cache = $cached_data[$css_cache_id]; + } + if (isset($cached_data[$js_cache_id])) { + $js_cache = $cached_data[$js_cache_id]; + } + } + + // Special handling if the css is loaded via JS. + if (!empty($css_cache) + && empty($js_cache) + && advagg_css_in_js($css_cache) + ) { + // If CSS is being loaded via JavaScript and the css cache is set but the + // js cache is not set; then unset the css cache as well. + unset($css_cache); + } + + // Set to empty arrays on a cache miss. + if (!isset($css_cache)) { + $css_cache = new stdClass(); + } + if (!isset($js_cache)) { + $js_cache = new stdClass(); + } + return array($css_cache, $js_cache, $css_cache_id, $js_cache_id); +} + +/** + * Replacement for template_process_html(). + */ +function _advagg_process_html(&$variables) { + // Don't fail even if the menu router failed. + if (drupal_get_http_header('status') === '404 Not Found') { + // See if the URI contains advagg. + $uri = request_uri(); + if (stripos($uri, '/advagg_') !== FALSE) { + $advagg_items = advagg_menu(); + + // Check css. + $css = reset($advagg_items); + $css_path = key($advagg_items); + $css_path = substr($css_path, 0, strlen($css_path) - 1); + $css_start = strpos($uri, $css_path); + if ($css_start !== FALSE) { + $filename = substr($uri, $css_start + strlen($css_path)); + } + + // Check js. + if (empty($filename)) { + $js = next($advagg_items); + $js_path = key($advagg_items); + $js_path = substr($js_path, 0, strlen($js_path) - 1); + $js_start = strpos($uri, $js_path); + if ($js_start !== FALSE) { + $filename = substr($uri, $js_start + strlen($js_path)); + } + } + + // If we have a filename call the page callback. + if (!empty($filename)) { + $router_item = $css; + if (isset($js)) { + $router_item = $js; + } + + // Include the file if needed. + if ($router_item['file']) { + $included = module_load_include($router_item['file'], 'advagg'); + if (!$included && !function_exists($router_item['page callback'])) { + $file = DRUPAL_ROOT . '/' . drupal_get_path('module', 'advagg') . '/' . $router_item['file']; + if (is_file($file)) { + require_once $file; + } + } + } + + // Call the function. + if (function_exists($router_item['page callback'])) { + // Strip query and fragment form the filename. + if ($pos = strpos($filename, '?')) { + $filename = substr($filename, 0, $pos); + } + if ($pos = strpos($filename, '#')) { + $filename = substr($filename, 0, $pos); + } + // Generate the file. + call_user_func_array($router_item['page callback'], array($filename)); + } + else { + // Report the bigger issue to watchdog. + watchdog('advagg', 'You need to flush your menu cache. This can be done at the top of the performance page. The advagg callback failed while trying to generate this file: @uri', array( + '@performance' => url('admin/config/development/performance'), + '@uri' => $uri, + ), WATCHDOG_CRITICAL); + } + } + } + } + + if (!advagg_enabled()) { + template_process_html($variables); + return; + } + + // Render page_top and page_bottom into top level variables. + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_top'])) { + $variables['page_top'] = drupal_render($variables['page']['page_top']); + } + elseif (!isset($variables['page_top'])) { + $variables['page_top'] = ''; + } + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_bottom'])) { + $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); + } + elseif (!isset($variables['page_bottom'])) { + $variables['page_bottom'] = ''; + } + // Place the rendered HTML for the page body into a top level variable. + if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['#children'])) { + $variables['page'] = $variables['page']['#children']; + } + $advagg_script_alt_scope_scripts = array(); + if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + $prefix = ""; + $suffix = ""; + $variables['page'] = $prefix . $variables['page'] . $suffix; + $prefix = ""; + $suffix = ""; + $variables['page_top'] = $prefix . $variables['page_top'] . $suffix; + $prefix = ""; + $suffix = ""; + $variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix; + + $matches = array(); + preg_match_all('//', $variables['page_top'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + preg_match_all('//', $variables['page'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + preg_match_all('//', $variables['page_bottom'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + } + + // Parts of drupal_get_html_head(). + $elements = drupal_add_html_head(); + if (is_callable('advagg_mod_html_head_post_alter')) { + advagg_mod_html_head_post_alter($elements); + } + + // Get default javascript. + // @see http://drupal.org/node/1279226 + if (function_exists('drupal_add_js_page_defaults')) { + drupal_add_js_page_defaults(); + } + $javascript = array(); + + // Try the render cache. + if (!variable_get('advagg_debug', ADVAGG_DEBUG)) { + // No Alter. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5 && !module_exists('advagg_relocate')) { + // Get all CSS and JS variables needed; running no alters. + list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(TRUE); + list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(TRUE); + + // Get the render cache. + list($css_cache, $js_cache, $css_cache_id_no_alter, $js_cache_id_no_alter) = advagg_get_render_cache($full_css, $js_scope_array); + } + + // With Alter. + if ((empty($css_cache->data) || empty($js_cache->data)) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { + // Get all CSS and JS variables needed; running alters. + list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); + list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(); + + // Get the render cache. + list($css_cache, $js_cache, $css_cache_id, $js_cache_id) = advagg_get_render_cache($full_css, $js_scope_array); + } + } + + // CSS has nice hooks so we don't need to work around it. + if (!empty($css_cache->data)) { + // Use render cache. + list($variables['styles'], $full_css) = $css_cache->data; + } + else { + // Get the css if we have not done so. + if (empty($full_css)) { + list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); + } + // Render the CSS; advagg_pre_render_styles() gets called here. + $variables['styles'] = drupal_render($full_css); + if (!empty($css_cache_id) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { + // Save to the cache. + cache_set($css_cache_id, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + if (!empty($css_cache_id_no_alter) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + // Save to the cache. + cache_set($css_cache_id_no_alter, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + } + + if (module_exists('advagg_font') && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER)) { + $fonts = array(); + foreach ($full_css['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + if (isset($file['advagg_font'])) { + foreach ($file['advagg_font'] as $class => $name) { + $fonts[$class] = $name; + } + } + } + } + } + if (!empty($fonts)) { + if (isset($js_scope_settings_array)) { + $key = key($js_scope_settings_array); + $js_scope_settings_array[$key]['settings']['data'][] = array('advagg_font' => $fonts); + } + drupal_add_js(array('advagg_font' => $fonts), array('type' => 'setting')); + } + } + + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + foreach ($full_css['#groups'] as $groups) { + if (empty($groups['data']) || $groups['type'] === 'inline') { + continue; + } + advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'style'); + } + } + + // JS needs hacks. + // Clear out all old scripts. + if (variable_get('advagg_clear_scripts', ADVAGG_CLEAR_SCRIPTS)) { + $variables['scripts'] = ''; + } + if (!isset($variables['scripts'])) { + $variables['scripts'] = ''; + } + if (!isset($variables['page_bottom']) || !is_string($variables['page_bottom'])) { + $variables['page_bottom'] = ''; + } + + $use_cache = FALSE; + if (!empty($js_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) { + // Use render cache. + $use_cache = TRUE; + $add_to_variables = array(); + // Replace cached settings with current ones. + $js_settings_used = array(); + + $js_scope_settings_array_copy = $js_scope_settings_array; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + if (!empty($js_scope_settings_array_copy['header']) && empty($js_scope_settings_array_copy['footer'])) { + // Copy header settings into the footer. + $js_scope_settings_array_copy['footer'] = $js_scope_settings_array_copy['header']; + } + } + + list($js_cache_data, $js_scope_array) = $js_cache->data; + + foreach ($js_cache_data as $scope => $value) { + $scope_settings = $scope; + if ($scope_settings === 'scripts') { + $scope_settings = 'header'; + } + if ($scope === 'page_bottom') { + $scope_settings = 'footer'; + } + // Search $value for Drupal.settings. + $start = strpos($value, 'jQuery.extend(Drupal.settings,'); + if ($start !== FALSE) { + // If the cache and current settings scope's do not match; do not use + // the cached version. + if (!isset($js_scope_settings_array_copy[$scope_settings]['settings'])) { + $use_cache = FALSE; + break; + } + + // Replace cached Drupal.settings with current Drupal.settings for this + // page. + $merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($js_scope_settings_array_copy[$scope_settings]['settings']['data'], 'is_array'))); + $json_data = advagg_json_encode($merged); + if (!empty($json_data)) { + // Record that this is being used. + $js_settings_used[$scope_settings] = TRUE; + + // Replace the drupal settings string. + $value = advagg_replace_drupal_settings_string($value, $json_data); + } + } + $add_to_variables[$scope] = $value; + } + + if ($use_cache) { + $all_used = array_diff(array_keys($js_scope_settings_array_copy), array_keys($js_settings_used)); + // Ignore this check if the cache level is less than 5. + if (!empty($all_used) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5 && !empty($js_settings_used)) { + // Some js settings did not make it into the output. Skip cache. + $use_cache = FALSE; + } + } + + if ($use_cache) { + // Using the cache; write to the $variables array. + foreach ($add_to_variables as $scope => $value) { + // Set the scope variable if not set. + if (!isset($variables[$scope]) || !is_string($variables[$scope])) { + $variables[$scope] = ''; + } + // Append the js to the scope. + $variables[$scope] .= $value; + } + } + } + + // If the cache isn't used. + if (!$use_cache) { + if (!empty($js_cache->data) && !empty($css_cache->data) && advagg_css_in_js($css_cache)) { + // Render the css so it will be added to the js array; + // advagg_pre_render_styles() gets called here. + $variables['styles'] = drupal_render($full_css); + } + + // Check if the js has changed. + $new_js = drupal_add_js(); + $diff = array_diff(array_keys($new_js), array_keys($javascript)); + if (!empty($diff) || empty($javascript)) { + // Get all JS variables needed again because js changed; or because we + // never got them in the first place. + list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(); + } + + $js_cache = array(); + $js_cache['scripts'] = ''; + if (!empty($js_scope_array)) { + // Add JS to the header and footer of the page. + foreach ($js_scope_array as $scope => &$scripts_array) { + // Add js settings. + if (!empty($js_scope_settings_array[$scope]['settings'])) { + $scripts_array['#items']['settings'] = $js_scope_settings_array[$scope]['settings']; + } + // Render js; advagg_pre_render_scripts() gets called here. + $scripts = drupal_render($scripts_array); + + if ($scope === 'header') { + // Add to the top of this section. + $variables['scripts'] = $scripts . $variables['scripts']; + $js_cache['scripts'] = $scripts . $js_cache['scripts']; + } + // Footer scripts. + elseif ($scope === 'footer') { + // Add to the bottom of this section. + $variables['page_bottom'] .= $scripts; + $js_cache['page_bottom'] = $scripts; + } + // Above css scripts. + elseif ($scope === 'above_css') { + // Put in this new section. + $variables['above_css'] = $scripts; + $js_cache['above_css'] = $scripts; + } + elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + // Scripts in other places. + if (isset($variables[$scope]) + && is_string($variables[$scope]) + && array_key_exists($scope, $GLOBALS['theme_info']->info['regions']) + ) { + // Add to the bottom of this section. + $variables[$scope] .= $scripts; + $js_cache[$scope] = $scripts; + } + elseif (array_search($scope, $advagg_script_alt_scope_scripts, TRUE) !== FALSE) { + // Add to the inline html. + $pos_page_top = strpos($variables['page_top'], ""); + $pos_page = strpos($variables['page'], ""); + $pos_page_bottom = strpos($variables['page_bottom'], ""); + if ($pos_page_top !== FALSE) { + $pos_page_top += strlen(""); + $variables['page_top'] = substr_replace($variables['page_top'], "\n$scripts", $pos_page_top, 0); + $js_cache[$scope] = $scripts; + } + elseif ($pos_page !== FALSE) { + $pos_page += strlen(""); + $variables['page'] = substr_replace($variables['page'], "\n$scripts", $pos_page, 0); + $js_cache[$scope] = $scripts; + } + elseif ($pos_page_bottom !== FALSE) { + $pos_page_bottom += strlen(""); + $variables['page_bottom'] = substr_replace($variables['page_bottom'], "\n$scripts", $pos_page_bottom, 0); + $js_cache[$scope] = $scripts; + } + } + // Add javascript to scripts if we can't find the region in the theme. + elseif (strpos($scope, ':') === FALSE) { + // Add to the bottom of this section. + $variables['scripts'] .= $scripts; + $js_cache['scripts'] .= $scripts; + } + } + } + unset($scripts_array); + + // Clear drupal settings so cache is smaller. + foreach ($js_cache as &$string) { + $string = advagg_replace_drupal_settings_string($string, '{}'); + } + unset($string); + + // Clear drupal settings and not needed items from render cache. + $js_scope_array = array_intersect_key($js_scope_array, array_flip(element_children($js_scope_array))); + foreach ($js_scope_array as $scope => &$scripts_array) { + // Clear element children. + $scripts_array = array_diff_key($scripts_array, array_flip(element_children($scripts_array))); + if (isset($scripts_array['#children'])) { + unset($scripts_array['#children']); + } + // Clear drupal settings. + if (isset($scripts_array['#items']['settings']['data']) && is_array($scripts_array['#items']['settings']['data'])) { + $scripts_array['#items']['settings']['data'] = array(); + } + // Clear printed keys. + if (isset($scripts_array['#printed'])) { + unset($scripts_array['#printed']); + } + // Clear not used groups. + foreach ($scripts_array['#groups'] as $key => $groups) { + if (!isset($groups['items']['files'])) { + unset($scripts_array['#groups'][$key]); + } + } + } + unset($scripts_array); + + if (!empty($js_cache_id) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { + cache_set($js_cache_id, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + if (!empty($js_cache_id_no_alter) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + cache_set($js_cache_id_no_alter, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + } + } + if (!empty($variables['above_css'])) { + $variables['styles'] = $variables['above_css'] . $variables['styles']; + } + + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + foreach ($js_scope_array as $scope => &$scripts_array) { + if ($scope !== 'header' + && $scope !== 'footer' + && $scope !== 'above_css' + && !variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) + ) { + continue; + } + + foreach ($scripts_array['#groups'] as $groups) { + if (empty($groups['data']) || $groups['type'] === 'inline') { + continue; + } + advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'script'); + } + } + } + + $head_elements_before = drupal_add_html_head(); + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + // Prefetch css domains. + foreach ($full_css['#items'] as $file) { + advagg_add_resource_hints_array($file); + } + foreach ($full_css['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + advagg_add_resource_hints_array($file); + } + } + } + + // Prefetch js domains. + foreach ($js_scope_array as $scope_js) { + foreach ($scope_js['#items'] as $file) { + advagg_add_resource_hints_array($file); + } + if (isset($scope_js['#groups'])) { + foreach ($scope_js['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + advagg_add_resource_hints_array($file); + } + } + } + } + } + } + + // Add in preload link headers. + advagg_add_preload_header(); + + // Add in the headers added by advagg. + $head_elements_after = drupal_add_html_head(); + $elements += array_diff_key($head_elements_after, $head_elements_before); + + // Parts of drupal_get_html_head(). + drupal_alter('html_head', $elements); + $head = drupal_render($elements); + if (variable_get('advagg_html_head_in_css_location', ADVAGG_HTML_HEAD_IN_CSS_LOCATION)) { + $variables['styles'] = $head . $variables['styles']; + $variables['head'] = ''; + } + else { + $variables['head'] = $head; + } + + // Remove AdvAgg comments. + if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) + && !empty($advagg_script_alt_scope_scripts) + && !variable_get('theme_debug', FALSE) + ) { + $variables['page_top'] = preg_replace('//', '', $variables['page_top']); + $variables['page'] = preg_replace('//', '', $variables['page']); + $variables['page_bottom'] = preg_replace('//', '', $variables['page_bottom']); + } + + // Output debug info. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $debug = $GLOBALS['_advagg']['debug']; + if (is_callable('httprl_pr')) { + $output = ' ' . httprl_pr($debug); + } + else { + $output = '
    ' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '
    '; + } + watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG); + } +} + +/** + * Replace inline drupal settings script. + * + * @param string $subject + * Inline js. + * @param string $replace + * JS settings replacement. + * + * @return string + * Returns the subject with the replacement in place if this is a drupal + * settings json blob. + */ +function advagg_replace_drupal_settings_string($subject, $replace) { + $start = strpos($subject, 'jQuery.extend(Drupal.settings,'); + if ($start === FALSE) { + return $subject; + } + + // Find the end of the Drupal.settings. + $script_end = stripos($subject, '', $start); + $settings_substring = substr($subject, $start, $script_end - $start); + $json_end = strripos($settings_substring, '});'); + + // Check if LABjs has added an additional wrapper around Drupal settings. + $script_tag_start = strripos(substr($subject, 0, $start), ' $value) { + if (advagg_remove_short_keys($key)) { + if (is_array($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js'][$key])) { + unset($data['ajaxPageState']['js'][$key]); + } + elseif (is_object($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js']->{$key})) { + unset($data['ajaxPageState']['js']->{$key}); + } + } + } + } + // Remove inline css from the ajaxPageState data. + if (isset($data['ajaxPageState']['css'])) { + foreach ((array) $data['ajaxPageState']['css'] as $key => $value) { + if (advagg_remove_short_keys($key, 6)) { + if (is_object($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css']->{$key})) { + unset($data['ajaxPageState']['css']->{$key}); + } + elseif (is_array($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css'][$key])) { + unset($data['ajaxPageState']['css'][$key]); + } + } + } + } + // Remove settings from the js ajaxPageState data. + if (isset($data['ajaxPageState']['js']['settings'])) { + unset($data['ajaxPageState']['js']['settings']); + } + if (isset($data['ajaxPageState']['js']->settings)) { + unset($data['ajaxPageState']['js']->settings); + } + return $data; +} + +/** + * Find dns_prefetch and call advagg_add_dns_prefetch(). + * + * @param array $values + * Attributes added via code for the file. + */ +function advagg_add_resource_hints_array(array $values) { + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { + if (!empty($values['type']) + && ($values['type'] === 'external' || $values['type'] === 'file') + ) { + // Get external domains. + advagg_add_dns_prefetch($values['data']); + } + if (!empty($values['dns_prefetch'])) { + // Grab domains that will be access when this file is loaded. + if (is_array($values['dns_prefetch'])) { + foreach ($values['dns_prefetch'] as $url) { + advagg_add_dns_prefetch($url); + } + } + else { + advagg_add_dns_prefetch($values['dns_prefetch']); + } + } + } + if (!empty($values['preload']) && variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + if (is_array($values['preload'])) { + foreach ($values['preload'] as $url) { + advagg_add_preload_header($url); + } + } + else { + advagg_add_preload_header($values['preload']); + } + } +} + +/** + * Add in the dns-prefetch header for CSS and JS external files. + * + * @param string $url + * The url of the external host. + * + * @return bool + * TRUE if it was added to the head. + */ +function advagg_add_dns_prefetch($url) { + // Keep the order. + $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION); + static $weight = -1001; + if ($advagg_resource_hints_location == 3) { + $weight = -999.9; + } + $weight += 0.0001; + + // Get the host. + $parse = @parse_url($url); + if (empty($parse['host'])) { + // If just the hostname was given, build proper url. + if (strpos($url, '.') && strpos($url, '/') === FALSE) { + $parse['scheme'] = '//'; + $parse['host'] = $url; + // Check for fragment. + $pos = strpos($url, '#'); + if ($pos !== FALSE) { + $parse['fragment'] = substr($url, $pos + 1); + $parse['host'] = substr($url, 0, $pos); + } + // Put it back together and parse again. + $url = advagg_glue_url($parse); + $parse = @parse_url($url); + } + if (empty($parse['host'])) { + return FALSE; + } + } + + // Filter out wrong schemes. + if (!empty($parse['scheme']) + && $parse['scheme'] !== 'http' + && $parse['scheme'] !== 'https' + ) { + return FALSE; + } + + // Filter out local host. + $host = @parse_url($GLOBALS['base_root'], PHP_URL_HOST); + if ($parse['host'] === $host) { + return FALSE; + } + + // Add DNS information for more domains. + if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) { + // Add fonts.gstatic.com when fonts.googleapis.com is added. + advagg_add_dns_prefetch('https://fonts.gstatic.com/#crossorigin'); + } + + // Build render array. + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)) { + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'dns-prefetch', + 'href' => '//' . $parse['host'], + ), + '#weight' => $weight, + ); + // Add markup for dns-prefetch to html_head. + drupal_add_html_head($element, 'advagg_resource_hints_dns_prefetch:' . $parse['host']); + } + if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { + // HTTPS use Protocol Relative; HTTP and scheme defined use given scheme. + $href = '//' . $parse['host']; + if (!$GLOBALS['is_https'] && isset($parse['scheme'])) { + $href = "{$parse['scheme']}://{$parse['host']}"; + } + + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'preconnect', + 'href' => $href, + ), + '#weight' => $weight, + ); + if (!empty($parse['fragment']) && $parse['fragment'] === 'crossorigin') { + $element['#attributes']['crossorigin'] = ''; + } + // Add markup for dns-prefetch to html_head. + drupal_add_html_head($element, 'advagg_resource_hints_preconnect:' . $parse['host']); + } + + // Build render array. Goes after charset tag. + if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') { + // Hacky way to open up a connection to the remote host. + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'prefetch', + 'href' => '//' . $parse['host'] . '/robots.txt', + ), + '#weight' => $weight, + ); + drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']); + } + return TRUE; +} + +/** + * Returns a themed representation of all stylesheets to attach to the page. + * + * It loads the CSS in order, with 'module' first, then 'theme' afterwards. + * This ensures proper cascading of styles so themes can easily override + * module styles through CSS selectors. + * + * Themes may replace module-defined CSS files by adding a stylesheet with the + * same filename. For example, themes/bartik/system-menus.css would replace + * modules/system/system-menus.css. This allows themes to override complete + * CSS files, rather than specific selectors, when necessary. + * + * If the original CSS file is being overridden by a theme, the theme is + * responsible for supplying an accompanying RTL CSS file to replace the + * module's. + * + * @param array $css + * (Optional) An array of CSS files. If no array is provided, the default + * stylesheets array is used instead. + * @param bool $skip_alter + * (Optional) If set to TRUE, this function skips calling drupal_alter() on + * $css, useful when the calling function passes a $css array that has already + * been altered. + * + * @return array + * An array ready to be passed into drupal_render(). + * + * @see drupal_add_css() + */ +function advagg_get_css(array $css = array(), $skip_alter = FALSE) { + if (empty($css)) { + $css = drupal_add_css(); + } + + // Allow modules and themes to alter the CSS items. + if (!$skip_alter) { + advagg_add_default_dns_lookups($css, 'css'); + // Call hook_css_alter(). + drupal_alter('css', $css); + // Call hook_css_post_alter(). + drupal_alter('css_post', $css); + // Call these advagg functions after the hook_css_alter was called. + advagg_fix_type($css, 'css'); + } + + // Sort CSS items, so that they appear in the correct order. + advagg_drupal_sort_css_js_stable($css); + + // Provide the page with information about the individual CSS files used, + // information not otherwise available when CSS aggregation is enabled. The + // setting is attached later in this function, but is set here, so that CSS + // files removed below are still considered "used" and prevented from being + // added in a later AJAX request. + // Skip if no files were added to the page or jQuery.extend() will overwrite + // the Drupal.settings.ajaxPageState.css object with an empty array. + if (!empty($css)) { + // Cast the array to an object to be on the safe side even if not empty. + $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); + } + + // Remove the overridden CSS files. Later CSS files override former ones. + $previous_item = array(); + foreach ($css as $key => $item) { + if ($item['type'] == 'file') { + // If defined, force a unique basename for this file. + $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']); + if (isset($previous_item[$basename])) { + // Remove the previous item that shared the same base name. + unset($css[$previous_item[$basename]]); + } + $previous_item[$basename] = $key; + } + } + + // Remove empty files. + advagg_remove_empty_files($css); + + // Render the HTML needed to load the CSS. + $styles = array( + '#type' => 'styles', + '#items' => $css, + ); + + if (!empty($setting)) { + $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); + } + + return $styles; +} + +/** + * Get full JS array. + * + * Note that hook_js_alter(&$javascript) is called during this function call + * to allow alterations of the JavaScript during its presentation. Calls to + * drupal_add_js() from hook_js_alter() will not be added to the output + * presentation. The correct way to add JavaScript during hook_js_alter() + * is to add another element to the $javascript array, deriving from + * drupal_js_defaults(). See locale_js_alter() for an example of this. + * + * @param array $javascript + * (optional) An array with all JavaScript code. Defaults to the default + * JavaScript array for the given scope. + * @param bool $skip_alter + * (optional) If set to TRUE, this function skips calling drupal_alter() on + * $javascript, useful when the calling function passes a $javascript array + * that has already been altered. + * + * @return array + * The raw JavaScript array. + * + * @see drupal_add_js() + * @see locale_js_alter() + * @see drupal_js_defaults() + */ +function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { + if (empty($javascript)) { + $javascript = drupal_add_js(); + } + + // Return an empty array if + // no javascript is used, + // only the settings array is used and scope is header. + if (empty($javascript) + || (isset($javascript['settings']) && count($javascript) == 1) + ) { + return array(); + } + + // Allow modules to alter the JavaScript. + if (!$skip_alter) { + advagg_add_default_dns_lookups($javascript, 'js'); + if (is_callable('advagg_mod_js_pre_alter')) { + advagg_mod_js_pre_alter($javascript); + } + // Call hook_js_alter(). + drupal_alter('js', $javascript); + // Call hook_js_post_alter(). + drupal_alter('js_post', $javascript); + // Call these advagg functions after the hook_js_alter was called. + advagg_fix_type($javascript, 'js'); + } + elseif (is_callable('advagg_mod_js_move_to_footer')) { + if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 3) { + advagg_mod_js_move_to_footer($javascript); + } + } + + // If in development mode make sure the ajaxPageState css is there. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $have_css = FALSE; + foreach ($javascript['settings']['data'] as $setting) { + if (!empty($setting['ajaxPageState']['css'])) { + $have_css = TRUE; + break; + } + } + if (!$have_css) { + $css = drupal_add_css(); + if (!empty($css)) { + // Cast the array to an object to be on the safe side even if not empty. + $javascript['settings']['data'][]['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); + } + } + } + + // Remove empty files. + advagg_remove_empty_files($javascript); + + return $javascript; +} + +/** + * Returns a themed presentation of all JavaScript code for the current page. + * + * References to JavaScript files are placed in a certain order: first, all + * 'core' files, then all 'module' and finally all 'theme' JavaScript files + * are added to the page. Then, all settings are output, followed by 'inline' + * JavaScript code. If running update.php, all preprocessing is disabled. + * + * Note that hook_js_alter(&$javascript) is called during this function call + * to allow alterations of the JavaScript during its presentation. Calls to + * drupal_add_js() from hook_js_alter() will not be added to the output + * presentation. The correct way to add JavaScript during hook_js_alter() + * is to add another element to the $javascript array, deriving from + * drupal_js_defaults(). See locale_js_alter() for an example of this. + * + * @param string $scope + * (optional) The scope for which the JavaScript rules should be returned. + * Defaults to 'header'. + * @param array $javascript + * (optional) An array with all JavaScript code. Defaults to the default + * JavaScript array for the given scope. + * @param bool $ajax + * (optional) If set to TRUE, this function will not output Drupal.settings. + * + * @return array + * An array ready to be passed into drupal_render() containing all JavaScript + * code segments and includes for the scope as HTML tags. + * + * @see drupal_add_js() + * @see locale_js_alter() + * @see drupal_js_defaults() + */ +function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = FALSE) { + // Keep track of js added for ajaxPageState. + $page_state = &drupal_static(__FUNCTION__, array()); + + // Add in javascript if none was passed in. + if (empty($javascript) && !$ajax) { + $javascript = advagg_get_full_js(); + } + + // Return an empty array if no javascript is used. + if (empty($javascript)) { + return array(); + } + + // Filter out elements of the given scope. + $items = array(); + foreach ($javascript as $key => $item) { + if (!empty($item['scope']) && $item['scope'] === $scope) { + $items[$key] = $item; + } + } + + // Sort the JavaScript so that it appears in the correct order. + advagg_drupal_sort_css_js_stable($items); + + // In Drupal 8, there's a JS_SETTING group for making setting variables + // appear last after libraries have loaded. In Drupal 7, this is forced + // without that group. We do not use the $key => $item type of iteration, + // because PHP uses an internal array pointer for that, and we're modifying + // the array order inside the loop. + if ($scope === 'footer' && !empty($items['settings'])) { + // Remove settings array from items. + $settings_js['settings'] = $items['settings']; + unset($items['settings']); + + // Move $settings_js to the bottom of the js that was added to the + // header, but has now been moved to the footer via advagg_mod. + $counter = 0; + foreach ($items as $key => $item) { + if ($item['group'] > 9000) { + advagg_array_splice_assoc($items, $counter, 0, $settings_js); + unset($settings_js); + break; + } + ++$counter; + } + // Nothing in the footer, add settings to the bottom of the array. + if (isset($settings_js)) { + $items = array_merge($items, $settings_js); + } + } + else { + foreach (array_keys($items) as $key) { + if ($items[$key]['type'] === 'setting') { + $item = $items[$key]; + unset($items[$key]); + $items[$key] = $item; + } + } + } + + // Provide the page with information about the individual JavaScript files + // used, information not otherwise available when aggregation is enabled. + $page_state = array_merge($page_state, array_fill_keys(array_keys($items), 1)); + + // If we're outputting the header scope, then this should be the final time + // that drupal_get_js() is running, so add the setting to this output as well + // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's + // because drupal_get_js() was intentionally passed a $javascript argument + // stripped of settings, potentially in order to override how settings get + // output, so in this case, do not add the setting to this output. + // Also output the settings if we have pushed all javascript to the footer. + if (isset($items['settings'])) { + $items['settings']['data'][] = array( + 'ajaxPageState' => array( + 'js' => $page_state, + ), + ); + } + + // Do not include jQuery.extend(Drupal.settings) if the output is ajax. + if ($ajax) { + unset($items['settings']['data']); + } + + // Semi support of the attributes array. + foreach ($items as $key => $item) { + if (!isset($item['attributes'])) { + continue; + } + if (isset($item['attributes']['defer'])) { + $items[$key]['defer'] = $item['attributes']['defer']; + } + if (isset($item['attributes']['async'])) { + $items[$key]['async'] = $item['attributes']['async']; + } + if (isset($item['attributes']['onload'])) { + $items[$key]['onload'] = $item['attributes']['onload']; + } + if (isset($item['attributes']['onerror'])) { + $items[$key]['onerror'] = $item['attributes']['onerror']; + } + } + + // Render the HTML needed to load the JavaScript. + $elements = array( + '#type' => 'scripts', + '#items' => $items, + ); + + // Aurora and Omega themes uses alter without checking previous value. + if (variable_get('advagg_enforce_scripts_callback', TRUE)) { + // Get the element_info for scripts. + $scripts = element_info('scripts'); + if (empty($scripts) || $scripts['#aggregate_callback'] !== '_advagg_aggregate_js') { + // Directly alter the static. + $element_info = &drupal_static('element_info'); + advagg_element_info_alter($element_info); + if (function_exists('advagg_mod_element_info_alter')) { + advagg_mod_element_info_alter($element_info); + } + } + } + + // Remove ajaxPageState CSS/JS from Drupal.settings if ajax.js is not used. + if (function_exists('advagg_mod_js_no_ajaxpagestate')) { + if (variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE)) { + advagg_mod_js_no_ajaxpagestate($elements); + } + } + return $elements; +} + +/** + * Remove a portion of the array and replace it with something else. + * + * @param array $input + * The input array. + * @param int $offset + * If offset is positive then the start of removed portion is at that offset + * from the beginning of the input array. If offset is negative then it starts + * that far from the end of the input array. + * @param int $length + * If length is omitted, removes everything from offset to the end of the + * array. If length is specified and is positive, then that many elements will + * be removed. If length is specified and is negative then the end of the + * removed portion will be that many elements from the end of the array. Tip: + * to remove everything from offset to the end of the array when replacement + * is also specified, use count($input) for length. + * @param mixed $replacement + * If replacement array is specified, then the removed elements are replaced + * with elements from this array. + * If offset and length are such that nothing is removed, then the elements + * from the replacement array are inserted in the place specified by the + * offset. Note that keys in replacement array are preserved. + * If replacement is just one element it is not necessary to put array() + * around it, unless the element is an array itself, an object or NULL. + * + * @see http://php.net/array-splice#111204 + */ +function advagg_array_splice_assoc(array &$input, $offset, $length, $replacement) { + $replacement = (array) $replacement; + $key_indices = array_flip(array_keys($input)); + if (isset($input[$offset]) && is_string($offset)) { + $offset = $key_indices[$offset]; + } + if (isset($input[$length]) && is_string($length)) { + $length = $key_indices[$length] - $offset; + } + + $input = array_slice($input, 0, $offset, TRUE) + $replacement + array_slice($input, $offset + $length, NULL, TRUE); +} + +/** + * Callback for array_filter. Will return FALSE if strlen < 3. + * + * @param string $value + * A value from an array/object. + * @param int $min_len + * The strlen check length. + * + * @return bool + * TRUE or FALSE. + */ +function advagg_remove_short_keys($value, $min_len = 3) { + if (strlen($value) < $min_len) { + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Get all javascript scopes set in the $javascript array. + * + * @param array $javascript + * An array with all JavaScript code. + * + * @return array + * Array of scopes that are currently being used. + */ +function advagg_get_js_scopes(array $javascript) { + // Return if nothing given to us. + if (empty($javascript)) { + return array(); + } + + // Filter out elements of the given scope. + $scopes = array(); + $js_settings_in_footer = FALSE; + foreach ($javascript as $name => $item) { + // Skip if the scope is not set. + if (!is_array($item) || empty($item['scope'])) { + continue; + } + if (!isset($scopes[$item['scope']])) { + $scopes[$item['scope']] = TRUE; + } + if ($name === 'settings' && $item['scope'] === 'footer') { + $js_settings_in_footer = TRUE; + } + } + + // Default to header if nothing found. + if (empty($scopes)) { + $scopes['header'] = TRUE; + } + + // Process header last. + if (isset($scopes['header']) && count($scopes) > 1) { + $temp = $scopes['header']; + unset($scopes['header']); + $scopes['header'] = $temp; + } + + // Process footer last if everything has been moved to the footer. + if (isset($scopes['footer']) + && count($scopes) > 1 + && $js_settings_in_footer + ) { + $temp = $scopes['footer']; + unset($scopes['footer']); + $scopes['footer'] = $temp; + } + + // Return the scopes. + return $scopes; +} + +/** + * Apply the advagg changes to the $css_js_groups array. + * + * @param array $css_js_groups + * An array of CSS or JS groups as returned by drupal_group_css/js(). + * @param array $plans + * An array of changes to do to the $css_js_groups array. + * + * @return array + * New version of $css_js_groups. + */ +function advagg_merge_plans(array $css_js_groups, array $plans) { + $used_keys = array(); + foreach ($plans as $plan) { + $plan_added = FALSE; + foreach ($css_js_groups as $key => $group) { + // Remove files from the old css/js array. + $file_removed = FALSE; + foreach ($css_js_groups[$key]['items'] as $k => $values) { + if (is_array($values) + && array_key_exists('data', $values) + && is_array($plan['items']['files']) + && is_string($values['data']) + ) { + // If the CSS is a split file, the first file is very meaningful, and + // is probably the only file. + $first_file = reset($plan['items']['files']); + if (array_key_exists($values['data'], $plan['items']['files'])) { + unset($css_js_groups[$key]['items'][$k]); + $file_removed = TRUE; + } + // This part will try to add each split part matching the original CSS + // path and only remove the original group if the current part is the + // last part. + elseif (!empty($first_file['split'])) { + if ($values['data'] == $first_file['split_original']) { + if (!empty($first_file['split_last_part'])) { + unset($css_js_groups[$key]['items'][$k]); + } + $file_removed = TRUE; + } + } + } + } + + // Replace first file of the old css/js array with one from advagg. + if ($file_removed && !$plan_added) { + $step = 0; + do { + ++$step; + $insert_key = '' . floatval($key) . '.' . sprintf('%03d', $step); + } while (array_key_exists($insert_key, $css_js_groups)); + $css_js_groups[(string) $insert_key] = $plan; + $plan_added = TRUE; + } + } + + // Remove old css/js grouping if no files are left in it. + foreach ($css_js_groups as $key => $group) { + if (empty($css_js_groups[$key]['items'])) { + unset($css_js_groups[$key]); + } + } + + if (!$plan_added) { + foreach ($css_js_groups as $key => $group) { + if (empty($group['items']['aggregate_filenames_hash']) + || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash'] + || empty($group['items']['aggregate_contents_hash']) + || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash'] + ) { + continue; + } + + // Insert a unique key. + do { + $key = '' . (floatval($key) + 0.01); + } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys)); + $used_keys[(string) $key] = TRUE; + $css_js_groups[(string) $key] = $plan; + $plan_added = TRUE; + break; + } + } + + } + + // Key sort and normalize the array before returning it. + ksort($css_js_groups); + $css_js_groups = array_values($css_js_groups); + return $css_js_groups; +} + +/** + * Function used to see if aggregation is enabled. + * + * @return bool + * The value of the advagg_enabled variable. + */ +function advagg_enabled() { + $init = &drupal_static(__FUNCTION__); + + if (!empty($init)) { + return variable_get('advagg_enabled', ADVAGG_ENABLED); + } + + // Set base_path if not set. + if (empty($GLOBALS['base_path'])) { + $GLOBALS['base_path'] = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/') . '/'; + } + + $init = TRUE; + // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x. + if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) { + if (!db_table_exists('advagg_aggregates_versions')) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + if (user_access('administer site configuration')) { + drupal_set_message(t('Please run database updates. AdvAgg will remain disabled until done.', array('@link' => url('update.php'))), 'error'); + } + } + else { + variable_del('advagg_needs_update'); + } + } + else { + // Get values and fill in defaults if needed. + $config_path = advagg_admin_config_root_path(); + $current_path = current_path(); + $arg = arg(); + $arg += array(1 => '', 2 => '', 3 => '', 4 => '', 5 => ''); + $admin_theme = variable_get('admin_theme'); + + // List of all the pages which will not have Advanced Aggregator enabled. + $list_of_pages = variable_get('advagg_disable_on_listed_pages'); + + $pages = trim(drupal_strtolower($list_of_pages)); + // Convert the Drupal path to lowercase. + $path = drupal_strtolower(drupal_get_path_alias(current_path())); + + // Compare the lowercase internal and lowercase path alias (if any). + $page_match = drupal_match_path($path, $pages); + if ($page_match) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + + // Disable advagg if on admin page and configured to do so. + // AND theme is admin theme + // AND NOT /admin/reports/status + // AND NOT /admin/config/development/performance/. + // AND NOT /admin/appearance/settings/*. + // AND NOT /admin/config/development/performance/advagg/*. + if (variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN) + && $GLOBALS['theme'] === $admin_theme + && path_is_admin($current_path) + && !($arg[1] === 'reports' && $arg[2] === 'status') + && !($arg[2] === 'development' && $arg[3] === 'performance' && empty($arg[4])) + && !($arg[1] === 'appearance' && $arg[2] === 'settings' && !empty($arg[3])) + && stripos($current_path, $config_path . '/advagg') !== 0 + ) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + + // Check if the advagg cookie is set. + $cookie_name = 'AdvAggDisabled'; + $bypass_cookie = FALSE; + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + $bypass_cookie = TRUE; + } + + // Allow for AdvAgg to be enabled per request. + if (isset($_GET['advagg']) + && $_GET['advagg'] == 1 + && !defined('MAINTENANCE_MODE') + && (user_access('bypass advanced aggregation') || $bypass_cookie) + ) { + $GLOBALS['conf']['advagg_enabled'] = TRUE; + $GLOBALS['conf']['preprocess_css'] = TRUE; + $GLOBALS['conf']['preprocess_js'] = TRUE; + } + + // Disable AdvAgg if maintenance mode is defined. + if (defined('MAINTENANCE_MODE')) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + } + // Only run code below if advagg is enabled. + if (variable_get('advagg_enabled', ADVAGG_ENABLED)) { + // Do not use AdvAgg or preprocessing functions if the disable cookie is + // set. + if ($bypass_cookie && !isset($_GET['advagg'])) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + $bypass_cookie = TRUE; + + // Let the user know that the AdvAgg bypass cookie is currently set. + static $msg_set; + if (!isset($msg_set) && variable_get('advagg_show_bypass_cookie_message', ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE)) { + $msg_set = TRUE; + if (user_access('administer site configuration')) { + drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by going to the AdvAgg Operations page and clicking the Toggle the "aggregation bypass cookie" for this browser button.', array( + '@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array('fragment' => 'edit-bypass')), + ))); + } + else { + drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by logging in with a user with the "administer site configuration" permissions and going to the AdvAgg Operations page (located at @advagg_operations) and clicking the Toggle the "aggregation bypass cookie" for this browser button.', array( + '@login' => 'user/login', + '@advagg_operations' => advagg_admin_config_root_path() . '/advagg/operations', + ))); + } + } + } + // Disable advagg if requested. + if (isset($_GET['advagg']) + && $_GET['advagg'] == -1 + && (user_access('bypass advanced aggregation') || $bypass_cookie) + ) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + // Disable core preprocessing if requested. + if (isset($_GET['advagg-core']) + && $_GET['advagg-core'] == 0 + && (user_access('bypass advanced aggregation') || $bypass_cookie) + ) { + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + // Enable core preprocessing if requested. + if (isset($_GET['advagg-core']) + && $_GET['advagg-core'] == 1 + && (user_access('bypass advanced aggregation') || $bypass_cookie) + ) { + $GLOBALS['conf']['preprocess_css'] = TRUE; + $GLOBALS['conf']['preprocess_js'] = TRUE; + } + // Enable debugging if requested. + if (isset($_GET['advagg-debug']) + && (user_access('bypass advanced aggregation') || $bypass_cookie) + ) { + // Cast to an int. + $GLOBALS['conf']['advagg_debug'] = (int) $_GET['advagg-debug']; + } + } + } + + return variable_get('advagg_enabled', ADVAGG_ENABLED); +} + +/** + * Get the current path used for advagg admin configuration. + * + * @return string + * Path to root advagg config. + */ +function advagg_admin_config_root_path() { + return variable_get('advagg_admin_config_root_path', ADVAGG_ADMIN_CONFIG_ROOT_PATH); +} + +/** + * Get an array of all hooks and settings that affect aggregated files contents. + * + * @return array + * array('variables' => array(...), 'hooks' => array(...)) + */ +function advagg_current_hooks_hash_array() { + $aggregate_settings = &drupal_static(__FUNCTION__); + if (!empty($aggregate_settings)) { + return $aggregate_settings; + } + list($css_path, $js_path) = advagg_get_root_files_dir(); + + // Put all enabled hooks and settings into a big array. + $aggregate_settings = array( + 'variables' => array( + 'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP), + 'advagg_brotli' => variable_get('advagg_brotli', ADVAGG_BROTLI), + 'advagg_no_zopfli' => variable_get('advagg_no_zopfli', ADVAGG_NO_ZOPFLI), + 'is_https' => $GLOBALS['is_https'], + 'advagg_global_counter' => advagg_get_global_counter(), + 'base_path' => $GLOBALS['base_path'], + 'advagg_ie_css_selector_limiter' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER), + 'advagg_ie_css_selector_limiter_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), + 'advagg_scripts_scope_anywhere' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE), + 'advagg_devel' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 ? TRUE : FALSE, + 'advagg_convert_absolute_to_relative_path' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH), + 'advagg_convert_absolute_to_protocol_relative_path' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH), + 'advagg_css_absolute_path' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH), + 'advagg_force_https_path' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH), + 'advagg_css_dir' => $css_path[0], + 'advagg_js_dir' => $js_path[0], + ), + 'hooks' => advagg_hooks_implemented(FALSE), + ); + // Add in language if locale is enabled. + if (module_exists('locale')) { + $aggregate_settings['variables']['language'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : ''; + } + + // Add the base url if so desired to. + if (variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL)) { + $aggregate_settings['variables']['base_url'] = $GLOBALS['base_url']; + } + + // CDN module settings. + // Patch in https://www.drupal.org/node/1942230#comment-7927171 is fine + // on a technical level but I got frustrated with all the reports about it not + // working with no good reason as to why it doesn't work. + if (!function_exists('cdn_advagg_current_hooks_hash_array_alter') && module_exists('cdn')) { + $aggregate_settings['variables'][CDN_MODE_VARIABLE] = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC); + $aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE] = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT); + $aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE] = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE); + $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); + $aggregate_settings['variables']['cdn_request_is_https'] = cdn_request_is_https(); + $aggregate_settings['variables']['cdn_check_drupal_path'] = cdn_check_drupal_path($_GET['q']); + } + + // Allow other modules to add in their own settings and hooks. + // Call hook_advagg_current_hooks_hash_array_alter(). + drupal_alter('advagg_current_hooks_hash_array', $aggregate_settings); + + return $aggregate_settings; +} + +/** + * Get the hash of all hooks and settings that affect aggregated files contents. + * + * @return string + * hash value. + */ +function advagg_get_current_hooks_hash() { + $current_hash = &drupal_static(__FUNCTION__); + + if (empty($current_hash)) { + // Get all advagg hooks and variables in use. + $aggregate_settings = advagg_current_hooks_hash_array(); + + // Generate the hash. + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $current_hash = drupal_hash_base64($serialize_function($aggregate_settings)); + + // Save into variables for verification purposes later on if not found. + $settings = advagg_get_hash_settings($current_hash); + if (empty($settings)) { + // Save new hash into. + advagg_set_hash_settings($current_hash, $aggregate_settings); + } + } + + return $current_hash; +} + +/** + * Store settings associated with hash. + * + * @param string $hash + * The hash. + * @param array $settings + * The settings associated with this hash. + * + * @return MergeQuery + * value from db_merge + */ +function advagg_set_hash_settings($hash, array $settings = array()) { + return db_merge('advagg_aggregates_hashes') + ->key(array('hash' => $hash)) + ->fields(array( + 'hash' => $hash, + 'settings' => serialize($settings), + )) + ->execute(); +} + +/** + * Get back what hooks are implemented. + * + * @param bool $all + * If TRUE get all hooks related to css/js files. + * if FALSE get only the subset of hooks that alter the filename/contents. + * + * @return array + * List of hooks and what modules have implemented them. + */ +function advagg_hooks_implemented($all = TRUE) { + // Get hooks in use. + $hooks = array( + 'advagg_get_css_file_contents_pre_alter' => array(), + 'advagg_get_css_file_contents_alter' => array(), + 'advagg_get_css_aggregate_contents_alter' => array(), + 'advagg_get_js_file_contents_alter' => array(), + 'advagg_get_js_aggregate_contents_alter' => array(), + 'advagg_save_aggregate_pre_alter' => array(), + 'advagg_save_aggregate_alter' => array(), + 'advagg_current_hooks_hash_array_alter' => array(), + 'advagg_get_root_files_dir_alter' => array(), + 'advagg_context_alter' => array(), + ); + if ($all) { + $hooks += array( + 'advagg_build_aggregate_plans_alter' => array(), + 'advagg_build_aggregate_plans_post_alter' => array(), + 'advagg_changed_files' => array(), + 'advagg_css_groups_alter' => array(), + 'advagg_js_groups_alter' => array(), + 'advagg_modify_css_pre_render_alter' => array(), + 'advagg_modify_js_pre_render_alter' => array(), + 'advagg_get_info_on_files_alter' => array(), + 'advagg_hooks_implemented_alter' => array(), + 'advagg_removed_aggregates' => array(), + 'advagg_scan_for_changes' => array(), + 'advagg_missing_root_file' => array(), + 'js_alter' => array(), + 'css_alter' => array(), + ); + } + // Call hook_advagg_hooks_implemented_alter(). + drupal_alter('advagg_hooks_implemented', $hooks, $all); + + // Cache module_implements as this will load up .inc files. + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64($serialize_function($hooks)); + $cache = cache_get($cid, 'cache_bootstrap'); + if (!empty($cache->data)) { + $hooks = $cache->data; + } + else { + foreach ($hooks as $hook => $values) { + $hooks[$hook] = module_implements($hook); + + // Also check themes as drupal_alter() allows for themes to alter things. + $theme_keys = array_keys(list_themes()); + if (!empty($theme_keys)) { + foreach ($theme_keys as $theme_key) { + $function = $theme_key . '_' . $hook; + if (function_exists($function)) { + $hooks[$hook][] = $theme_key; + } + } + } + } + cache_set($cid, $hooks, 'cache_bootstrap', CACHE_TEMPORARY); + } + + return $hooks; +} + +/** + * Returns the hashes settings. + * + * @param string $hash + * The name of the variable to return. + * + * @return array + * The settings array or an empty array if not found. + */ +function advagg_get_hash_settings($hash) { + $settings = db_select('advagg_aggregates_hashes', 'aah') + ->fields('aah', array('settings')) + ->condition('hash', $hash) + ->execute() + ->fetchField(); + + return !empty($settings) ? unserialize($settings) : array(); +} + +/** + * Get the CSS and JS path for advagg. + * + * @param bool $reset + * Set to TRUE to reset the static variables. + * + * @return array + * Example return below: + * + * @code + * array( + * array( + * public://advagg_css, + * sites/default/files/advagg_css, + * ), + * array( + * public://advagg_js, + * sites/default/files/advagg_js, + * ), + * ) + * @endcode + */ +function advagg_get_root_files_dir($reset = FALSE) { + $css_paths = &drupal_static(__FUNCTION__ . '_css'); + $js_paths = &drupal_static(__FUNCTION__ . '_js'); + + // Make sure directories are available and writable. + if (empty($css_paths) || empty($js_paths) || $reset) { + // Default is public://. + $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX); + $css_paths[0] = $prefix . 'advagg_css'; + $js_paths[0] = $prefix . 'advagg_js'; + + file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); + file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); + + // Set the URI of the directory. + $css_paths[1] = advagg_get_relative_path($css_paths[0], 'css'); + $js_paths[1] = advagg_get_relative_path($js_paths[0], 'js'); + + // If the css or js got a path, use it for the other missing one. + if (empty($css_paths[1]) && !empty($js_paths[1])) { + $css_paths[1] = str_replace('/advagg_js', '/advagg_css', $js_paths[1]); + } + elseif (empty($js_paths[1]) && !empty($css_paths[1])) { + $js_paths[1] = str_replace('/advagg_css', '/advagg_js', $css_paths[1]); + } + + // Fix if empty. + if (empty($css_paths[1])) { + $css_paths[1] = $css_paths[0]; + } + if (empty($js_paths[1])) { + $js_paths[1] = $js_paths[0]; + } + + // Allow other modules to alter css and js paths. + // Call hook_advagg_get_root_files_dir_alter() + drupal_alter('advagg_get_root_files_dir', $css_paths, $js_paths); + } + + return array($css_paths, $js_paths); +} + +/** + * Given a uri, get the relative_path. + * + * @param string $uri + * The uri for the stream wrapper. + * @param string $type + * (Optional) String: css or js. + * + * @return string + * The relative path of the uri. + * + * @see https://www.drupal.org/node/837794#comment-9124435 + */ +function advagg_get_relative_path($uri, $type = '') { + $wrapper = file_stream_wrapper_get_instance_by_uri($uri); + if ($wrapper instanceof DrupalLocalStreamWrapper) { + $relative_path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); + } + else { + $relative_path = parse_url(file_create_url($uri), PHP_URL_PATH); + if (empty($relative_path) && !empty($uri)) { + $filename = advagg_generate_advagg_filename_from_db($type); + $relative_path = parse_url(file_create_url("{$uri}/{$filename}"), PHP_URL_PATH); + $end = strpos($relative_path, "/{$filename}"); + if ($end !== FALSE) { + $relative_path = substr($relative_path, 0, $end); + } + } + if (substr($relative_path, 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) { + $relative_path = substr($relative_path, strlen($GLOBALS['base_path'])); + } + $relative_path = ltrim($relative_path, '/'); + } + return $relative_path; +} + +/** + * Builds the requested CSS/JS aggregates. + * + * @param array $filenames + * Array of AdvAgg filenames to generate. + * @param string $type + * String: css or js. + * + * @return array + * Array keyed by filename, value is result from advagg_missing_create_file(). + */ +function advagg_build_aggregates(array $filenames, $type) { + // Check input values. + if (empty($filenames)) { + return array(); + } + if (empty($type)) { + $filename = reset($filenames); + $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + } + + // Call the file generation function directly. + module_load_include('inc', 'advagg', 'advagg.missing'); + list($css_path, $js_path) = advagg_get_root_files_dir(); + $return = array(); + foreach ($filenames as $filename) { + // Skip if the file exists. + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + if (file_exists($uri)) { + continue; + } + + // Only create the file if we have a lock. + $lock_name = 'advagg_' . $filename; + if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { + $return[$filename] = advagg_missing_create_file($filename); + } + elseif (lock_acquire($lock_name, 10)) { + $return[$filename] = advagg_missing_create_file($filename); + lock_release($lock_name); + } + } + return $return; +} + +/** + * Gets the core CSS/JS included in this ajax request. + * + * Used so core JS can be rendered through the AdvAgg pipeline. + * + * @see ajax_render() + * + * @return array + * Returns an array containing $styles, $scripts_header, $scripts_footer, + * $items, and $settings. + */ +function advagg_build_ajax_js_css() { + $settings = array(); + // Ajax responses aren't rendered with html.tpl.php, so we have to call + // drupal_get_css() and drupal_get_js() here, in order to have new files added + // during this request to be loaded by the page. We only want to send back + // files that the page hasn't already loaded, so we implement simple diffing + // logic using array_diff_key(). + foreach (array('css', 'js') as $type) { + // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty, + // since the base page ought to have at least one JS file and one CSS file + // loaded. It probably indicates an error, and rather than making the page + // reload all of the files, instead we return no new files. + if (empty($_POST['ajax_page_state'][$type])) { + $items[$type] = array(); + $scripts = drupal_add_js(); + if (!empty($scripts['settings'])) { + $settings = $scripts['settings']; + } + } + else { + $function = 'drupal_add_' . $type; + // Get the current css/js needed for this page. + $items[$type] = $function(); + // Call hook_js_alter() OR hook_css_alter(). + drupal_alter($type, $items[$type]); + // @todo Inline CSS and JS items are indexed numerically. These can't be + // reliably diffed with array_diff_key(), since the number can change + // due to factors unrelated to the inline content, so for now, we strip + // the inline items from Ajax responses, and can add support for them + // when drupal_add_css() and drupal_add_js() are changed to use a hash + // of the inline content as the array key. + foreach ($items[$type] as $key => $item) { + if (is_numeric($key)) { + unset($items[$type][$key]); + } + } + // Ensure that the page doesn't reload what it already has. + // @ignore security_17 + $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]); + } + } + + // Render the HTML to load these files, and add AJAX commands to insert this + // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the + // data from being altered again, as we already altered it above. Settings are + // handled separately, afterwards. + $scripts = drupal_add_js(); + if (isset($scripts['settings'])) { + $settings = $scripts['settings']; + unset($items['js']['settings']); + } + + $styles = drupal_get_css($items['css'], TRUE); + $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); + $scripts_header = drupal_get_js('header', $items['js'], TRUE); + return array($styles, $scripts_header, $scripts_footer, $items, $settings); +} + +/** + * Given the type lets us know if advagg is enabled or disabled. + * + * @param string $type + * String: css or js. + * + * @return bool + * TRUE or FALSE. + */ +function advagg_file_aggregation_enabled($type) { + if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'update') { + return FALSE; + } + if (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) { + return FALSE; + } + if ($type === 'css') { + return variable_get('preprocess_css', FALSE); + } + if ($type === 'js') { + return variable_get('preprocess_js', FALSE); + } +} + +/** + * Update atime inside advagg_aggregates_versions and cache_advagg_info. + * + * @param array $files + * List of files in the aggregate as well as the aggregate name. + * + * @return bool + * Return TRUE if anything was written to the database. + */ +function advagg_multi_update_atime(array $files) { + $write_done = FALSE; + $records = array(); + foreach ($files as $values) { + // Create the cache id. + $cid = 'advagg:db:' . $values['aggregate_filenames_hash'] . ADVAGG_SPACE . $values['aggregate_contents_hash']; + // Create the db record. + $records[$cid] = array( + 'aggregate_filenames_hash' => $values['aggregate_filenames_hash'], + 'aggregate_contents_hash' => $values['aggregate_contents_hash'], + 'atime' => REQUEST_TIME, + ); + } + + // Use the cache. + $cids = array_keys($records); + $caches = cache_get_multiple($cids, 'cache_advagg_info'); + if (!empty($caches)) { + foreach ($caches as $cache) { + // See if the atime value needs to be updated. + if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) { + // If atime is less than 12 hours old, do nothing. + unset($records[$cache->cid]); + } + } + } + if (empty($records)) { + return $write_done; + } + + foreach ($records as $cid => $record) { + // Update atime in DB. + $result = db_merge('advagg_aggregates_versions') + ->key(array( + 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], + 'aggregate_contents_hash' => $record['aggregate_contents_hash'], + )) + ->fields(array('atime' => $record['atime'])) + ->execute(); + if (!$write_done && $result) { + $write_done = TRUE; + } + + // Update the atime in the cache. + // Get fresh copy of the cache. + $cache = cache_get($cid, 'cache_advagg_info'); + // Set the atime. + if (empty($cache->data)) { + $cache = new stdClass(); + } + $cache->data['atime'] = REQUEST_TIME; + + // Write to the cache. + // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. + // The random 0 to 45 day addition is to prevent a cache stampede. + cache_set($cid, $cache->data, 'cache_advagg_info', round(REQUEST_TIME + 1209600 + mt_rand(0, 3888000), -3)); + } + return $write_done; +} + +/** + * Return the advagg_global_counter variable. + * + * @return int + * Int value. + */ +function advagg_get_global_counter() { + $global_counter = variable_get('advagg_global_counter', ADVAGG_GLOBAL_COUNTER); + + return $global_counter; +} + +/** + * Cache clear callback for admin_menu/flush-cache/advagg. + */ +function advagg_admin_flush_cache() { + module_load_include('inc', 'advagg', 'advagg.admin'); + advagg_admin_flush_cache_button(); +} + +/** + * Returns HTML for a generic HTML tag with attributes. + * + * @param array $variables + * An associative array containing: + * - element: An associative array describing the tag: + * - #tag: The tag name to output. Typical tags added to the HTML HEAD: + * - meta: To provide meta information, such as a page refresh. + * - link: To refer to stylesheets and other contextual information. + * - script: To load JavaScript. + * - #attributes: (optional) An array of HTML attributes to apply to the + * tag. + * - #value: (optional) A string containing tag content, such as inline + * CSS. + * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA + * wrapper prefix. + * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA + * wrapper suffix. + */ +function theme_html_script_tag(array $variables) { + $element = $variables['element']; + $attributes = ''; + $onload = ''; + $onerror = ''; + if (isset($element['#attributes'])) { + // On Load. + if (!empty($element['#attributes']['onload'])) { + $onload = $element['#attributes']['onload']; + unset($element['#attributes']['onload']); + } + // On Error. + if (!empty($element['#attributes']['onerror'])) { + $onerror = $element['#attributes']['onerror']; + unset($element['#attributes']['onerror']); + } + $attributes = !empty($element['#attributes']) ? drupal_attributes($element['#attributes']) : ''; + if (!empty($onload)) { + $attributes .= ' onload="' . advagg_jsspecialchars($onload) . '"'; + } + if (!empty($onerror)) { + $attributes .= ' onerror="' . advagg_jsspecialchars($onerror) . '"'; + } + } + + if (!isset($element['#value'])) { + return '<' . $element['#tag'] . $attributes . " />\n"; + } + else { + $output = '<' . $element['#tag'] . $attributes . '>'; + if (isset($element['#value_prefix'])) { + $output .= $element['#value_prefix']; + } + $output .= $element['#value']; + if (isset($element['#value_suffix'])) { + $output .= $element['#value_suffix']; + } + $output .= '\n"; + return $output; + } +} + +/** + * Replace quotes with the html version of it. + * + * @param string $string + * Input string. Convert quotes to html chars. + * + * @return string + * Transformed string. + */ +function advagg_jsspecialchars($string = '') { + $string = str_replace('"', '"', $string); + $string = str_replace("'", ''', $string); + + return $string; +} + +/** + * Callback for pre_render to add elements needed for JavaScript to be rendered. + * + * This function evaluates the aggregation enabled/disabled condition on a group + * by group basis by testing whether an aggregate file has been made for the + * group rather than by testing the site-wide aggregation setting. This allows + * this function to work correctly even if modules have implemented custom + * logic for grouping and aggregating files. + * + * @param array $elements + * A render array containing: + * - #items: The JavaScript items as returned by drupal_add_js() and + * altered by drupal_get_js(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of JavaScript tags. + * + * @see drupal_get_js() + */ +function advagg_pre_render_scripts(array $elements) { + // Don't run it twice. + if (!empty($elements['#groups'])) { + return $elements; + } + + // Group and aggregate the items. + if (isset($elements['#group_callback'])) { + // Call advagg_group_js(). + $elements['#groups'] = $elements['#group_callback']($elements['#items']); + } + if (isset($elements['#aggregate_callback'])) { + // Call _advagg_aggregate_js(). + $elements['#aggregate_callback']($elements['#groups']); + } + + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. Files that should not be cached (see drupal_add_js()) + // get REQUEST_TIME as query-string instead, to enforce reload on every + // page request. + $default_query_string = variable_get('css_js_query_string', '0'); + + // For inline JavaScript to validate as XHTML, all JavaScript containing + // XHTML needs to be wrapped in CDATA. To make that backwards compatible + // with HTML 4, we need to comment out the CDATA-tag. + $embed_prefix = "\n\n"; + + // Since JavaScript may look for arguments in the URL and act on them, some + // third-party code might require the use of a different query string. + $js_version_string = variable_get('drupal_js_version_query_string', 'v='); + + // Defaults for each SCRIPT element. + $element_defaults = array( + '#type' => 'html_script_tag', + '#tag' => 'script', + '#value' => '', + '#attributes' => array( + 'type' => 'text/javascript', + ), + ); + $hooks = theme_get_registry(FALSE); + if (empty($hooks['html_script_tag'])) { + $element_defaults['#type'] = 'html_tag'; + } + + // Loop through each group. + foreach ($elements['#groups'] as $group) { + // If a group of files has been aggregated into a single file, + // $group['data'] contains the URI of the aggregate file. Add a single + // script element for this file. + if (isset($group['type']) && $group['type'] === 'file' && isset($group['data'])) { + $element = $element_defaults; + $element['#attributes']['src'] = advagg_file_create_url($group['data']) . ($group['cache'] ? '' : '?' . REQUEST_TIME); + $element['#browsers'] = $group['browsers']; + if (!empty($group['defer'])) { + $element['#attributes']['defer'] = 'defer'; + } + if (!empty($group['async'])) { + $element['#attributes']['async'] = 'async'; + } + if (!empty($group['onload'])) { + if (!isset($element['#attributes']['onload'])) { + $element['#attributes']['onload'] = ''; + } + $element['#attributes']['onload'] .= $group['onload']; + } + if (!empty($group['onerror'])) { + if (!isset($element['#attributes']['onerror'])) { + $element['#attributes']['onerror'] = ''; + } + $element['#attributes']['onerror'] .= $group['onerror']; + } + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + // For non-file types, and non-aggregated files, add a script element per + // item. + else { + foreach ($group['items'] as $item) { + // Skip if data is empty. + if (empty($item['data'])) { + continue; + } + + // Element properties that do not depend on item type. + $element = $element_defaults; + if (!empty($item['defer'])) { + $element['#attributes']['defer'] = 'defer'; + } + if (!empty($item['async'])) { + $element['#attributes']['async'] = 'async'; + } + if (!empty($item['onload'])) { + if (!isset($element['#attributes']['onload'])) { + $element['#attributes']['onload'] = ''; + } + $element['#attributes']['onload'] .= $item['onload']; + } + if (!empty($item['onerror'])) { + if (!isset($element['#attributes']['onerror'])) { + $element['#attributes']['onerror'] = ''; + } + $element['#attributes']['onerror'] .= $item['onerror']; + } + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array(); + + // Crude type detection if needed. + if (empty($item['type'])) { + if (is_array($item['data'])) { + $item['type'] = 'setting'; + } + elseif (strpos($item['data'], 'http://') === 0 || strpos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0) { + $item['type'] = 'external'; + } + elseif (file_exists(trim($item['data']))) { + $item['type'] = 'file'; + } + else { + $item['type'] = 'inline'; + } + } + // Element properties that depend on item type. + switch ($item['type']) { + case 'setting': + $data = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($item['data'], 'is_array'))); + $json_data = advagg_json_encode($data); + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");"; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'inline': + // If a BOM is found, convert the string to UTF-8. + $encoding = advagg_get_encoding_from_bom($item['data']); + if (!empty($encoding)) { + $item['data'] = advagg_convert_to_utf8($item['data'], $encoding); + } + + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = $item['data']; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'file': + $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; + $cache_validator = REQUEST_TIME; + if (!empty($item['cache'])) { + $cache_validator = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];; + } + + $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . $cache_validator; + break; + + case 'external': + // Convert to protocol relative path. + $file_uri = $item['data']; + if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) { + $file_uri = advagg_convert_abs_to_protocol($item['data']); + } + $element['#attributes']['src'] = $file_uri; + break; + } + + $elements[] = $element; + } + } + } + + return $elements; +} + +/** + * Get the prefix and suffix for inline css. + * + * @return array + * An array where the prefix is key 0 and suffix is key 1. + */ +function advagg_get_css_prefix_suffix() { + $embed_prefix = "\n/* */\n"; + return array($embed_prefix, $embed_suffix); +} + +/** + * A #pre_render callback to add elements needed for CSS tags to be rendered. + * + * For production websites, LINK tags are preferable to STYLE tags with @import + * statements, because: + * - They are the standard tag intended for linking to a resource. + * - On Firefox 2 and perhaps other browsers, CSS files included with @import + * statements don't get saved when saving the complete web page for offline + * use: http://drupal.org/node/145218. + * - On IE, if only LINK tags and no @import statements are used, all the CSS + * files are downloaded in parallel, resulting in faster page load, but if + * the @import statements are used and span across multiple STYLE tags, all + * the ones from 1 STYLE tag must be downloaded before downloading begins for + * the next STYLE tag. Furthermore, IE7 does not support media declaration on + * the @import statement, so multiple STYLE tags must be used when different + * files are for different media types. Non-IE browsers always download in + * parallel, so this is an IE-specific performance quirk: + * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/. + * + * However, IE has an annoying limit of 31 total CSS inclusion tags + * (http://drupal.org/node/228818) and LINK tags are limited to one file per + * tag, whereas STYLE tags can contain multiple @import statements allowing + * multiple files to be loaded per tag. When CSS aggregation is disabled, a + * Drupal site can easily have more than 31 CSS files that need to be loaded, so + * using LINK tags exclusively would result in a site that would display + * incorrectly in IE. Depending on different needs, different strategies can be + * employed to decide when to use LINK tags and when to use STYLE tags. + * + * The strategy employed by this function is to use LINK tags for all aggregate + * files and for all files that cannot be aggregated (e.g., if 'preprocess' is + * set to FALSE or the type is 'external'), and to use STYLE tags for groups + * of files that could be aggregated together but aren't (e.g., if the site-wide + * aggregation setting is disabled). This results in all LINK tags when + * aggregation is enabled, a guarantee that as many or only slightly more tags + * are used with aggregation disabled than enabled (so that if the limit were to + * be crossed with aggregation enabled, the site developer would also notice the + * problem while aggregation is disabled), and an easy way for a developer to + * view HTML source while aggregation is disabled and know what files will be + * aggregated together when aggregation becomes enabled. + * + * This function evaluates the aggregation enabled/disabled condition on a group + * by group basis by testing whether an aggregate file has been made for the + * group rather than by testing the site-wide aggregation setting. This allows + * this function to work correctly even if modules have implemented custom + * logic for grouping and aggregating files. + * + * @param array $elements + * A render array containing: + * - '#items': The CSS items as returned by drupal_add_css() and altered by + * drupal_get_css(). + * - '#group_callback': A function to call to group #items to enable the use + * of fewer tags by aggregating files and/or using multiple @import + * statements within a single tag. + * - '#aggregate_callback': A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of XHTML CSS tags. + * + * @see drupal_get_css() + */ +function advagg_pre_render_styles(array $elements) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return drupal_pre_render_styles($elements); + } + + // Don't run it twice. + if (!empty($elements['#groups'])) { + return $elements; + } + + // Group and aggregate the items. + if (isset($elements['#group_callback'])) { + // Call drupal_group_css(). + $elements['#groups'] = $elements['#group_callback']($elements['#items']); + } + if (isset($elements['#aggregate_callback'])) { + // Call _advagg_aggregate_css(). + $elements['#aggregate_callback']($elements['#groups']); + } + + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. + $query_string = variable_get('css_js_query_string', '0'); + + // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be + // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to + // comment out the CDATA-tag. + // @see https://www.drupal.org/node/1021622 + list($embed_prefix, $embed_suffix) = advagg_get_css_prefix_suffix(); + + // Defaults for LINK and STYLE elements. + $link_element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'type' => 'text/css', + 'rel' => 'stylesheet', + ), + ); + $style_element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'style', + '#attributes' => array( + 'type' => 'text/css', + ), + ); + + // Loop through each group. + foreach ($elements['#groups'] as $group) { + switch ($group['type']) { + // For file items, there are three possibilities. + // - The group has been aggregated: in this case, output a LINK tag for + // the aggregate file. + // - The group can be aggregated but has not been (most likely because + // the site administrator disabled the site-wide setting): in this case, + // output as few STYLE tags for the group as possible, using @import + // statement for each file in the group. This enables us to stay within + // IE's limit of 31 total CSS inclusion tags. + // - The group contains items not eligible for aggregation (their + // 'preprocess' flag has been set to FALSE): in this case, output a LINK + // tag for each file. + case 'file': + // The group has been aggregated into a single file: output a LINK tag + // for the aggregate file. + if (isset($group['data'])) { + $element = $link_element_defaults; + $element['#attributes']['href'] = advagg_file_create_url($group['data']); + $element['#attributes']['media'] = $group['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + // The group can be aggregated, but hasn't been: combine multiple items + // into as few STYLE tags as possible. + elseif ($group['preprocess']) { + $import = array(); + foreach ($group['items'] as $item) { + // A theme's .info file may have an entry for a file that doesn't + // exist as a way of overriding a module or base theme CSS file from + // being added to the page. Normally, file_exists() calls that need + // to run for every page request should be minimized, but this one + // is okay, because it only runs when CSS aggregation is disabled. + // On a server under heavy enough load that file_exists() calls need + // to be minimized, CSS aggregation should be enabled, in which case + // this code is not run. When aggregation is enabled, + // drupal_load_stylesheet() checks file_exists(), but only when + // building the aggregate file, which is then reused for many page + // requests. + if (file_exists($item['data'])) { + // The dummy query string needs to be added to the URL to control + // browser-caching. IE7 does not support a media type on the + // "@import" statement, so we instead specify the media for the + // group on the STYLE tag. + $import[] = '@import url("' . check_plain(advagg_file_create_url($item['data']) . '?' . $query_string) . '");'; + } + } + // In addition to IE's limit of 31 total CSS inclusion tags, it also + // has a limit of 31 @import statements per STYLE tag. + while (!empty($import)) { + $import_batch = array_slice($import, 0, 31); + $import = array_slice($import, 31); + $element = $style_element_defaults; + // This simplifies the JavaScript regex, allowing each line + // (separated by \n) to be treated as a completely different string. + // This means that we can use ^ and $ on one line at a time, and not + // worry about style tags since they'll never match the regex. + $element['#value'] = "\n" . implode("\n", $import_batch) . "\n"; + $element['#attributes']['media'] = $group['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + } + // The group contains items ineligible for aggregation: output a LINK + // tag for each file. + else { + foreach ($group['items'] as $item) { + $element = $link_element_defaults; + // We do not check file_exists() here, because this code runs for + // files whose 'preprocess' is set to FALSE, and therefore, even + // when aggregation is enabled, and we want to avoid needlessly + // taxing a server that may be under heavy load. The file_exists() + // performed above for files whose 'preprocess' is TRUE is done for + // the benefit of theme .info files, but code that deals with files + // whose 'preprocess' is FALSE is responsible for ensuring the file + // exists. + // The dummy query string needs to be added to the URL to control + // browser-caching. + $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; + $element['#attributes']['href'] = advagg_file_create_url($item['data']) . $query_string_separator . $query_string; + $element['#attributes']['media'] = $item['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + } + break; + + // For inline content, the 'data' property contains the CSS content. If + // the group's 'data' property is set, then output it in a single STYLE + // tag. Otherwise, output a separate STYLE tag for each item. + case 'inline': + if (isset($group['data'])) { + $element = $style_element_defaults; + $element['#value'] = $group['data']; + $element['#value_prefix'] = $embed_prefix; + $element['#value_suffix'] = $embed_suffix; + $element['#attributes']['media'] = $group['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + else { + foreach ($group['items'] as $item) { + $element = $style_element_defaults; + $element['#value'] = $item['data']; + $element['#value_prefix'] = $embed_prefix; + $element['#value_suffix'] = $embed_suffix; + $element['#attributes']['media'] = $item['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + } + break; + + // Output a LINK tag for each external item. The item's 'data' property + // contains the full URL. + case 'external': + foreach ($group['items'] as $item) { + $element = $link_element_defaults; + // Convert to protocol relative path. + $file_uri = $item['data']; + if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) { + $file_uri = advagg_convert_abs_to_protocol($item['data']); + } + $element['#attributes']['href'] = $file_uri; + $element['#attributes']['media'] = $item['media']; + $element['#browsers'] = $group['browsers']; + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } + $elements[] = $element; + } + break; + + } + } + + return $elements; +} + +/** + * Default callback to group JavaScript items. + * + * This function arranges the JavaScript items that are in the #items property + * of the scripts element into groups. When aggregation is enabled, files within + * a group are aggregated into a single file, significantly improving page + * loading performance by minimizing network traffic overhead. + * + * This function puts multiple items into the same group if they are groupable + * and if they are for the same browsers. Items of the 'file' type are groupable + * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or + * 'external' type are not groupable. + * + * This function also ensures that the process of grouping items does not change + * their relative order. This requirement may result in multiple groups for the + * same type and browsers, if needed to accommodate other items in + * between. + * + * @param array $javascript + * An array of JavaScript items, as returned by drupal_add_js(), but after + * alteration performed by drupal_get_js(). + * + * @return array + * An array of JavaScript groups. Each group contains the same keys (e.g., + * 'data', etc.) as a JavaScript item from the $javascript parameter, with the + * value of each key applying to the group as a whole. Each group also + * contains an 'items' key, which is the subset of items from $javascript that + * are in the group. + * + * @see drupal_pre_render_scripts() + */ +function advagg_group_js(array $javascript) { + $groups = array(); + // If a group can contain multiple items, we track the information that must + // be the same for each item in the group, so that when we iterate the next + // item, we can determine if it can be put into the current group, or if a + // new group needs to be made for it. + $current_group_keys = NULL; + $index = -1; + foreach ($javascript as $key => $item) { + if (empty($item)) { + continue; + } + // The browsers for which the JavaScript item needs to be loaded is part of + // the information that determines when a new group is needed, but the order + // of keys in the array doesn't matter, and we don't want a new group if all + // that's different is that order. + if (isset($item['browsers'])) { + ksort($item['browsers']); + } + else { + $item['browsers'] = array(); + } + + // Fix missing types. + if (empty($item['type'])) { + // Setting is easy. + if ($key === 'settings') { + $item['type'] = 'setting'; + } + // Check for the schema or // for protocol-relative. + elseif ((stripos($item['data'], 'http://') === 0 + || stripos($item['data'], 'https://') === 0 + || (strpos($item['data'], '//') === 0 && strpos($item['data'], '///') === FALSE)) + ) { + $item['type'] = 'external'; + } + // See if the data contains a semicolon, new line, $, or quotes. + elseif (strpos($item['data'], ';') !== FALSE + || strpos($item['data'], "\n") + || strpos($item['data'], "$") + || strpos($item['data'], "'") + || strpos($item['data'], '"') + ) { + $item['type'] = 'inline'; + } + // Ends in .js. + elseif (stripos(strrev($item['data']), strrev('.js')) === 0) { + $item['type'] = 'file'; + } + } + + switch ($item['type']) { + case 'file': + // Group file items if their 'preprocess' flag is TRUE. + // Help ensure maximum reuse of aggregate files by only grouping + // together items that share the same 'group' value and 'every_page' + // flag. See drupal_add_js() for details about that. + $group_keys = !empty($item['preprocess']) ? array( + $item['type'], + $item['group'], + $item['every_page'], + $item['browsers'], + ) : FALSE; + break; + + case 'external': + case 'setting': + case 'inline': + // Do not group external, settings, and inline items. + $group_keys = FALSE; + break; + + default: + // Define this here so we don't get undefined alerts down below. + $group_keys = NULL; + // Log the error as well. + watchdog('advagg', 'Bad javascript was added. Type is unknown. @key - @item', array( + '@key' => $key, + '@item' => print_r($item, TRUE), + ), WATCHDOG_NOTICE); + break; + + } + + // If the group keys don't match the most recent group we're working with, + // then a new group must be made. + if ($group_keys !== $current_group_keys) { + ++$index; + // Initialize the new group with the same properties as the first item + // being placed into it. The item's 'data' and 'weight' properties are + // unique to the item and should not be carried over to the group. + $groups[$index] = $item; + unset($groups[$index]['data'], $groups[$index]['weight']); + $groups[$index]['items'] = array(); + $current_group_keys = $group_keys ? $group_keys : NULL; + } + + // Add the item to the current group. + $groups[$index]['items'][] = $item; + } + + return $groups; +} + +/** + * Stable sort for CSS and JS items. + * + * Preserves the order of items with equal sort criteria. + * + * The function will sort by: + * - $item['group'], integer, ascending + * - $item['every_page'], boolean, first TRUE then FALSE + * - $item['weight'], integer, ascending + * + * @param array &$items + * Array of JS or CSS items, as in drupal_add_css() and drupal_add_js(). + * The array keys can be integers or strings. The items themselves are arrays. + * + * @see drupal_get_css() + * @see drupal_get_js() + * @see drupal_add_css() + * @see drupal_add_js() + * @see https://drupal.org/node/1388546 + */ +function advagg_drupal_sort_css_js_stable(array &$items) { + // Within a group, order all infrequently needed, page-specific files after + // common files needed throughout the website. Separating this way allows for + // the aggregate file generated for all of the common files to be reused + // across a site visit without being cut by a page using a less common file. + $nested = array(); + foreach ($items as $key => &$item) { + // If weight is not set, make it 0. + if (!isset($item['weight'])) { + $item['weight'] = 0; + } + // If every_page is not set, make it FALSE. + if (!isset($item['every_page'])) { + $item['every_page'] = FALSE; + } + // If group is not set, make it CSS_DEFAULT/JS_DEFAULT (0). + if (!isset($item['group'])) { + $item['group'] = 0; + } + // If scope is not set, make it header. + if (!isset($item['scope'])) { + $item['scope'] = 'header'; + } + + // Weight cast to string to preserve float. + $weight = (string) $item['weight']; + $nested[$item['group']][$item['every_page'] ? 1 : 0][$weight][$key] = $item; + } + // First order by group, so that, for example, all items in the CSS_SYSTEM + // group appear before items in the CSS_DEFAULT group, which appear before + // all items in the CSS_THEME group. Modules may create additional groups by + // defining their own constants. + $sorted = array(); + // Sort group; then iterate over it. + ksort($nested); + foreach ($nested as &$group_items) { + // Reverse sort every_page; then iterate over it. + krsort($group_items); + foreach ($group_items as &$ep_items) { + // Sort weight; then iterate over it. + ksort($ep_items); + // Finally, order by weight. + foreach ($ep_items as &$weight_items) { + foreach ($weight_items as $key => &$item) { + $sorted[$key] = $item; + } + unset($item); + } + } + unset($ep_items); + } + unset($group_items); + $items = $sorted; +} + +/** + * Converts a PHP variable into its JavaScript equivalent. + * + * @param mixed $data + * Usually an array of data to be converted into a JSON string. + * + * @return string + * If there are no errors, this will return a JSON string. FALSE will be + * returned on failure. + */ +function advagg_json_encode($data) { + // Different versions of PHP handle json_encode() differently. + static $php550; + static $php540; + static $php530; + if (!isset($php550)) { + $php550 = version_compare(PHP_VERSION, '5.5.0', '>='); + } + if (!isset($php540)) { + $php540 = version_compare(PHP_VERSION, '5.4.0', '>='); + } + if (!isset($php530)) { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + // Use fallback drupal encoder if PHP < 5.3.0. + if (!$php530) { + return @drupal_json_encode($data); + } + + // Default json encode options. + $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; + if ($php550 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + // Output partial json if not in development mode and PHP >= 5.5.0. + $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; + } + if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + // Pretty print JSON if in development mode and PHP >= 5.4.0. + $options |= JSON_PRETTY_PRINT; + } + // Encode to JSON. + $json_data = @json_encode($data, $options); + + // Uses json_last_error() if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $error_number = json_last_error(); + switch ($error_number) { + case JSON_ERROR_NONE: + $error_message = ''; + break; + + case JSON_ERROR_DEPTH: + $error_message = 'Maximum stack depth exceeded'; + break; + + case JSON_ERROR_STATE_MISMATCH: + $error_message = 'Underflow or the modes mismatch'; + break; + + case JSON_ERROR_CTRL_CHAR: + $error_message = 'Unexpected control character found'; + break; + + case JSON_ERROR_SYNTAX: + $error_message = 'Syntax error, malformed JSON'; + break; + + case JSON_ERROR_UTF8: + $error_message = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + + default: + $error_message = 'Unknown error: ' . $error_number; + break; + } + if (!empty($error_message)) { + if (is_callable('httprl_pr')) { + $pretty_data = httprl_pr($data); + } + elseif (is_callable('kprint_r')) { + // @codingStandardsIgnoreLine + $pretty_data = kprint_r($data, TRUE); + } + else { + $pretty_data = '
    ' . filter_xss(print_r($data, TRUE)) . '
    '; + } + watchdog('advagg_json', 'Error with json encoding the Drupal.settings value. Error Message: %error_message. JSON Data: !data', array( + '%error_message' => $error_message, + '!data' => $pretty_data, + ), WATCHDOG_ERROR); + } + } + return $json_data; +} + +/** + * Will scan, flush, use, and report any changes to css/js files in aggregates. + */ +function advagg_scan_filesystem_for_changes_live() { + static $function_has_ran; + if (isset($function_has_ran)) { + return; + } + $function_has_ran = TRUE; + + $bypass_cookie = FALSE; + $cookie_name = 'AdvAggDisabled'; + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + $bypass_cookie = TRUE; + } + if ((!advagg_enabled() && !$bypass_cookie) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + return; + } + + // Scan for changes to any CSS/JS files. + module_load_include('inc', 'advagg', 'advagg.cache'); + $flushed = advagg_push_new_changes(); + + // Report back the results. + if (empty($flushed) || !user_is_logged_in()) { + return; + } + + list($css_path) = advagg_get_root_files_dir(); + $parts_uri = $css_path[1] . '/parts'; + + foreach ($flushed as $filename => $data) { + if (strpos($filename, $parts_uri) === 0) { + // Do not report on css files manged in the parts directory. + continue; + } + if (variable_get('advagg_show_file_changed_message', ADVAGG_SHOW_FILE_CHANGED_MESSAGE)) { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins. Trigger: @changes', array( + '%filename' => $filename, + '%db_usage' => count($data[0]), + '%db_count' => count($data[1]), + '@changes' => print_r($data[2], TRUE), + '%type' => $ext, + ))); + } + } +} + +/** + * Checks if the filename matches the advagg file pattern. + * + * @param string $filename + * Path to check. + * + * @return int + * Returns 1 if the pattern matches, 0 if it does not. + */ +function advagg_match_file_pattern($filename) { + return preg_match('/.*(j|cs)s' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}\.(j|cs)s$/', $filename); +} + +/** + * Converts absolute paths to be self references. + * + * @param string $path + * Path to check. + * @param bool $strip_base_path + * Do no add the base path to the given path if TRUE. + * + * @return string + * The path. + */ +function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) { + $base_url = $GLOBALS['base_url']; + // Add a slash to end if none is found. + if (strpos(strrev($base_url), '/') !== 0) { + $base_url .= '/'; + } + // Set base path. + $base_path = $GLOBALS['base_path']; + if ($strip_base_path) { + $base_path = ''; + } + + // Do conversion of https and http to self references. + $base_url_https = advagg_force_https_path($base_url); + $path = str_replace($base_url_https, $base_path, $path); + $base_url_http = advagg_force_http_path($base_url); + $path = str_replace($base_url_http, $base_path, $path); + + $base_url = advagg_convert_abs_to_protocol($GLOBALS['base_url']); + // Add a slash to end if none is found. + if (strpos(strrev($base_url), '/') !== 0) { + $base_url .= '/'; + } + // Do conversion of protocol relative to self references. + $path = str_replace($base_url, $base_path, $path); + + return $path; +} + +/** + * Converts absolute paths to be protocol relative paths. + * + * @param string $path + * Path to check. + * + * @return string + * The path. + */ +function advagg_convert_abs_to_protocol($path) { + if (strpos($path, 'http://') === 0) { + $path = substr($path, 5); + } + return $path; +} + +/** + * Convert http:// and // to https://. + * + * @param string $path + * Path to check. + * + * @return string + * The path. + */ +function advagg_force_https_path($path) { + if (strpos($path, 'http://') === 0) { + $path = 'https://' . substr($path, 7); + } + elseif (strpos($path, '//') === 0) { + $path = 'https:' . $path; + } + return $path; +} + +/** + * Convert https:// to http://. + * + * @param string $path + * Path to check. + * + * @return string + * The path. + */ +function advagg_force_http_path($path) { + if (strpos($path, 'https://') === 0) { + $path = 'http://' . substr($path, 8); + } + return $path; +} + +/** + * Wrapper around file_create_url() to do post-processing on the created url. + * + * @param string $path + * Path to check. + * @param array $aggregate_settings + * Array of settings used. + * @param bool $run_file_create_url + * If TRUE then run the given path through file_create_url(). + * @param string $source_type + * CSS or JS; if empty url in not embedded in another file. + * + * @return string + * The file uri. + */ +function advagg_file_create_url($path, array $aggregate_settings = array(), $run_file_create_url = TRUE, $source_type = '') { + $file_uri = $path; + if ($run_file_create_url) { + // This calls hook_file_url_alter(). + $file_uri = file_create_url($path); + } + elseif (strpos($path, '/') !== 0 && !advagg_is_external($path)) { + $file_uri = '/' . $path; + } + // Ideally convert to relative path. + if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) + && $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) + || (!isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) + && variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH)) + ) { + $file_uri = advagg_convert_abs_to_rel($file_uri); + } + // Next try protocol relative path. + if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) + && $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) + || (!isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) + && variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) + ) { + $file_uri = advagg_convert_abs_to_protocol($file_uri); + } + if ($source_type === 'css' + && !advagg_is_external($file_uri) + && ((isset($aggregate_settings['variables']['advagg_css_absolute_path']) + && $aggregate_settings['variables']['advagg_css_absolute_path']) + || (!isset($aggregate_settings['variables']['advagg_css_absolute_path']) + && variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH))) + ) { + // Get public dir. + list($css_path) = advagg_get_root_files_dir(); + $parsed = parse_url($css_path[0]); + $new_parsed = array(); + if (!empty($parsed['host'])) { + $new_parsed['host'] = $parsed['host']; + } + if (!empty($parsed['path'])) { + $new_parsed['path'] = $parsed['path']; + } + $css_path_0 = advagg_glue_url($new_parsed); + $parsed = parse_url($css_path[1]); + $new_parsed = array(); + if (!empty($parsed['host'])) { + $new_parsed['host'] = $parsed['host']; + } + if (!empty($parsed['path'])) { + $new_parsed['path'] = $parsed['path']; + } + $css_path_1 = advagg_glue_url($new_parsed); + $pos = strpos($css_path_1, $css_path_0); + if (!empty($pos)) { + $public_dir = substr($css_path_1, 0, $pos); + + // If public dir is not in the file uri, use absolute URL. + if (strpos($file_uri, $public_dir) === FALSE) { + $file_uri = url($path, array('absolute' => TRUE)); + } + } + } + // Finally force https. + if ((isset($aggregate_settings['variables']['advagg_force_https_path']) + && $aggregate_settings['variables']['advagg_force_https_path']) + || (!isset($aggregate_settings['variables']['advagg_force_https_path']) + && variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH)) + ) { + $file_uri = advagg_force_https_path($file_uri); + } + return $file_uri; +} + +/** + * Loads the stylesheet and resolves all @import commands. + * + * Loads a stylesheet and replaces @import commands with the contents of the + * imported file. Use this instead of file_get_contents when processing + * stylesheets. + * + * The returned contents are compressed removing white space and comments only + * when CSS aggregation is enabled. This optimization will not apply for + * color.module enabled themes with CSS aggregation turned off. + * + * @param string $file + * Name of the stylesheet to be processed. + * @param bool $optimize + * Defines if CSS contents should be compressed or not. + * @param bool $reset_basepath + * Used internally to facilitate recursive resolution of @import commands. + * + * @return string + * Contents of the stylesheet, including any resolved @import commands. + * + * @see drupal_load_stylesheet() + */ +function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') { + // These static's are not cache variables, so we don't use drupal_static(). + static $_optimize, $basepath; + if ($reset_basepath) { + $basepath = ''; + } + // Store the value of $optimize for preg_replace_callback with nested @import + // loops. + if (isset($optimize)) { + $_optimize = $optimize; + } + + // Stylesheets are relative one to each other. Start by adding a base path + // prefix provided by the parent stylesheet (if necessary). + if ($basepath && !file_uri_scheme($file)) { + $file = $basepath . '/' . $file; + } + // Store the parent base path to restore it later. + $parent_base_path = $basepath; + // Set the current base path to process possible child imports. + $basepath = dirname($file); + + // Load the CSS stylesheet. We suppress errors because themes may specify + // stylesheets in their .info file that don't exist in the theme's path, + // but are merely there to disable certain module CSS files. + $content = ''; + if (empty($contents) && !empty($file)) { + $contents = (string) @advagg_file_get_contents($file); + } + if ($contents) { + // Return the processed stylesheet. + $content = advagg_load_stylesheet_content($contents, $_optimize); + } + + // Restore the parent base path as the file and its children are processed. + $basepath = $parent_base_path; + if ($_optimize) { + $content = trim($content); + } + return $content; +} + +/** + * Decodes UTF byte-order mark (BOM) into the encoding's name. + * + * @param string $data + * The data possibly containing a BOM. This can be the entire contents of + * a file, or just a fragment containing at least the first five bytes. + * + * @return string|bool + * The name of the encoding, or FALSE if no byte order mark was present. + * + * @see https://api.drupal.org/api/drupal/core!lib!Drupal!Component!Utility!Unicode.php/function/Unicode%3A%3AencodingFromBOM/8 + */ +function advagg_get_encoding_from_bom($data) { + static $bom_map = array( + "\xEF\xBB\xBF" => 'UTF-8', + "\xFE\xFF" => 'UTF-16BE', + "\xFF\xFE" => 'UTF-16LE', + "\x00\x00\xFE\xFF" => 'UTF-32BE', + "\xFF\xFE\x00\x00" => 'UTF-32LE', + "\x2B\x2F\x76\x38" => 'UTF-7', + "\x2B\x2F\x76\x39" => 'UTF-7', + "\x2B\x2F\x76\x2B" => 'UTF-7', + "\x2B\x2F\x76\x2F" => 'UTF-7', + "\x2B\x2F\x76\x38\x2D" => 'UTF-7', + ); + + foreach ($bom_map as $bom => $encoding) { + if (strpos($data, $bom) === 0) { + return $encoding; + } + } + return FALSE; +} + +/** + * Converts data to UTF-8. + * + * Requires the iconv, GNU recode or mbstring PHP extension. + * + * @param string $data + * The data to be converted. + * @param string $encoding + * The encoding that the data is in. + * + * @return string|bool + * Converted data or FALSE. + */ +function advagg_convert_to_utf8($data, $encoding) { + if (function_exists('iconv')) { + return @iconv($encoding, 'utf-8', $data); + } + elseif (function_exists('mb_convert_encoding')) { + return @mb_convert_encoding($data, 'utf-8', $encoding); + } + elseif (function_exists('recode_string')) { + return @recode_string($encoding . '..utf-8', $data); + } + // Cannot convert. + return FALSE; +} + +/** + * Processes the contents of a stylesheet for aggregation. + * + * @param string $contents + * The contents of the stylesheet. + * @param bool $optimize + * (Optional) Boolean whether CSS contents should be minified. Defaults to + * FALSE. + * + * @return string + * Contents of the stylesheet including the imported stylesheets. + * + * @see drupal_load_stylesheet_content() + */ +function advagg_load_stylesheet_content($contents, $optimize = FALSE) { + // If a BOM is found, convert the file to UTF-8. Used for inline CSS here. + $encoding = advagg_get_encoding_from_bom($contents); + if (!empty($encoding)) { + $contents = advagg_convert_to_utf8($contents, $encoding); + } + + if ($optimize) { + // Perform some safe CSS optimizations. + // Regexp to match comment blocks. + // Regexp to match double quoted strings. + // Regexp to match single quoted strings. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; + // Strip all comment blocks, but keep double/single quoted strings. + $contents = preg_replace( + "<($double_quot|$single_quot)|$comment>Ss", + "$1", + $contents + ); + + // Remove certain whitespace. + // There are different conditions for removing leading and trailing + // whitespace. + // @see http://php.net/manual/regexp.reference.subpatterns.php + $contents = preg_replace('< + # Do not strip any space from within single or double quotes + (' . $double_quot . '|' . $single_quot . ') + # Strip leading and trailing whitespace. + | \s*([@{};,])\s* + # Strip only leading whitespace from: + # - Closing parenthesis: Retain "@media (bar) and foo". + | \s+([\)]) + # Strip only trailing whitespace from: + # - Opening parenthesis: Retain "@media (bar) and foo". + # - Colon: Retain :pseudo-selectors. + | ([\(:])\s+ + >xSs', + // Only one of the three capturing groups will match, so its reference + // will contain the wanted value and the references for the + // two non-matching groups will be replaced with empty strings. + '$1$2$3$4', + $contents + ); + // End the file with a new line. + $contents = trim($contents); + $contents .= "\n"; + } + + // Remove multiple charset declarations for standards compliance (and fixing + // Safari problems). + $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); + + // Replaces @import commands with the actual stylesheet content. + // This happens recursively but omits external files. + $contents = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', '_advagg_load_stylesheet', $contents); + return $contents; +} + +/** + * Loads stylesheets recursively and returns contents with corrected paths. + * + * This function is used for recursive loading of stylesheets and + * returns the stylesheet content with all url() paths corrected. + * + * @param array $matches + * The matches from preg_replace_callback(). + * + * @return array + * String with altered internal url() paths. + * + * @see _drupal_load_stylesheet() + */ +function _advagg_load_stylesheet(array $matches) { + $filename = $matches[1]; + // Load the imported stylesheet and replace @import commands in there as well. + $file = advagg_load_stylesheet($filename, NULL, FALSE); + + if (empty($file)) { + if (strpos($matches[0], 'http://') === 0 + || strpos($matches[0], 'https://') === 0 + || strpos($matches[0], '//') === 0 + ) { + return $matches[0]; + } + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Trying to load @file via @import statement but it was not found.', array('@file' => $filename), WATCHDOG_DEBUG); + } + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) <= 1) { + return $matches[0]; + } + else { + return ''; + } + } + + // Determine the file's directory. + $directory = dirname($filename); + // If the file is in the current directory, make sure '.' doesn't appear in + // the url() path. + $directory = $directory == '.' ? '' : $directory . '/'; + + // Alter all internal url() paths. Leave external paths alone. We don't need + // to normalize absolute paths here (i.e. remove folder/... segments) because + // that will be done later. + return preg_replace('%url\(\s*+([\'"]?+)(?![a-z]++:|/)([^\'")]+)([\'"]?+)\s*\)%i', 'url(\1' . $directory . '\2\3)', $file); +} + +/** + * Check and see if the aggressive cache can safely be enabled. + * + * @return array + * If there are no conflicts, this will return an empty array. + */ +function advagg_aggressive_cache_conflicts() { + $hooks = array('css_alter' => TRUE, 'js_alter' => TRUE); + foreach ($hooks as $hook => $values) { + $hooks[$hook] = module_implements($hook); + + // Also check themes as drupal_alter() allows for themes to alter things. + $themes = list_themes(); + $theme_keys = array_keys($themes); + if (!empty($theme_keys)) { + foreach ($theme_keys as $theme_key) { + $function = $theme_key . '_' . $hook; + // Search loaded themes. + if (function_exists($function)) { + $hooks[$hook][] = $theme_key; + continue; + } + // Skip disabled themes. + if (empty($themes[$theme_key]->status)) { + continue; + } + // Search enabled but not loaded themes. + $file = dirname($themes[$theme_key]->filename) . '/template.php'; + if (file_exists($file)) { + $contents = (string) @advagg_file_get_contents($file); + if (stripos($contents, $function)) { + $hooks[$hook][] = $theme_key; + } + } + } + } + } + + $whitelist = array( + // Core. + // + // locale_js_directory variable; default: languages. + // javascript_parsed variable; default: array(). + 'locale', + + // No control; same every time. + 'simpletest', + + // No control; same every time. + 'seven', + + // Popular contrib. + // + // No control; same every time. + 'at_commerce', + + // ais_adaptive_styles variable; Default: array(). + // ais_adaptive_styles_method; Default: 'both-max'. + // 'ais', + // + // No control; same every time. + 'bluecheese', + + // drupal_static('clientside_validation_settings') array. + // 'clientside_validation', + // + // version_compare(VERSION, '7.14', '<'). + 'conditional_fields', + + // _css_injector_load_rule() function. + // Changes the weight of all files added in init so no special handling. + // 'css_injector', + // + // disable_css_ . $theme . _all variable; default: FALSE. + // disable_css_ . $theme . _modules; default: array(). + // disable_css_ . $theme . _files; default: array(). + // 'disable_css', + // + // Empty call; commented code is same every time. + 'elfinder', + + // excluded_css_custom variable; Default: ''. + // excluded_javascript_custom variable; Default: ''. + // 'excluded', + // + // No control; same every time. + 'fences', + + // jqmulti_jquery_path() function. + // jqmulti_get_files() function. + // jqmulti_load_always variable; Default: FALSE. + // 'jqmulti', + // + // No control; same every time. + 'jquery_dollar', + + // labjs_suppress() function. + 'labjs_js', + + // Empty call. + 'panopoly_core', + + // speedy_js_production variable; Default: TRUE. + 'speedy', + + // logintoboggan_validating_id() function. + 'logintoboggan', + ); + // Allow other modules to modify the $whitelist. + // Call hook_advagg_aggressive_cache_conflicts_alter() + drupal_alter('advagg_aggressive_cache_conflicts', $whitelist); + + $questionable_modules = array(); + foreach ($hooks as $hook => $modules) { + foreach ($modules as $key => $module) { + // Anything from advagg is ok. + if (strpos($module, 'advagg') === 0 || strpos($module, '_advagg') === 0) { + unset($modules[$key]); + continue; + } + + // Remove known modules that should work with aggressive caching. + if (in_array($module, $whitelist)) { + unset($modules[$key]); + } + else { + $questionable_modules[$module] = $module; + } + } + } + return $questionable_modules; +} + +/** + * Alt to http_build_url(). + * + * @param array $parsed + * Array from parse_url(). + * @param bool $strip_query_and_fragment + * If set to TRUE the query and fragment will be removed from the output. + * + * @return string + * URI is returned. + * + * @see http://php.net/parse-url#85963 + */ +function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) { + $uri = ''; + if (isset($parsed['scheme'])) { + switch (strtolower($parsed['scheme'])) { + // Mailto uri. + case 'mailto': + $uri .= $parsed['scheme'] . ':'; + break; + + // Protocol relative uri. + case '//': + $uri .= $parsed['scheme']; + break; + + // Standard uri. + default: + $uri .= $parsed['scheme'] . '://'; + } + } + $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : ''; + $uri .= isset($parsed['host']) ? $parsed['host'] : ''; + $uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : ''; + + if (isset($parsed['path'])) { + $uri .= (substr($parsed['path'], 0, 1) === '/') ? $parsed['path'] : ((!empty($uri) ? '/' : '') . $parsed['path']); + } + + if (!$strip_query_and_fragment) { + $uri .= isset($parsed['query']) ? '?' . $parsed['query'] : ''; + $uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : ''; + } + return $uri; +} + +/** + * Clear certain caches on form submit. + */ +function advagg_cache_clear_admin_submit() { + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); + } + cache_clear_all('hook_info', 'cache_bootstrap'); + cache_clear_all('advagg_hooks_implemented:', 'cache_bootstrap', TRUE); +} + +/** + * Get the resource hint settings for the preload attribute. + * + * @param bool $return_defaults + * Default FALSE, TRUE returns the default values. + * + * @return array + * Ordered 2 dimensional array. + */ +function advagg_get_resource_hints_preload_settings($return_defaults = FALSE) { + $sub_defaults = array( + 'enabled' => 1, + 'push' => 0, + 'local' => 1, + 'external' => 1, + ); + // Collect your data. + $advagg_resource_hints_preload_settings_defaults = array( + 'style' => $sub_defaults + array( + '#weight' => -10, + 'title' => t('CSS Files'), + ), + 'font' => $sub_defaults + array( + '#weight' => -9, + 'title' => t('Font Files'), + ), + 'script' => $sub_defaults + array( + '#weight' => -8, + 'title' => t('JS Files'), + ), + 'svg' => $sub_defaults + array( + '#weight' => -7, + 'title' => t('SVG Files'), + ), + 'image' => $sub_defaults + array( + '#weight' => -6, + 'title' => t('Image Files'), + ), + 'all_others' => $sub_defaults + array( + '#weight' => -5, + 'title' => t('All Other Files'), + ), + ); + if ($return_defaults) { + return $advagg_resource_hints_preload_settings_defaults; + } + $advagg_resource_hints_preload_settings = variable_get('advagg_resource_hints_preload_settings', $advagg_resource_hints_preload_settings_defaults); + // Merge in defaults. + foreach ($advagg_resource_hints_preload_settings as $id => &$entry) { + if (isset($advagg_resource_hints_preload_settings_defaults[$id])) { + $entry += $advagg_resource_hints_preload_settings_defaults[$id]; + } + ksort($entry); + } + unset($entry); + // Sort the rows. + uasort($advagg_resource_hints_preload_settings, 'element_sort'); + return $advagg_resource_hints_preload_settings; +} + +/** + * See if the .htaccess file uses the RewriteBase directive. + * + * @param string $location + * The location of the .htaccess file. + * + * @return string + * The last active RewriteBase entry in htaccess. + */ +function advagg_htaccess_rewritebase($location = DRUPAL_ROOT) { + if (is_readable($location . '/.htaccess')) { + $htaccess = advagg_file_get_contents($location . '/.htaccess'); + $matches = array(); + $found = preg_match_all('/\\n\s*RewriteBase\s.*/i', $htaccess, $matches); + if ($found && !empty($matches[0])) { + $matches[0] = array_map('trim', $matches[0]); + return array_pop($matches[0]); + } + } + return ''; +} + +/** + * Get the latest version number for the remote version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * @param string $url + * URL for the remote version to lookup. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_github_version_json(array $library, array $options, $url) { + $http_options = array('timeout' => 2); + $package = drupal_http_request($url, $http_options); + if (empty($package->data)) { + // Try again. + $package = drupal_http_request($url, array('timeout' => 5)); + } + if (empty($package->data)) { + // Try again but force http. + $url = advagg_force_http_path($url); + $package = drupal_http_request($url, $http_options); + } + if (!empty($package->data)) { + $package = json_decode($package->data); + if (isset($package->version)) { + return (string) $package->version; + } + } + return 0; +} + +/** + * Get the latest version number for the remote version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * @param string $url + * URL for the remote version to lookup. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_github_version_txt(array $library, array $options, $url) { + $http_options = array('timeout' => 2); + $request = drupal_http_request($url, $http_options); + if (empty($request->data)) { + // Try again. + $request = drupal_http_request($url, array('timeout' => 5)); + } + if (empty($request->data)) { + // Try again but force http. + $url = advagg_force_http_path($url); + $request = drupal_http_request($url, $http_options); + } + if (!empty($request->data)) { + $matches = array(); + if (preg_match($options['pattern'], $request->data, $matches)) { + return $matches[1]; + } + } + return '0'; +} + +/** + * Update github version numbers to the latest. + * + * @param bool $refresh + * Set to TRUE to skip the cache and force a refresh of the data. + * + * @return mixed + * Key Value pair of the project name and remote version number. If $target is + * set then that version number is returned. + */ +function advagg_get_remote_libraries_versions($refresh = FALSE) { + $cid = __FUNCTION__; + $versions = array(); + if (!$refresh) { + $versions = advagg_get_remote_libraries_versions_cache($cid); + if (!empty($versions)) { + return $versions; + } + } + + if (is_callable('libraries_info')) { + $libraries = libraries_info(); + foreach ($libraries as $key => $library) { + // Get current version. + $libraries_detect = libraries_detect($key); + if (isset($libraries_detect['version'])) { + $versions[$key]['local'] = $libraries_detect['version']; + } + elseif (!empty($libraries_detect['local version'])) { + $versions[$key]['local'] = $libraries_detect['local version']; + } + + // Check if callback is live. + $remote = advagg_get_remote_libraries_version($key, $library, FALSE); + if (!empty($remote)) { + $versions[$key]['remote'] = $remote; + } + } + } + + if (!empty($versions)) { + cache_set($cid, $versions, 'cache_advagg_info'); + } + return $versions; +} + +/** + * Get the remote and local versions cache of the available libraries. + * + * @param string $cid + * Cache ID. + * + * @return array + * Library name => (local, remote). + */ +function advagg_get_remote_libraries_versions_cache($cid = '') { + if (empty($cid)) { + $cid = 'advagg_get_remote_libraries_versions'; + } + $versions = &drupal_static($cid, array()); + if (empty($versions)) { + $cache = cache_get($cid, 'cache_advagg_info'); + if (!empty($cache) && !empty($cache->data)) { + $versions = $cache->data; + } + } + return $versions; +} + +/** + * Get the latest version number for the remote version. + * + * @param string $name + * Name of the library. + * @param array $library + * An associative array containing all information about the library. + * @param bool $use_cache + * TRUE try the cache first. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_remote_libraries_version($name, array $library, $use_cache = TRUE) { + if ($use_cache) { + $cid = 'advagg_get_remote_libraries_versions'; + $versions = advagg_get_remote_libraries_versions_cache($cid); + if (isset($versions[$name]['remote'])) { + return $versions[$name]['remote']; + } + } + + // Remote url is not set, see if we can generate it given the current data. + if (empty($library['remote']['url']) + && !empty($library['version arguments']) + ) { + if (!isset($library['version arguments']['file']) + && isset($library['version arguments']['variants']) + ) { + // Use the first variant. + $file = reset($library['version arguments']['variants']); + $library['version arguments']['file'] = $file['file']; + $library['version arguments']['pattern'] = $file['pattern']; + } + + if (!empty($library['version arguments']['file'])) { + if (!empty($library['vendor url'])) { + // Use vendor url if it's a github one. + if (strpos($library['vendor url'], 'https://github.com/') === 0) { + $parsed_vendor = @parse_url($library['vendor url']); + // Previously: https://rawgit.com{$parsed_vendor['path']}/master/{$library['version arguments']['file']} . + $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_vendor['path']}@master/{$library['version arguments']['file']}"; + } + } + if (empty($library['remote']['url']) && !empty($library['download url'])) { + // Use download url if it's a github one. + if (strpos($library['download url'], 'https://github.com/') === 0) { + $parsed_download = @parse_url($library['download url']); + $paths = explode('/', $parsed_download['path']); + $parsed_download['path'] = "/{$paths[1]}/{$paths[2]}"; + // Previously: https://rawgit.com{$parsed_download['path']}/master/{$library['version arguments']['file']} . + $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_download['path']}@master/{$library['version arguments']['file']}"; + } + } + } + } + + // Remote callback is not set, try to generate it given the current data. + if (empty($library['remote']['callback']) + && isset($library['version arguments']['file']) + ) { + if (!empty($library['version callback'])) { + // Use defined parser. + $library['remote']['callback'] = $library['version callback']; + } + else { + if ($library['version arguments']['file'] === 'package.json') { + // JSON parser. + $library['remote']['callback'] = 'advagg_get_github_version_json'; + } + else { + // Text parser. + $library['remote']['callback'] = 'advagg_get_github_version_txt'; + } + } + } + + // Get remote version. + $return = 0; + if (!empty($library['remote']) + && !empty($library['remote']['callback']) + && !empty($library['remote']['url']) + && isset($library['version arguments']) + && is_callable($library['remote']['callback']) + ) { + $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); + + // Try package.json on failure. + if (empty($return) && $library['version arguments']['file'] !== 'package.json') { + $pos = strrpos($library['remote']['url'], $library['version arguments']['file']); + $library['remote']['url'] = substr($library['remote']['url'], 0, $pos) . 'package.json'; + $library['remote']['callback'] = 'advagg_get_github_version_json'; + $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); + } + } + return $return; +} + +/** + * Get the latest version number for the remote version. + * + * @param string $name + * Name of the library. + * @param string $module_name + * Name of the module where the library is registered. + * + * @return array + * The library array. + */ +function advagg_get_library($name, $module_name) { + $library = cache_get($name, 'cache_libraries'); + if ($library) { + $library = $library->data; + } + else { + if (is_callable('libraries_detect')) { + $library = libraries_detect($name); + } + elseif (is_callable("{$module_name}_libraries_info")) { + $library = call_user_func("{$module_name}_libraries_info"); + $library = $library[$name]; + } + } + return $library; +} + +/** + * Alter the js array fixing the type key if set incorrectly. + * + * @param array $array + * CSS or JS array. + * @param string $type + * CSS or JS. + */ +function advagg_fix_type(array &$array, $type) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + + // Skip if setting is turned off. + if ($type === 'css' && !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) { + return; + } + if ($type === 'js' && !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) { + return; + } + + // Fix type if it was incorrectly set. + // Get hostname and base path. + $mod_base_url = substr($GLOBALS['base_root'] . $GLOBALS['base_path'], strpos($GLOBALS['base_root'] . $GLOBALS['base_path'], '//') + 2); + $mod_base_url_len = strlen($mod_base_url); + + foreach ($array as &$value) { + // Skip if the data is empty or not a string. + if (empty($value['data']) || !is_string($value['data'])) { + continue; + } + + // Default to file if type is not set. + if (!isset($value['type'])) { + $value['type'] = 'file'; + } + + // If inline, be done with processing. + if ($value['type'] === 'inline') { + continue; + } + + // Default to file if not file, inline, external, setting. + if ($value['type'] !== 'file' + && $value['type'] !== 'inline' + && $value['type'] !== 'external' + && $value['type'] !== 'setting' + ) { + if ($value['type'] === 'settings') { + $value['type'] = 'setting'; + } + else { + $value['type'] = 'file'; + } + } + + $lower = strtolower($value['data']); + $http_pos = strpos($lower, 'http://'); + $https_pos = strpos($lower, 'https://'); + $double_slash_pos = strpos($lower, '//'); + $tripple_slash_pos = strpos($lower, '///'); + $mod_base_url_pos = stripos($value['data'], $mod_base_url); + + // If type is external but doesn't start with http, https, or // change it + // to file. + if ($value['type'] === 'external' + && $http_pos !== 0 + && $https_pos !== 0 + && $double_slash_pos !== 0 + ) { + if (is_readable($value['data'])) { + $value['type'] = 'file'; + } + else { + // Fix for subdir issues. + $parsed = parse_url($value['data']); + if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { + $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + } + } + } + } + + // If type is file but it starts with http, https, or // change it to + // external. Skip tripple slash for local files. + if ($value['type'] === 'file' + && ($http_pos === 0 + || $https_pos === 0 + || ($double_slash_pos === 0 && $tripple_slash_pos === FALSE)) + ) { + $value['type'] = 'external'; + } + + // If type is external and starts with http, https, or // but points to + // this host change to file, but move it to the top of the aggregation + // stack. + if ($value['type'] === 'external' + && $mod_base_url_pos - 2 === $double_slash_pos + && ($http_pos === 0 + || $https_pos === 0 + || $double_slash_pos === 0 + ) + ) { + $path = substr($value['data'], $mod_base_url_pos + $mod_base_url_len); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + $value['group'] = JS_LIBRARY; + $value['every_page'] = TRUE; + $value['weight'] = -40000; + } + else { + // Fix for subdir issues. + $parsed = parse_url($path); + if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { + $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + $value['group'] = JS_LIBRARY; + $value['every_page'] = TRUE; + $value['weight'] = -40000; + } + } + } + } + } + unset($value); +} + +/** + * Alter the CSS or JS array removing empty files from the aggregates. + * + * @param array $array + * CSS or JS array. + */ +function advagg_remove_empty_files(array &$array) { + if (!variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES)) { + return; + } + + if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) { + foreach ($array as $key => $value) { + if ($value['type'] !== 'file') { + continue; + } + // Check locally. + if (!is_readable($value['data']) || filesize($value['data']) == 0) { + unset($array[$key]); + } + } + } + else { + module_load_include('inc', 'advagg', 'advagg'); + $files = array(); + foreach ($array as $key => $value) { + if ($value['type'] !== 'file') { + continue; + } + $files[$key] = $value['data']; + } + // Check cache/db. + $info = advagg_get_info_on_files($files); + foreach ($info as $key => $values) { + if (empty($values['filesize'])) { + $key = array_search($values['data'], $files); + if (isset($array[$key])) { + unset($array[$key]); + } + } + } + } +} + +/** + * Alter the CSS or JS array adding in DNS domain info. + * + * @param array $array + * CSS or JS array. + * @param string $type + * CSS or JS. + */ +function advagg_add_default_dns_lookups(array &$array, $type) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + // Remove this return once CSS lookups are needed. + if ($type !== 'js') { + return; + } + + // Add DNS information for some of the more popular modules. + foreach ($array as &$value) { + if (!is_string($value['data'])) { + continue; + } + // Google Ad Manager. + if (strpos($value['data'], '/google_service.') !== FALSE) { + if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { + $temp = $value['dns_prefetch']; + unset($value['dns_prefetch']); + $value['dns_prefetch'] = array($temp); + } + // Domains in the google_service.js file. + $value['dns_prefetch'][] = 'https://csi.gstatic.com'; + $value['dns_prefetch'][] = 'https://pubads.g.doubleclick.net'; + $value['dns_prefetch'][] = 'https://partner.googleadservices.com'; + $value['dns_prefetch'][] = 'https://securepubads.g.doubleclick.net'; + + // Domains in the google_ads.js file. + $value['dns_prefetch'][] = 'https://pagead2.googlesyndication.com'; + + // Other domains that usually get hit. + $value['dns_prefetch'][] = 'https://cm.g.doubleclick.net'; + $value['dns_prefetch'][] = 'https://tpc.googlesyndication.com'; + } + + // Google Analytics. + if (strpos($value['data'], 'GoogleAnalyticsObject') !== FALSE + || strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE + ) { + if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { + $temp = $value['dns_prefetch']; + unset($value['dns_prefetch']); + $value['dns_prefetch'] = array($temp); + } + if ($GLOBALS['is_https'] && strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) { + $value['dns_prefetch'][] = 'https://ssl.google-analytics.com'; + } + else { + $value['dns_prefetch'][] = 'https://www.google-analytics.com'; + } + $value['dns_prefetch'][] = 'https://stats.g.doubleclick.net'; + } + } +} + +/** + * Insert element into an array at a specific position. + * + * @param array $input_array + * The original array. + * @param array $new_value + * The element that is getting inserted. + * @param int $location_key + * The key location. + * + * @return array + * The new array with the element merged in. + */ +function advagg_insert_into_array_at_location(array $input_array, array $new_value, $location_key) { + return array_merge(array_slice($input_array, 0, $location_key), $new_value, array_slice($input_array, $location_key)); +} + +/** + * Insert element into an array at a specific key location. + * + * @param array $input_array + * The original array. + * @param array $insert + * The element that is getting inserted; array(key => value). + * @param string $target_key + * The key name. + * @param int $location + * After is 1 , 0 is replace, -1 is before. + * + * @return array + * The new array with the element merged in. + */ +function advagg_insert_into_array_at_key(array $input_array, array $insert, $target_key, $location = 1) { + $output = array(); + $new_value = reset($insert); + $new_key = key($insert); + foreach ($input_array as $key => $value) { + if ($key === $target_key) { + // Insert before. + if ($location == -1) { + $output[$new_key] = $new_value; + $output[$key] = $value; + } + // Replace. + if ($location == 0) { + $output[$new_key] = $new_value; + } + // After. + if ($location == 1) { + $output[$key] = $value; + $output[$new_key] = $new_value; + } + } + else { + // Pick next key if there is an number collision. + if (is_numeric($key)) { + while (isset($output[$key])) { + $key++; + } + } + $output[$key] = $value; + } + } + // Add to array if not found. + if (!isset($output[$new_key])) { + // Before everything. + if ($location == -1) { + $output = $insert + $output; + } + // After everything. + if ($location == 1) { + $output[$new_key] = $new_value; + } + + } + return $output; +} + +/** + * Given a URL output a filename. + * + * @param string $url + * The url. + * @param string $strict + * If FALSE then slashes will be kept. + * + * @return string + * The filename. + */ +function advagg_url_to_filename($url, $strict = TRUE) { + // Keep filtering till there are no more changes. + $decoded1 = _advagg_url_to_filename_filter($url, $strict); + $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); + while ($decoded1 != $decoded2) { + $decoded1 = _advagg_url_to_filename_filter($decoded2, $strict); + $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); + } + $filename = $decoded1; + + // Shorten filename to a max of 250 characters. + $filename = mb_strcut($filename, 0, 250, mb_detect_encoding($filename)); + return $filename; +} + +/** + * Given a URL output a filtered filename. + * + * @param string $url + * The url. + * @param string $strict + * If FALSE then slashes will be kept. + * + * @return string + * The filename. + */ +function _advagg_url_to_filename_filter($url, $strict = TRUE) { + // URL Decode if needed. + $decoded1 = $url; + $decoded2 = rawurldecode($decoded1); + while ($decoded1 != $decoded2) { + $decoded1 = rawurldecode($decoded2); + $decoded2 = rawurldecode($decoded1); + } + $url = $decoded1; + + // Replace url spaces with a dash. + $filename = str_replace(array('%20', '+'), '-', $url); + + // Remove file system reserved characters + // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + // Remove control charters + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + // Remove non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN + // Remove URI reserved characters + // https://tools.ietf.org/html/rfc3986#section-2.2 + // Remove URL unsafe characters + // https://www.ietf.org/rfc/rfc1738.txt + if ($strict) { + $filename = preg_replace('~[<>:"/\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); + } + else { + $filename = preg_replace('~[<>:"\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); + } + + // Replce all white spaces with a dash. + $filename = preg_replace('/[\r\n\t -]+/', '-', $filename); + + // Avoid ".", ".." or ".hiddenFiles". + $filename = ltrim($filename, '.-'); + + // Compress spaces in a file name and replace with a dash. + // Compress underscores in a file name and replace with a dash. + // Compress dashes in a file name and replace with a dash. + $filename = preg_replace(array('/ +/', '/_+/', '/-+/'), '-', $filename); + + // Compress dashes and dots in a file name and replace with a dot. + $filename = preg_replace(array('/-*\.-*/', '/\.{2,}/'), '.', $filename); + + // Lowercase for windows/unix interoperability + // http://support.microsoft.com/kb/100625. + $filename = mb_strtolower($filename, 'UTF-8'); + // Remove ? \ .. + $filename = str_replace(array('?', '\\', '..'), '', $filename); + + // ".file-name.-" becomes "file-name". + $filename = trim($filename, '.-'); + + return $filename; +} + +/** + * Given a URI return TRUE if it is external. + * + * @param string $uri + * The uri. + * + * @return bool + * TRUE if external. + */ +function advagg_is_external($uri) { + $http_pos = strpos($uri, 'http://'); + $https_pos = strpos($uri, 'https://'); + $double_slash_pos = strpos($uri, '//'); + if ($http_pos !== 0 + && $https_pos !== 0 + && $double_slash_pos !== 0 + ) { + return FALSE; + } + return TRUE; +} + +/** + * Same as file_get_contents() but will convert string to UTF-8 if needed. + * + * @return mixed + * The files contents or FALSE on failure. + */ +function advagg_file_get_contents() { + // Get the file contents. + $file_contents = call_user_func_array('file_get_contents', func_get_args()); + if ($file_contents === FALSE) { + return $file_contents; + } + + // If a BOM is found, convert the file to UTF-8. + $encoding = advagg_get_encoding_from_bom($file_contents); + if (!empty($encoding)) { + $file_contents = advagg_convert_to_utf8($file_contents, $encoding); + } + + return $file_contents; +} + +/** + * Get the description text based off the library version. + * + * @param string $library_name + * Name of the library. + * @param string $module_name + * Name of the module that contains hook_libraries_info for this library. + * + * @return array + * Description text and info array. + */ +function advagg_get_version_description($library_name, $module_name, $only_remote_ok = FALSE) { + $t = get_t(); + + // Get local and external library version numbers. + $versions = &drupal_static(__FUNCTION__); + if (!isset($versions)) { + $versions = advagg_get_remote_libraries_versions(TRUE); + } + + $description = ''; + if (!empty($versions[$library_name]['remote']) + && (empty($versions[$library_name]['local']) + || $versions[$library_name]['local'] !== $versions[$library_name]['remote'] + )) { + $library = advagg_get_library($library_name, $module_name); + if (empty($versions[$library_name]['local'])) { + $versions[$library_name]['local'] = 'NULL'; + } + if (!empty($library['installed'])) { + $description = $t('Go to the @name page and download the latest version (@remote) into the @lib_path folder. An example valid filename is %version_file. Current Version: %version.', array( + '@name' => $library['name'], + '@lib_path' => $library['library path'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version' => $versions[$library_name]['local'], + '%version_file' => $library['library path'] . '/' . $library['version arguments']['file'], + )); + } + elseif (!$only_remote_ok && is_callable('libraries_load')) { + $description = $t('Go to the @name page and download the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( + '@name' => $library['name'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", + )); + } + elseif (!$only_remote_ok) { + $description = $t('Install the Libraries API module and then go to the @name page and download the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( + '@name' => $library['name'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", + '@url-lib-api' => 'https://www.drupal.org/project/libraries', + )); + } + } + $path = drupal_get_path('module', $module_name); + $info = drupal_parse_info_file("{$path}/{$module_name}.info"); + + // Check if library was unzipped with -master. + if (!empty($description) && is_callable('libraries_get_libraries')) { + $libraries_paths = libraries_get_libraries(); + if (!empty($libraries_paths["{$library_name}-master"])) { + $description = $t('Rename @lib_dir_master to @lib_dir at this location: @lib_path_master.', array( + '@lib_dir_master' => "{$library_name}-master", + '@lib_path_master' => $libraries_paths["{$library_name}-master"], + '@lib_dir' => $library_name, + )); + } + } + + return array($description, $info); +} + +/** + * Given a advagg type this will return the most recent aggregate from the db. + * + * @param string $type + * String: css or js. + * + * @return string + * Returns advagg filename or an empty string on failure. + */ +function advagg_generate_advagg_filename_from_db($type) { + // Get the most recently accessed file from the database. + $query = db_select('advagg_aggregates_versions', 'aav'); + $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = + aav.aggregate_filenames_hash'); + $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND + af.filetype = :type', array(':type' => $type)); + $query = $query->fields('aav', array('aggregate_filenames_hash', 'aggregate_contents_hash')) + ->orderBy('atime', 'DESC') + ->range(0, 1); + $results = $query->execute(); + if (empty($results)) { + return ''; + } + $hooks_hash = advagg_get_current_hooks_hash(); + foreach ($results as $row) { + return $type . ADVAGG_SPACE . $row->aggregate_filenames_hash . ADVAGG_SPACE . $row->aggregate_contents_hash . ADVAGG_SPACE . $hooks_hash . '.' . $type; + } +} + +/** + * Display a message if there are requirement issues with AdvAgg. + * + * @param array $requirements + * Other requirements to list besides the standard ones. + */ +function advagg_display_message_if_requirements_not_met(array $requirements = array()) { + include_once DRUPAL_ROOT . '/includes/install.inc'; + module_load_include('install', 'advagg'); + $requirements += advagg_install_fast_checks(); + if (!empty($requirements)) { + module_load_include('admin.inc', 'system'); + usort($requirements, '_system_sort_requirements'); + $report = theme('status_report', array('requirements' => $requirements)); + drupal_set_message(t('Go to the status report page and fix the issues that AdvAgg lists there. Sneak peak: !report', array( + '@url' => url('admin/reports/status'), + '!report' => $report, + ))); + } +} + +/** + * Add in the preload header for CSS and JS external files. + * + * @param string $url + * The url of the external host. + * + * @return bool + * TRUE if it was added to the head. + */ +function advagg_add_preload_header($url = '', $as = '') { + // Get defaults and setup static's. + $list = &drupal_static(__FUNCTION__ . ':list', array()); + $output = &drupal_static(__FUNCTION__ . ':output'); + $header_strlen = &drupal_static(__FUNCTION__ . ':strlen', 0); + static $resource_hints_preload_order; + if (!isset($resource_hints_preload_order)) { + $resource_hints_preload_order = advagg_get_resource_hints_preload_settings(); + } + if (!isset($output)) { + $keys = array_keys($resource_hints_preload_order); + $output = array_fill_keys($keys, array()); + } + + // Output headers. + if (empty($url)) { + + // Call hook_advagg_preload_header_alter() + drupal_alter('advagg_preload_header', $output); + + // Build header string. + $header_value = ''; + foreach ($output as $value) { + if (!empty($value)) { + // Remove empty values. + $value = array_filter($value); + foreach ($value as $string) { + $header_strlen += strlen($string) + 2; + // Don't add if over the limit. + if ($header_strlen >= variable_get('advagg_resource_hints_preload_max_size', ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE)) { + continue; + } + // Add to $header_value string. + if (empty($header_value)) { + $header_value = $string; + } + else { + $header_value .= ',' . $string; + } + } + } + } + if (!empty($header_value)) { + drupal_add_http_header('Link', $header_value, TRUE); + } + return FALSE; + } + + // Check for duplicates. + if (isset($list[$url])) { + return FALSE; + } + + // Fill in missing info. + $payload = "<{$url}>; rel=preload"; + list($as, $type, $crossorigin, $parse) = advagg_get_preload_info_from_url($url, $as); + if (!empty($as) && $as === 'php') { + $list[$url] = FALSE; + return FALSE; + } + + $additional_info = array(); + if (!empty($crossorigin)) { + $additional_info[] = "crossorigin"; + } + if (!empty($type)) { + $additional_info[] = $type; + } + $additional_info = implode('; ', $additional_info); + + // Get settings. + if (!empty($as) && !empty($resource_hints_preload_order[$as])) { + $settings = $resource_hints_preload_order[$as]; + } + elseif (!empty($resource_hints_preload_order['all_others'])) { + $settings = $resource_hints_preload_order['all_others']; + } + + // Apply settings. + if (!$settings['enabled']) { + $list[$url] = FALSE; + return FALSE; + } + if (!$settings['local'] && empty($parse['host'])) { + $list[$url] = FALSE; + return FALSE; + } + if (!$settings['external'] && !empty($parse['host'])) { + $list[$url] = FALSE; + return FALSE; + } + + // Add additional info and queue. + if (!empty($as)) { + $payload .= "; as={$as}"; + } + if (!empty($additional_info)) { + $payload .= "; {$additional_info}"; + } + if (!$settings['push']) { + $payload .= "; nopush"; + } + $list[$url] = TRUE; + $output[$as][] = $payload; +} + +/** + * Given a link get the as, type, and crossorigin attributes. + * + * @param string $url + * Link to the url that will be preloaded. + * @param string $as + * What type of content is this; font, image, video, etc. + * @param string $type + * The mime type of the file. + * @param string $crossorigin + * Preload cross-origin resources; fonts always need to be CORS. + * + * @return array + * An array containing + */ +function advagg_get_preload_info_from_url($url, $as = '', $type = '', $crossorigin = NULL) { + // Get extension. + $parse = @parse_url($url); + if (empty($parse['path'])) { + return FALSE; + } + $file_ext = strtolower(pathinfo($parse['path'], PATHINFO_EXTENSION)); + if (empty($file_ext)) { + $file_ext = basename($parse['path']); + } + + // Detect missing parts. + $list = advagg_preload_list(); + if (empty($as) && !empty($file_ext)) { + foreach ($list as $as_key => $list_type) { + $key = array_search($file_ext, $list_type); + if ($key !== FALSE) { + $as = $as_key; + // Type of font, ext is svg but file doesn't contain string font. + // This will be treated as an image. + if ($as === 'font' + && $file_ext === 'svg' + && stripos($url, 'font') === FALSE + ) { + $as = ''; + } + } + if (!empty($as)) { + break; + } + } + } + if (empty($type) && !empty($as)) { + $type = "$as/$file_ext"; + if ($file_ext === 'svg') { + $type .= '+xml'; + } + if ($file_ext === 'js') { + $type = 'text/javascript'; + } + } + if ($as === 'font' && is_null($crossorigin)) { + $crossorigin = 'anonymous'; + } + return array($as, $type, $crossorigin, $parse); +} + +/** + * Add preload link to the top of the html head. + * + * @param string $url + * Link to the url that will be preloaded. + * @param string $media + * Media types or media queries, allowing for responsive preloading. + * @param string $as + * What type of content is this; font, image, video, etc. + * @param string $type + * The mime type of the file. + * @param string $crossorigin + * Preload cross-origin resources; fonts always need to be CORS. + */ +function advagg_add_preload_link($url, $media = '', $as = '', $type = '', $crossorigin = NULL) { + static $weight = -2000; + $weight += 0.0001; + + $href = advagg_file_create_url($url); + $key = "advagg_preload:{$href}"; + + // Return here if url has already been added. + $stored_head = drupal_static('drupal_add_html_head'); + if (isset($stored_head[$key])) { + return TRUE; + } + + // Add basic attributes. + $attributes = array( + 'rel' => 'preload', + 'href' => $href, + ); + + // Fill in missing info. + list($as, $type, $crossorigin) = advagg_get_preload_info_from_url($url, $as, $type, $crossorigin); + + // Exit if no as. + if (empty($as)) { + return FALSE; + } + // Build up attributes array. + $attributes['as'] = $as; + if (!empty($type)) { + $attributes['type'] = $type; + } + if (!empty($crossorigin)) { + $attributes['crossorigin'] = $crossorigin; + } + if (!empty($media)) { + $attributes['media'] = $media; + } + // Call hook_advagg_preload_link_attributes_alter() + drupal_alter('advagg_preload_link_attributes', $attributes); + + // Add to HTML head. + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => $attributes, + '#weight' => $weight, + ); + drupal_add_html_head($element, $key); + return TRUE; +} + +/** + * Generate a list of file types for the as field given the extension. + * + * @return array + * Returns an array of arrays. + */ +function advagg_preload_list() { + $list = array( + 'font' => array( + 'woff2', + 'woff', + 'ttf', + 'otf', + 'eot', + // Need to check if the svg file is in a font folder. + 'svg', + ), + 'image' => array( + 'gif', + 'jpg', + 'jpeg', + 'jpe', + 'jif', + 'jfif', + 'jfi', + 'png', + 'webp', + 'jp2', + 'jpx', + 'jxr', + 'heif', + 'heic', + 'bpg', + 'svg', + ), + 'style' => array( + 'css', + ), + 'script' => array( + 'js', + ), + 'video' => array( + 'mp4', + 'webm', + 'ogg', + ), + ); + + // Call hook_advagg_preload_list_alter() + drupal_alter('advagg_preload_list', $list); + return $list; +} + +/** + * Save form defaults or recommended values. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_all_recommended_admin_values(array &$element, $key_name = '#recommended_value') { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child[$key_name]) + ) { + $results[$child['#name']] = $child[$key_name]; + } + $results = array_merge($results, advagg_find_all_recommended_admin_values($child, $key_name)); + } + unset($child); + } + return $results; +} + +/** + * Get form values that have changed. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_all_changed_admin_values(array &$element) { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child['#default_value']) + && isset($child['#value']) + ) { + if ($child['#type'] === 'checkboxes') { + // Add in not selected by default values. + $child['#value'] += array_diff_assoc($child['#default_value'], $child['#value']); + } + if ($child['#default_value'] != $child['#value']) { + $results[$child['#name']] = array($child['#value'], $child['#default_value']); + } + } + $results = array_merge($results, advagg_find_all_changed_admin_values($child)); + } + unset($child); + } + return $results; +} + +/** + * Get form title and description. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_title(array &$element) { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child['#title']) + && isset($child['#default_value']) + && !isset($results[$child['#name']]) + && $child['#type'] !== 'radio' + ) { + $results[$child['#name']] = $child['#title']; + } + $results = array_merge($results, advagg_find_title($child)); + } + unset($child); + } + return $results; +} + +/** + * Save form defaults or recommended values. + * + * @param array $form_state + * Form state array from drupal form submit. + * @param string $trigger_key + * The key of the setting from the form that controls this. + */ +function advagg_set_admin_form_defaults_recommended(array &$form_state, $trigger_key) { + $changed = array(); + $recommended_values = array(); + + // Set to recommended values. + if ($form_state['values'][$trigger_key] == 2) { + $recommended_values = advagg_find_all_recommended_admin_values($form_state['complete form']); + foreach ($recommended_values as $key => $value) { + if (!isset($form_state['values'][$key])) { + $changed[$key] = array($value); + } + elseif ($value != $form_state['values'][$key]) { + $changed[$key] = array($value, $form_state['values'][$key]); + } + $form_state['values'][$key] = $value; + } + } + + // Set to defaults. + if ($form_state['values'][$trigger_key] == 0 || $form_state['values'][$trigger_key] == 2) { + // Reset to defaults. + foreach ($form_state['values'] as $key => &$value) { + // Skip non advagg settings, trigger key, or if a recommended value. + if (strpos($key, 'advagg_') !== 0 + || $key === $trigger_key + || isset($changed[$key]) + || isset($recommended_values[$key]) + ) { + continue; + } + + // Default to FALSE. + $default = FALSE; + // Get easy defaults. + if (defined(strtoupper($key))) { + $default = constant(strtoupper($key)); + } + + // Get more complex default values. + if ($key === 'advagg_resource_hints_preload_settings') { + $default = advagg_get_resource_hints_preload_settings(TRUE); + foreach ($default as $key => &$values) { + $default[$key]['weight'] = $values['#weight']; + unset($default[$key]['#weight'], $values['#weight'], $default[$key]['title'], $values['title']); + ksort($values); + } + ksort($default); + foreach ($value as $key => &$values) { + ksort($values); + } + ksort($value); + } + if ($key === 'advagg_relocate_css_inline_import_browsers') { + $default = array( + 'woff2' => 'woff2', + 'woff' => 'woff', + 'ttf' => 'ttf', + 'eot' => 0, + 'svg' => 0, + ); + } + + // See if it changed. + if ($default != $value) { + // After, Before. + $changed[$key] = array($default, $value); + $value = $default; + } + } + } + + if ($form_state['values'][$trigger_key] == 4) { + $changed = advagg_find_all_changed_admin_values($form_state['complete form']); + if (isset($changed[$trigger_key])) { + unset($changed[$trigger_key]); + } + } + + $all_titles_descriptions = advagg_find_title($form_state['complete form']); + foreach ($changed as $key => $values) { + + // Remove things that didn't really change. + if (isset($values[1])) { + if ($values[0] == $values[1]) { + unset($changed[$key]); + continue; + } + if (is_string($values[0]) + && is_string($values[1]) + && trim($values[0]) == trim($values[1]) + ) { + unset($changed[$key]); + continue; + } + } + + // Make output nicer. + if (!isset($values[1])) { + $values[1] = NULL; + } + if (is_bool($values[0]) && !is_bool($values[1]) + || !is_bool($values[0]) && is_bool($values[1]) + ) { + $values[0] = (bool) $values[0]; + $values[1] = (bool) $values[1]; + } + if (is_int($values[0]) && !is_int($values[1]) + || !is_int($values[0]) && is_int($values[1]) + ) { + $values[0] = (int) $values[0]; + $values[1] = (int) $values[1]; + } + + // Let user know what changed. + if (empty($all_titles_descriptions[$key])) { + drupal_set_message(t('%before -> %after for %title', array( + '%title' => $key, + '%before' => var_export($values[1], TRUE), + '%after' => var_export($values[0], TRUE), + ))); + } + else { + drupal_set_message(t('%before -> %after for %title', array( + '%title' => $all_titles_descriptions[$key], + '%before' => var_export($values[1], TRUE), + '%after' => var_export($values[0], TRUE), + ))); + } + } + + return $changed; +} + +/** + * Given a list of items see what ones need to be inserted/updated or deleted. + * + * @param array $defaults + * Array of default values, representing a row in the db. + * @param mixed $new_values + * Array of edited values, representing a row in the db. + * + * @return array + * Nested array strucutre; only the diff. + */ +function advagg_diff_multi(array $defaults, $new_values) { + $result = array(); + + foreach ($defaults as $key => $val) { + if (is_array($val) && isset($new_values[$key])) { + $tmp = advagg_diff_multi($val, $new_values[$key]); + if ($tmp) { + $result[$key] = $tmp; + } + } + elseif (!isset($new_values[$key])) { + $result[$key] = NULL; + } + elseif ($val != $new_values[$key]) { + $result[$key] = $new_values[$key]; + } + if (isset($new_values[$key])) { + unset($new_values[$key]); + } + } + + $result = $result + $new_values; + return $result; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc new file mode 100644 index 000000000..d7bfd0316 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc @@ -0,0 +1,229 @@ + t('Use HTTP 1.1 settings'), + 2 => t('Use HTTP 2.0 settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_bundler_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_bundler_admin_mode', ADVAGG_BUNDLER_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible.
    Recommended settings are optimized for speed."), + ); + + // Test http2. + $http2_support = 0; + $url = 'https://tools.keycdn.com/http2-query.php?url=' . url('', array('absolute' => TRUE)); + if (filter_var($_SERVER['HTTP_HOST'], FILTER_VALIDATE_IP) === FALSE && $_SERVER['HTTP_HOST'] !== 'localhost') { + $response = drupal_http_request($url, array( + 'timeout' => 3, + )); + } + else { + $response = new stdClass(); + $response->code = 0; + } + if ($response->code == 200 && !empty($response->data)) { + $http2_support = 1; + if (stripos($response->data, 'success') !== FALSE) { + $http2_support = 2; + } + } + if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== FALSE && is_callable('apache_get_modules')) { + $modules = apache_get_modules(); + $key_a = array_search('mod_http2', $modules); + $key_b = array_search('mod_h2', $modules); + if ($key_a !== FALSE || $key_b !== FALSE) { + $http2_support += 4; + } + } + // Display http2 info. + if ($http2_support & 2) { + $description = t('This server supports HTTP 2.0; the test from @url came back ok.', array('@url' => $url)); + } + else { + if ($http2_support == 0) { + $description = t('AdvAgg can not detect if this server supports HTTP 2.0. You can go here to learn how to enable it.', array('@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html')); + } + elseif ($http2_support & 4) { + $description = t('It appears that this server could support HTTP 2.0 (apache mod_http2 found)', array( + '@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html', + )); + if ($http2_support & 1) { + $description .= t(', but it may not be configured to do so. The test from here was inconclusive.', array( + '@test' => $url, + )); + } + else { + if (count($response) == 1) { + $description .= t(', but the test from here was not actually done due to the URL being an IP address or localhost.', array( + '@test' => $url, + )); + } + else { + $description .= t(', but the test from here was inconclusive.', array( + '@test' => $url, + )); + } + } + } + else { + $description = t('This server does not support HTTP 2.0.'); + } + $description .= ' ' . t('This guide can help you get http2 enabled on your site.', array('@url' => 'https://geekflare.com/http2-implementation-apache-nginx/')); + } + if (!empty($description)) { + $form['advagg_http2'] = array( + '#markup' => "

    {$description}

    ", + ); + } + + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_bundler_admin_mode"]' => array('value' => '4'), + ), + ), + ); + + $form['global_container']['advagg_bundler_active'] = array( + '#type' => 'checkbox', + '#title' => t('Bundler is Active (recommended)'), + '#default_value' => variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE), + '#description' => t('If not checked, the bundler will passively monitor your site, but it will not split up aggregates.'), + ); + + $options = array( + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + 9 => 9, + 10 => 10, + 11 => 11, + 12 => 12, + 13 => 13, + 14 => 14, + 15 => 15, + 16 => 16, + 17 => 17, + 18 => 18, + 19 => 19, + 20 => 20, + 21 => 21, + 22 => 22, + 23 => 23, + 24 => 24, + 25 => 25, + ); + $form['global_container']['advagg_bundler_max_css'] = array( + '#type' => 'select', + '#title' => t('Target Number Of CSS Bundles Per Page'), + '#default_value' => variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS), + '#options' => $options, + '#description' => t('If 0 is selected then the bundler is disabled. 2 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-bundler-active' => array('checked' => FALSE), + ), + ), + '#recommended_value' => 25, + ); + $form['global_container']['advagg_bundler_max_js'] = array( + '#type' => 'select', + '#title' => t('Target Number Of JS Bundles Per Page'), + '#default_value' => variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS), + '#options' => $options, + '#description' => t('If 0 is selected then the bundler is disabled. 5 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-bundler-active' => array('checked' => FALSE), + ), + ), + '#recommended_value' => 25, + ); + + $form['global_container']['advagg_bundler_grouping_logic'] = array( + '#type' => 'radios', + '#title' => t('Grouping logic'), + '#default_value' => variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC), + '#options' => array( + 0 => t('File count'), + 1 => t('File size'), + ), + '#description' => t('If file count is selected then each bundle will try to have a similar number of original files aggregated inside of it. If file size is selected then each bundle will try to have a similar file size.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-bundler-active' => array('checked' => FALSE), + ), + ), + '#recommended_value' => 0, + ); + + $form['global_container']['info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Raw Grouping Info'), + ); + module_load_include('inc', 'advagg', 'advagg.admin'); + $analysis = advagg_bundler_analysis('', TRUE); + + $rawtext = print_r($analysis, TRUE); + $form['global_container']['info']['advagg_bundler_info'] = array( + '#type' => 'textarea', + '#title' => t('%count different groupings', array('%count' => count($analysis))), + '#default_value' => $rawtext, + '#rows' => 30, + ); + + // Clear the cache bins on submit. Also remove advagg_bundler_info so it + // doesn't get added to the variable table. + $form['#submit'][] = 'advagg_bundler_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback, clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_bundler_admin_settings_form_submit($form, &$form_state) { + // Clear caches. + advagg_cache_clear_admin_submit(); + + // Unset advagg_bundler_info. + if (isset($form_state['values']['advagg_bundler_info'])) { + unset($form_state['values']['advagg_bundler_info']); + } + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_bundler_admin_mode'); +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.advagg.inc b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.advagg.inc new file mode 100644 index 000000000..1fed98a6e --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.advagg.inc @@ -0,0 +1,286 @@ + $max) { + $remerge_candidate = NULL; + $remerge_key = NULL; + $total_count = 0; + foreach ($final_groupings as $key => $groupings) { + if (count($groupings) > 1) { + if (is_null($remerge_candidate)) { + $remerge_candidate = $groupings; + $remerge_key = $key; + } + elseif (count($remerge_candidate) > count($groupings)) { + $remerge_candidate = $groupings; + $remerge_key = $key; + } + } + } + if (is_null($remerge_candidate)) { + break; + } + advagg_bundler_merge($remerge_candidate, count($remerge_candidate) - 1); + $final_groupings[$remerge_key] = $remerge_candidate; + + $total_count = 0; + foreach ($final_groupings as $key => $groupings) { + $total_count += count($groupings); + } + } + + // Replace $files array with new aggregate filenames. + $files = advagg_generate_filenames($final_groupings, $type); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Merge bundles together if too many where created. + * + * This preserves the order. + * + * @param array $groupings + * Array of requested groups. + * @param int $max + * Max number of grouping. + */ +function advagg_bundler_merge(array &$groupings, $max) { + $group_count = count($groupings); + + if (!empty($max)) { + // Keep going till array has been merged to the desired size. + while ($group_count > $max) { + // Get array sizes. + // Counts the number of files that are placed into each bundle. + $counts = array(); + $group_hash_counts = array(); + foreach ($groupings as $key => $values) { + // Get the group hash counts. + $file_size = 0; + $group_hash_counts[$key] = 0; + foreach ($values as $data) { + // Skip if bundler data is missing. + if (empty($data['bundler'])) { + continue; + } + $file_size += empty($data['bundler']['filesize_processed']) ? $data['bundler']['filesize'] : $data['bundler']['filesize_processed']; + $group_hash_counts[$key] += intval(substr($data['bundler']['group_hash'], 0, 8)); + } + if (variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC) == 0) { + $counts[$key] = count($values); + } + elseif (variable_get('advagg_bundler_grouping_logic', ADVAGG_BUNDLER_GROUPING_LOGIC) == 1) { + $counts[$key] = $file_size; + } + } + + // Create mapping. + // Calculates the file count of potential merges. It only merges with + // neighbors in order to preserve execution order. + $map = array(); + $prev_key = NULL; + foreach ($counts as $key => $val) { + // First run of the foreach loop; populate prev key/values and continue. + // We can't merge with the previous group in this case. + if (is_null($prev_key)) { + $prev_key = $key; + $prev_val = $val; + continue; + } + + // Array key ($prev_val + $val) is the file count of this new group if + // these 2 groups ($prev_key, $key) where to be merged together. + $map[] = array( + ($prev_val + $val) => array($prev_key, $key), + ); + + // Prep for next run. + $prev_key = $key; + $prev_val = $val; + } + + $group_hash_map = array(); + $prev_key = NULL; + foreach ($group_hash_counts as $key => $val) { + // First run of the foreach loop; populate prev key/values and continue. + // We can't merge with the previous group in this case. + if (is_null($prev_key)) { + $prev_key = $key; + $prev_val = $val; + continue; + } + + // Array value ($prev_val + $val) is the hash count of this new group if + // these 2 groups where to be merged together. + $group_hash_map[$prev_key . ' ' . $key] = $prev_val + $val; + + // Prep for next run. + $prev_key = $key; + $prev_val = $val; + } + + // Get best merge candidate. + // We are looking for the smallest file count. $min is populated with a + // large number (15 bits) so it won't be selected in this process. + $min = PHP_INT_MAX; + $first = NULL; + $last = NULL; + $last_min = NULL; + foreach ($map as $v) { + foreach ($v as $key => $values) { + $min = min($min, $key); + // If the min value (number of files in the proposed merged bundle) is + // the same as the key, then get the 2 bundle keys that generated this + // new min value. + if ($min == $key) { + if ($last_min == $min && !is_null($first) && !is_null($last)) { + list($new_first, $new_last) = $values; + // All things being equal pick the smaller count on the hash. + if ($group_hash_map[$first . ' ' . $last] > $group_hash_map[$new_first . ' ' . $new_last]) { + $first = $new_first; + $last = $new_last; + } + } + else { + list($first, $last) = $values; + } + $last_min = $min; + } + } + } + + // Create the new merged set. + $a = $groupings[$first]; + $b = $groupings[$last]; + $new_set = array_merge($a, $b); + + // Rebuild the array with the new set in the correct place. + $new_groupings = array(); + foreach ($groupings as $key => $files) { + if ($key == $first) { + $new_groupings[$first . ' ' . $last] = $new_set; + } + elseif ($key != $last) { + $new_groupings[$key] = $files; + } + } + $groupings = $new_groupings; + --$group_count; + } + } + + // Error prevention below. + // Make sure there isn't a group that has all files that don't exist or empty. + $bad_groups = array(); + foreach ($groupings as $key => $group) { + $missing_counter = 0; + foreach ($group as $data) { + if (empty($data['bundler']['filesize'])) { + ++$missing_counter; + } + } + + // If all files are missing/empty in this group then it is a bad set. + if ($missing_counter == count($group)) { + $bad_groups[$key] = TRUE; + } + } + + // Add the bad groups to the smallest grouping in this set. + if (!empty($bad_groups)) { + $merge_candidate_key = ''; + $merge_candidate_count = PHP_INT_MAX; + $bad_group = array(); + foreach ($groupings as $key => $group) { + if (isset($bad_groups[$key])) { + // Merge all bad groups into one. + $bad_group = array_merge($bad_group, $group); + + // Delete the bad group from the master set. + unset($groupings[$key]); + continue; + } + + // Find the smallest good grouping. + $min = min($merge_candidate_count, count($group)); + if ($min < $merge_candidate_count) { + $merge_candidate_key = $key; + $merge_candidate_count = $min; + } + } + + // Move the bad files into the smallest good group. + $new_set = isset($groupings[$merge_candidate_key]) ? $groupings[$merge_candidate_key] : array(); + $new_set = array_merge($new_set, $bad_group); + $groupings[$merge_candidate_key] = $new_set; + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info new file mode 100644 index 000000000..31477cffe --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info @@ -0,0 +1,13 @@ +name = AdvAgg Bundler +description = Provides intelligent bundling of CSS and JS files by grouping files that belong together. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +configure = admin/config/development/performance/advagg/bundler + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install new file mode 100644 index 000000000..0cfaf6e4e --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install @@ -0,0 +1,37 @@ + 'Bundler', + 'description' => 'Adjust Bundler settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_bundler_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_bundler.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implements hook_advagg_hooks_implemented_alter(). + */ +function advagg_bundler_advagg_hooks_implemented_alter(&$hooks, $all) { + if ($all) { + $hooks['advagg_bundler_analysis_alter'] = array(); + } +} + +/** + * Implements hook_init(). + */ +function advagg_bundler_init() { + if (advagg_bundler_enabled()) { + $GLOBALS['conf']['advagg_core_groups'] = FALSE; + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function advagg_bundler_form_advagg_admin_settings_form_alter(&$form, $form_state) { + if (advagg_bundler_enabled()) { + $form['global']['advagg_core_groups']['#disabled'] = TRUE; + $form['global']['advagg_core_groups']['#description'] = t('The bundler submodule disables core grouping logic.'); + $form['global']['advagg_core_groups']['#states'] = array(); + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * Returns TRUE if the bundler will run. + */ +function advagg_bundler_enabled() { + if (variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE) && (variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS) || variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS))) { + return TRUE; + } +} + +/** + * Given a filename return a bundle key. + * + * @param string $filename + * Filename. + * @param bool $force + * Bypass the cache and get a fresh version of the analysis. + * @param bool $safesql + * Turn off SQL language that might cause errors. + * @param int $depth + * Used to prevent endless loops. + * + * @return string + * String to be used for the grouping key. + */ +function advagg_bundler_analysis($filename = '', $force = FALSE, $safesql = FALSE, $depth = 0) { + // Cache query in a static. + static $analysis = array(); + if (empty($analysis)) { + // See if we have a cached version of this. Generate cache ID. + $query = db_select('advagg_aggregates_versions', 'aav') + ->condition('aav.root', 1) + ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>'); + $query->addExpression('COUNT(aggregate_filenames_hash)', 'counter'); + $count = $query->execute()->fetchField(); + $ideal_cid = 'advagg:bundler_analysis:' . $count; + + if (!$force) { + // Generate cache IDs. + $counts = range(max(0, $count - 3), $count + 3); + foreach ($counts as $count) { + $cache_ids[] = 'advagg:bundler_analysis:' . $count; + } + + // Get a range of cached bundler_analysis data. + $cache_hits = cache_get_multiple($cache_ids, 'cache_advagg_aggregates'); + if (!empty($cache_hits)) { + if (isset($cache_hits[$ideal_cid])) { + $cache = $cache_hits[$ideal_cid]; + } + elseif (!$force && module_exists('httprl') && httprl_is_background_callback_capable()) { + // Setup callback options array. + $callback_options = array( + array( + 'function' => 'advagg_bundler_analysis', + ), + $filename, TRUE, + ); + // Queue up the request. + httprl_queue_background_callback($callback_options); + // Execute request. + httprl_send_request(); + + // Use most recent bundler_analysis data. + $max = 0; + foreach ($cache_hits as $data) { + if ($data->created > $max) { + $max = $data->created; + $cache = $data; + } + } + } + } + } + + if ($force || empty($cache->data)) { + try { + $analysis = advagg_bundler_analyisis_query($safesql); + // Save results to the cache. + cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + catch (PDOException $e) { + if ($depth > 2) { + throw $e; + } + $depth++; + return advagg_bundler_analysis($filename, TRUE, TRUE, $depth); + } + } + else { + $analysis = $cache->data; + } + } + + // If no filename is given pass back then entire query results. + if (empty($filename)) { + return $analysis; + } + + // Return a key to be used in groupings. + if (!empty($analysis[$filename])) { + return $analysis[$filename]; + } + + // We need to return a value that can be used as an array key if the query + // didn't give us anything. + return 0; +} + +/** + * Run the analysis query and return the analysis array. + * + * "Magic Query"; only needs to run once. Results are cached. + * This is what the raw SQL looks like: + * + * @code + * SELECT + * af.filename AS filename, + * af.filesize AS filesize, + * af.mtime AS mtime, + * af.changes AS changes, + * af.linecount AS linecount, + * af.filename_hash AS filename_hash, + * aa.counter AS counter, + * aa.hashlist AS hashlist + * FROM advagg_files af + * INNER JOIN ( + * SELECT + * aa.filename_hash AS filename_hash, + * LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0') AS counter, + * HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC))) AS hashlist + * FROM advagg_aggregates aa + * INNER JOIN advagg_aggregates_versions aav + * ON aav.aggregate_filenames_hash = aa.aggregate_filenames_hash + * AND aav.root = 1 + * AND aav.atime > (UNIX_TIMESTAMP() - 1209600) + * GROUP BY aa.filename_hash + * ) aa ON af.filename_hash = aa.filename_hash + * @endcode + * + * @param bool $safesql + * Turn off SQL language that might cause errors. + * + * @return array + * The analysis array. + */ +function advagg_bundler_analyisis_query($safesql) { + // Return a count of how many root bundles all files are used in. Count is + // padded with eight zeros so the count can be key sorted as a string + // without worrying about it getting put in the wrong order. + // Return the bundle_md5's value; we need something more unique than count + // when grouping together. + // Return the filename. Used for lookup. + // We join the advagg bundles and files together making sure to only use + // root bundles that have been used in the last 2 weeks. This prevents an + // old site structure from influencing new bundles. + // Grouping by the filename gives us the count and makes it so we don't + // return a lot of rows. + $db_type = Database::getConnection()->databaseType(); + $schema = Database::getConnection()->schema(); + if ($safesql) { + $mssql_group_concat = FALSE; + $mssql_lpad = FALSE; + $mssql_md5 = FALSE; + $pg9 = FALSE; + } + else { + $mssql_group_concat = method_exists($schema, 'functionExists') && $schema->functionExists('GROUP_CONCAT'); + $mssql_lpad = method_exists($schema, 'functionExists') && $schema->functionExists('LPAD'); + $mssql_md5 = method_exists($schema, 'functionExists') && $schema->functionExists('MD5'); + if ($db_type === 'pgsql') { + $database_connection = Database::getConnection(); + $pg9 = FALSE; + if (version_compare($database_connection->version(), '9') >= 0) { + $pg9 = TRUE; + } + } + } + + // Create join query for the advagg_aggregates table. + $subquery_aggregates = db_select('advagg_aggregates', 'aa'); + + // Counter column. + $fields = array('counter'); + if ($db_type === 'sqlsrv' && !$mssql_lpad) { + // MS SQL does not support LPAD. + $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter'); + } + elseif ($db_type === 'sqlite') { + // SQLite does not support LPAD. + $subquery_aggregates->addExpression("substr('00000000' || CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), -8, 8)", 'counter'); + } + else { + $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter'); + } + + // Hashlist column. + if ($db_type === 'mysql') { + $fields[] = 'hashlist'; + db_query('SET SESSION group_concat_max_len = 65535'); + $subquery_aggregates->addExpression('HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)))', 'hashlist'); + } + elseif ($db_type === 'pgsql') { + if ($pg9) { + $fields[] = 'hashlist'; + $subquery_aggregates->addExpression("MD5(STRING_AGG(DISTINCT(aa.aggregate_filenames_hash), ',' ORDER BY aa.aggregate_filenames_hash ASC))", 'hashlist'); + } + } + elseif ($db_type === 'sqlite') { + $fields[] = 'hashlist'; + $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); + $subquery_aggregates->orderBy("aa.aggregate_filenames_hash", "ASC"); + } + elseif ($db_type === 'sqlsrv' && $mssql_group_concat) { + $fields[] = 'hashlist'; + if ($mssql_md5) { + $subquery_aggregates->addExpression('MD5(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash))', 'hashlist'); + } + else { + $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist'); + } + // The ORDER BY clause is invalid in views, inline functions, + // derived tables, subqueries, and common table expressions, unless TOP or + // FOR XML is also specified. So no point in doing an order-by like in the + // other cases. + } + + // Create join for the advagg_aggregates_versions table. + // 1209600 = 2 weeks. + $time = REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'); + $subquery_aggregates->join('advagg_aggregates_versions', 'aav', "aav.aggregate_filenames_hash=aa.aggregate_filenames_hash AND aav.root=1 AND aav.atime > $time"); + + $subquery_aggregates = $subquery_aggregates->fields('aa', array('filename_hash')) + ->groupBy('aa.filename_hash'); + + // Create main query for the advagg_files table. + $af_fields = array( + 'filename', + 'filesize', + 'mtime', + 'changes', + 'linecount', + 'filename_hash', + ); + // Make drupal_get_installed_schema_version() available. + include_once DRUPAL_ROOT . '/includes/install.inc'; + if (drupal_get_installed_schema_version('advagg') >= 7211) { + $af_fields[] = 'filesize_processed'; + } + + $query = db_select('advagg_files', 'af'); + $query->join($subquery_aggregates, 'aa', 'af.filename_hash=aa.filename_hash'); + $query = $query->fields('af', $af_fields) + ->fields('aa', $fields); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + $analysis = array(); + foreach ($results as $row) { + // Implement slower GROUP_CONCAT functionality for non mysql databases. + if (empty($row->hashlist)) { + $subquery_aggregates_versions = db_select('advagg_aggregates_versions', 'aav') + ->fields('aav') + ->condition('aav.root', 1) + ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>'); + + $subquery_aggregates = db_select('advagg_aggregates', 'aa'); + $subquery_aggregates->join($subquery_aggregates_versions, 'aav', 'aav.aggregate_filenames_hash=aa.aggregate_filenames_hash'); + $subquery_aggregates = $subquery_aggregates->fields('aa', array('aggregate_filenames_hash')) + ->condition('aa.filename_hash', $row->filename_hash) + ->groupBy('aa.aggregate_filenames_hash') + ->orderBy('aa.aggregate_filenames_hash', 'ASC'); + $subquery_aggregates->comment('Query called from ' . __FUNCTION__ . '()'); + $aa_results = $subquery_aggregates->execute(); + $aa_rows = array(); + foreach ($aa_results as $aa_row) { + $aa_rows[] = $aa_row->aggregate_filenames_hash; + } + $row->hashlist = implode(',', $aa_rows); + } + + $row->hashlist = drupal_hash_base64($row->hashlist); + $analysis[$row->filename] = array( + 'group_hash' => $row->counter . ' ' . $row->hashlist, + 'mtime' => $row->mtime, + 'filesize' => $row->filesize, + 'filesize_processed' => empty($row->filesize_processed) ? $row->filesize : $row->filesize_processed, + 'linecount' => $row->linecount, + 'changes' => $row->changes, + ); + } + arsort($analysis); + + // Invoke hook_advagg_bundler_analysis_alter() to give installed modules a + // chance to alter the analysis array. + drupal_alter('advagg_bundler_analysis', $analysis); + + return $analysis; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.admin.inc new file mode 100644 index 000000000..c9cae0350 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.admin.inc @@ -0,0 +1,381 @@ + ".form-item-lookup{padding-bottom:0;margin-bottom:0;}", + 'type' => 'inline', + ); + $form['add_item']['theme'] = array( + '#type' => 'select', + '#title' => t('Theme'), + '#options' => array_combine($themes, $themes), + '#default_value' => $default_theme, + '#description' => t('Theme Default: %default, Current Theme: %current', array( + '%default' => $default_theme, + '%current' => $global_theme, + )), + ); + $form['add_item']['user'] = array( + '#type' => 'select', + '#title' => t('User type'), + '#default_value' => 0, + '#options' => array( + 'anonymous' => t('anonymous'), + 'authenticated' => t('authenticated'), + 'all' => t('all'), + ), + ); + $type_options = array( + 0 => t('Disabled'), + 2 => t('URL'), + 8 => t('Node Type'), + ); + $form['add_item']['type'] = array( + '#type' => 'select', + '#title' => t('Type of lookup'), + '#default_value' => 2, + '#options' => $type_options, + ); + + $form['add_item']['lookup'] = array( + '#type' => 'textfield', + '#title' => t('Value to lookup'), + '#maxlength' => 255, + '#states' => array( + 'disabled' => array( + ':input[name="type"]' => array('value' => 0), + ), + ), + ); + $form['add_item']['lookup_container_disabled'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 0), + ), + ), + ); + $form['add_item']['lookup_container_disabled']['disabled'] = array( + '#markup' => '
    ', + ); + $form['add_item']['lookup_container_current_path'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 2), + ), + ), + ); + $form['add_item']['lookup_container_current_path']['current_path'] = array( + '#markup' => t('%front is the front page; can use internal URLs like %internal or an alias like %here', array( + '%front' => '', + '%internal' => 'node/2', + '%here' => current_path(), + )), + ); + $form['add_item']['lookup_container_node_type'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 8), + ), + ), + ); + $form['add_item']['lookup_container_node_type']['node_type'] = array( + '#markup' => t('Node type is the machine name of the node; list of node types: @node_types', array( + '@current_path' => 'https://api.drupal.org/api/drupal/includes%21path.inc/function/current_path/7.x', + '@request_path' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_path/7.x', + '@request_uri' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_uri/7.x', + '@node_types' => implode(', ', array_keys(node_type_get_names())), + )), + ); + + $form['add_item']['css'] = array( + '#type' => 'textarea', + '#title' => t('Critical CSS'), + '#description' => t('Can be generated via https://www.sitelocity.com/critical-path-css-generator. If this field is empty this entry will be deleted.', array( + '@url' => 'https://www.sitelocity.com/critical-path-css-generator', + )), + '#default_value' => '', + ); + $form['add_item']['dns'] = array( + '#type' => 'textarea', + '#title' => t('Hostnames to lookup'), + '#description' => t('Hosts that will be connected to.'), + '#default_value' => '', + ); + $form['add_item']['pre'] = array( + '#type' => 'textarea', + '#title' => t('Urls to Preload'), + '#description' => t('Assets for the browser that should be downloaded at a high priority.'), + '#default_value' => '', + ); + + // Lookup saved data. + $query = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + // Put results into array. + $counter = 0; + foreach ($results as $row) { + $counter++; + $row = (array) $row; + + foreach ($form['add_item'] as $key => $values) { + // Fix the states array for type. + if (!empty($values['#states'])) { + foreach ($values['#states'] as $states_key => $states_values) { + $states_value = reset($values['#states'][$states_key]); + $values['#states'][$states_key] = array(":input[name=\"{$counter}_type\"]" => $states_value); + } + } + $form['existing_items'][$counter]["{$counter}_{$key}"] = $values; + if (isset($row[$key])) { + $form['existing_items'][$counter]["{$counter}_{$key}"]['#default_value'] = $row[$key]; + } + } + + // Add in css to move the text hint up. + $form['#attached']['css'][] = array( + 'data' => ".form-item-{$counter}-lookup{padding-bottom:0;margin-bottom:0;}", + 'type' => 'inline', + ); + + // Add fieldset. + $filename = advagg_url_to_filename($row['lookup'], FALSE); + $base = drupal_get_path('theme', $row['theme']) . "/critical-css/{$row['user']}/"; + if ($row['type'] == 2) { + $base .= "urls/$filename"; + } + elseif ($row['type'] == 8) { + $base .= "type/$filename"; + } + else { + $base = ''; + } + $form['existing_items'][$counter] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('@type @theme @user @lookup', array( + '@theme' => $row['theme'], + '@type' => $type_options[$row['type']], + '@user' => $row['user'], + '@lookup' => $row['lookup'], + )), + ); + + if (!empty($base)) { + $form['existing_items'][$counter]['#description'] = t('If you wish to store this configuration in a file
    Critical CSS: @css', array( + '@css' => "$base.css", + )); + if (!empty($row['dns'])) { + $form['existing_items'][$counter]['#description'] .= t('
    Hostnames: @dns', array( + '@dns' => "$base.dns", + )); + } + if (!empty($row['pre'])) { + $form['existing_items'][$counter]['#description'] .= t('
    Preload: @pre', array( + '@pre' => "$base.pre", + )); + } + } + } + + // Add top level fieldsets. + $form['add_item'] += array( + '#type' => 'fieldset', + '#title' => t('Add Critical CSS'), + '#collapsible' => TRUE, + '#collapsed' => $results->rowCount(), + ); + if (!empty($form['existing_items'])) { + $form['existing_items'] += array( + '#type' => 'fieldset', + '#title' => t('Edit Critical CSS'), + ); + } + + $form['advagg_critical_css_selector_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('Selector Blacklist'), + '#description' => t('Selectors to exclude. Enter one per line. Useful for things like google ads.'), + '#default_value' => variable_get('advagg_critical_css_selector_blacklist', ''), + ); + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_critical_css_admin_settings_form_submit'; + + // Most code below taken from system_settings_form(). + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + $form['actions']['disable'] = array( + '#type' => 'submit', + '#value' => t('Disable All From Database'), + '#submit' => array('advagg_critical_css_admin_settings_form_submit_disable'), + ); + if (!empty($_POST) && form_get_errors()) { + drupal_set_message(t('The settings have not been saved because of the errors.'), 'error'); + } + // By default, render the form using theme_system_settings_form(). + if (!isset($form['#theme'])) { + $form['#theme'] = 'system_settings_form'; + } + return $form; +} + +/** + * Submit callback, process the advagg_critical_css form. + * + * Also clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_critical_css_admin_settings_form_submit_disable($form, &$form_state) { + $query = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + // Put results into array. + $insert_update = array(); + foreach ($results as $row) { + $row = (array) $row; + $new_row = $row; + $new_row['type'] = 0; + $insert_update[] = array( + $row, + $new_row, + ); + } + advagg_critical_css_table_insert_update($insert_update); +} + +/** + * Submit callback, process the advagg_critical_css form. + * + * Also clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_critical_css_admin_settings_form_submit($form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + + // Save advagg_critical_css_selector_blacklist. + if (!isset($form_state['values']['advagg_critical_css_selector_blacklist'])) { + $form_state['values']['advagg_critical_css_selector_blacklist'] = ''; + } + $advagg_critical_css_selector_blacklist = variable_get('advagg_critical_css_selector_blacklist', ''); + if ($form_state['values']['advagg_critical_css_selector_blacklist'] !== $advagg_critical_css_selector_blacklist) { + variable_set('advagg_critical_css_selector_blacklist', $form_state['values']['advagg_critical_css_selector_blacklist']); + } + unset($form_state['values']['advagg_critical_css_selector_blacklist']); + + // Rearrange form values into key value pairs. + $items = advagg_critical_css_get_rows_from_form($form_state['values']); + // Get default values. + $default_values = advagg_find_all_recommended_admin_values($form_state['complete form'], '#default_value'); + unset($default_values['form_token']); + $default_items = advagg_critical_css_get_rows_from_form($default_values); + + // Get diff, see what items need to be saved. + $diff = advagg_diff_multi($default_items, $items); + $changed_items = array(); + foreach ($diff as $key => $values) { + $changed_items[$key] = $items[$key]; + } + + // Get items to insert/update and delete. + list($insert_update, $delete) = advagg_critical_css_get_db_operations_arrays($changed_items, $default_items); + advagg_critical_css_table_insert_update($insert_update); + advagg_critical_css_table_delete($delete); + + // Clear caches. + advagg_cache_clear_admin_submit(); + drupal_set_message(t('The configuration options have been saved.')); +} + +/** + * Translate from state values into a nested array strucutre. + * + * @param array $form_state_values + * From state values; from $form_state['values']. + * + * @return array + * Nested array strucutre, each index is a row in the db. + */ +function advagg_critical_css_get_rows_from_form(array $form_state_values) { + $items = array(); + $counter = 0; + foreach ($form_state_values as $key => $values) { + // Get the index from the start of the form name. + $matches = array(); + // 1_type turns into $counter = 1 and $key = type. + preg_match('/^(\d)_(.*)/', $key, $matches); + if (!empty($matches)) { + $counter = $matches[1]; + $key = $matches[2]; + } + $items[$counter][$key] = $values; + } + return $items; +} + +/** + * Given a list of items see what ones need to be inserted/updated or deleted. + * + * @param array $items + * Array of values, representing a row in the db. + * + * @return array + * Nested array strucutre, index 0 is the insert update, 1 is the deleted. + */ +function advagg_critical_css_get_db_operations_arrays(array $items, array $old_items) { + $insert_update = array(); + $delete = array(); + foreach ($items as $key => $values) { + // If the css is empty then this needs to be deleted. + if (empty($values['css'])) { + // Do not delete the new items entry (0); it's not in the db currently. + if (!empty($key)) { + $delete[$key] = $values; + } + } + else { + // Pass along the old key value pairs for db_merge. + if (!empty($old_items[$key])) { + $keys = $old_items[$key] + $values; + } + else { + $keys = $values; + } + $insert_update[$key] = array($keys, $values); + } + } + return array($insert_update, $delete); +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.info b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.info new file mode 100644 index 000000000..a6a78db0a --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.info @@ -0,0 +1,14 @@ +name = AdvAgg Critical CSS +description = Control Critical CSS via UI and/or a 3rd party service. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg +dependencies[] = advagg_mod + +configure = admin/config/development/performance/advagg/critical-css + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.install b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.install new file mode 100644 index 000000000..98c954d6f --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.install @@ -0,0 +1,84 @@ + 'The critical css to inline.', + 'fields' => array( + 'theme' => array( + 'description' => 'The theme name.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'type' => array( + 'description' => 'Type like url or node.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'user' => array( + 'description' => 'User Type or UID.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'lookup' => array( + 'description' => 'Value from current_path if url or node type if node.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'css' => array( + 'description' => 'Critical CSS.', + 'type' => 'blob', + 'size' => 'big', + ), + 'dns' => array( + 'description' => 'Hosts for dns lookedup.', + 'type' => 'blob', + 'size' => 'big', + ), + 'pre' => array( + 'description' => 'URLs for preloading.', + 'type' => 'blob', + 'size' => 'big', + ), + 'settings' => array( + 'description' => 'Extra settings if desired.', + 'type' => 'blob', + 'size' => 'big', + 'translatable' => TRUE, + 'serialize' => TRUE, + ), + ), + 'primary key' => array( + 'lookup', + 'user', + 'type', + 'theme', + ), + ); + + return $schema; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.module b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.module new file mode 100644 index 000000000..6bd3c5727 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_critical_css/advagg_critical_css.module @@ -0,0 +1,239 @@ + 'Critical CSS', + 'description' => 'Control critical css.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_critical_css_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_critical_css.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_critical_css_module_implements_alter(&$implementations, $hook) { + // Move critical_css_advagg_mod_critical_css_file_pre_alter to the bottom. + if ($hook === 'critical_css_advagg_mod_critical_css_file_pre_alter' && array_key_exists('advagg_critical_css', $implementations)) { + $item = $implementations['advagg_critical_css']; + unset($implementations['advagg_critical_css']); + $implementations['advagg_critical_css'] = $item; + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_mod_critical_css_file_pre_alter(). + */ +function advagg_critical_css_advagg_mod_critical_css_file_pre_alter(&$filename, &$params, &$inline_strings) { + list($dirs, $front_page, $object) = $params; + + // Build query parameters. + $lookup = array($dirs[6]); + if ($front_page) { + $lookup = array(''); + } + $lookup[] = $dirs[9]; + $lookup[] = $dirs[10]; + if (!empty($object->type)) { + $lookup[] = $object->type; + } + $type = array(2, 8); + $users = array(rtrim($dirs[2], '/\\'), rtrim($dirs[3], '/\\')); + + // Get Results. + $result = advagg_critical_css_table_get($GLOBALS['theme'], $type, $lookup, $users); + + // Put into the inline strings array. + if (!empty($result)) { + // Set string values. + $inline_strings[0] = $result['css']; + $inline_strings[1] = $result['dns']; + $inline_strings[2] = $result['pre']; + // Disable file lookup. + $dirs[0] = ''; + $dirs[1] = ''; + } + + // Repack the $params array. + $params = array($dirs, $front_page, $object); +} + +/** + * Implements hook_advagg_mod_critical_css_file_post_alter(). + */ +function advagg_critical_css_advagg_mod_critical_css_file_post_alter(&$filename, &$params, &$inline_strings) { + if (!empty($inline_strings[0])) { + // Remove given css selectors. + $selectors = variable_get('advagg_critical_css_selector_blacklist', ''); + $selectors_array = array_filter(array_map('trim', explode("\n", $selectors))); + foreach ($selectors_array as $pattern) { + $pattern = preg_quote($pattern, '/'); + $pattern = "/([^}]*{$pattern}[^{]*[^}]*\})/s"; + $inline_strings[0] = preg_replace($pattern, '', $inline_strings[0]); + } + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Get the db select return object. + * + * @param string $theme + * Name of the current theme. + * @param array $type + * Array of int types to lookup. + * @param array $lookup + * The lookup value. + * @param array $user + * Array of user string values. + * + * @return SelectQuery + * Return the SelectQuery object after it has been executed. + */ +function advagg_critical_css_table_get($theme, array $type, array $lookup, array $user) { + $output = array(); + try { + $results = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->condition('theme', $theme) + ->condition('type', $type, 'IN') + ->condition('user', $user, 'IN') + ->condition('lookup', $lookup, 'IN') + ->orderBy('type', 'DESC') + ->execute(); + + // Get first result. + $output = $results->fetchAssoc(); + + // Check for a better match in other results if they exist. + foreach ($results as $values) { + $values = (array) $values; + if ($values['type'] < $output['type']) { + $output = $values; + break; + } + if ($values['type'] = $output['type']) { + if (($values['user'] === 'anonymous' || $values['user'] === 'authenticated') + && $output['user'] === 'all' + ) { + $output = $values; + break; + } + if (is_int($values['user'])) { + $output = $values; + break; + } + } + } + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } + } + + return $output; +} + +/** + * Insert/Update data in the advagg_critical_css table. + * + * @param array $records + * List of rows needed that need to be changed in the db. + * + * @return array + * Return array of booleans if anything was written to the database. + */ +function advagg_critical_css_table_insert_update(array $records) { + $return = array(); + foreach ($records as $values) { + list($keys, $record) = $values; + if (!isset($record['settings'])) { + $record['settings'] = ''; + } + try { + $return[] = db_merge('advagg_critical_css') + ->key(array( + 'theme' => $keys['theme'], + 'user' => $keys['user'], + 'type' => $keys['type'], + 'lookup' => $keys['lookup'], + )) + ->fields($record) + ->execute(); + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } + } + } + return $return; +} + +/** + * Delete data in the advagg_critical_css table. + * + * @param array $records + * List of rows needed that need to be removed from the db. + * + * @return array + * Return array of booleans if anything was removed from the database. + */ +function advagg_critical_css_table_delete(array $records) { + $return = array(); + foreach ($records as $record) { + try { + $return[] = db_delete('advagg_critical_css') + ->condition('theme', $record['theme']) + ->condition('user', $record['user']) + ->condition('type', $record['type']) + ->condition('lookup', $record['lookup']) + ->execute(); + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: @info', array('@info' => $e)); + } + } + } + return $return; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.info b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.info new file mode 100644 index 000000000..e6e0319a9 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.info @@ -0,0 +1,11 @@ +name = AdvAgg CDN CSS +description = Use a shared CDN for CSS libraries, Google Libraries API currently. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.install b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.install new file mode 100644 index 000000000..213c2599c --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.install @@ -0,0 +1,65 @@ +jquery update settings page and select a CDN instead of using this module.', array( + '@settings' => url('admin/config/development/jquery_update', array( + 'fragment' => 'edit-jquery-update-jquery-cdn', + )), + )); + } + else { + $jquery_description = $t('The jquery update module is already configured to use the external CDN "@cdn".', array('@cdn' => $jquery_cdn)); + } + + $requirements['advagg_css_cdn_jquery_update'] = array( + 'title' => $t('Adv CSS CDN - jquery update'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Duplicate Functionality: Use jquery update instead of the advagg_css_cdn sub module.'), + 'description' => $jquery_description . ' ' . $t('You should go to the modules page and disable the "AdvAgg CDN CSS" module.', array( + '@modules' => url('admin/modules', array( + 'fragment' => 'edit-modules-advanced-cssjs-aggregation', + )), + )), + ); + } + + if (empty($requirements)) { + $requirements['advagg_css_cdn'] = array( + 'title' => $t('Adv CSS CDN'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('OK'), + 'description' => $t('jQuery UI CSS should be coming from a CDN.'), + ); + } + + return $requirements; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.module b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.module new file mode 100644 index 000000000..bd6cd4283 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_cdn/advagg_css_cdn.module @@ -0,0 +1,195 @@ + $values) { + // Only modify if + // advagg_css_cdn_jquery_ui is enabled, + // name is in the $ui_mapping array. + // and type is file. + if (variable_get('advagg_css_cdn_jquery_ui', ADVAGG_CSS_CDN_JQUERY_UI) + && array_key_exists($name, $ui_mapping) + && $css[$name]['type'] === 'file' + ) { + $css[$name]['data'] = '//ajax.googleapis.com/ajax/libs/jqueryui/' . $jquery_ui_version . '/themes/base/jquery.' . $ui_mapping[$name] . '.css'; + $css[$name]['type'] = 'external'; + + // Fallback does not work do to + // "SecurityError: The operation is insecure.". + } + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_css_groups_alter(). + */ +function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { + // Work around a bug with seven_css_alter. + // http://drupal.org/node/1937860 + $theme_keys[] = $GLOBALS['theme']; + if (!empty($GLOBALS['base_theme_info'])) { + foreach ($GLOBALS['base_theme_info'] as $base) { + $theme_keys[] = $base->name; + } + } + $match = FALSE; + foreach ($theme_keys as $name) { + if ($name === 'seven') { + $match = TRUE; + } + } + if (empty($match)) { + return; + } + + $target = FALSE; + $last_group = FALSE; + $last_key = FALSE; + $kill_key = FALSE; + $replaced = FALSE; + foreach ($css_groups as $key => $group) { + if (empty($target)) { + if ($group['type'] === 'external' && $group['preprocess'] && $preprocess_css) { + foreach ($group['items'] as $k => $value) { + if ($value['data'] === 'themes/seven/jquery.ui.theme.css') { + // Type should be file and not external (core bug). + $value['type'] = 'file'; + $target = $value; + unset($css_groups[$key]['items'][$k]); + if (empty($css_groups[$key]['items'])) { + unset($css_groups[$key]); + $kill_key = $key; + } + } + } + } + } + else { + $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); + if ($group['type'] != $target['type'] + || $group['group'] != $target['group'] + || $group['every_page'] != $target['every_page'] + || $group['media'] != $target['media'] + || $group['media'] != $target['media'] + || $group['preprocess'] != $target['preprocess'] + || !empty($diff) + ) { + if (!empty($last_group)) { + $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); + if ($last_group['type'] != $target['type'] + || $last_group['group'] != $target['group'] + || $last_group['every_page'] != $target['every_page'] + || $last_group['media'] != $target['media'] + || $last_group['media'] != $target['media'] + || $last_group['preprocess'] != $target['preprocess'] + || !empty($diff) + ) { + // Insert New. + $css_groups[$kill_key] = array( + 'group' => $target['group'], + 'type' => $target['type'], + 'every_page' => $target['every_page'], + 'media' => $target['media'], + 'preprocess' => $target['preprocess'], + 'browsers' => $target['browsers'], + 'items' => array($target), + ); + $replaced = TRUE; + } + else { + // Insert above. + $css_groups[$last_key]['items'][] = $target; + $replaced = TRUE; + } + } + } + else { + // Insert below. + array_unshift($css_groups[$key]['items'], $target); + $replaced = TRUE; + } + } + $last_group = $group; + $last_key = $key; + if ($replaced) { + break; + } + } + ksort($css_groups); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Return an array of jquery ui files. + */ +function advagg_css_cdn_get_ui_mapping() { + // Replace jQuery UI's CSS, beginning by defining the mapping. + $ui_mapping = array( + 'misc/ui/jquery.ui.accordion.css' => 'ui.accordion', + 'misc/ui/jquery.ui.autocomplete.css' => 'ui.autocomplete', + 'misc/ui/jquery.ui.button.css' => 'ui.button', + 'misc/ui/jquery.ui.core.css' => 'ui.core', + 'misc/ui/jquery.ui.datepicker.css' => 'ui.datepicker', + 'misc/ui/jquery.ui.dialog.css' => 'ui.dialog', + 'misc/ui/jquery.ui.progressbar.css' => 'ui.progressbar', + 'misc/ui/jquery.ui.resizable.css' => 'ui.resizable', + 'misc/ui/jquery.ui.selectable.css' => 'ui.selectable', + 'misc/ui/jquery.ui.slider.css' => 'ui.slider', + 'misc/ui/jquery.ui.tabs.css' => 'ui.tabs', + 'misc/ui/jquery.ui.theme.css' => 'ui.theme', + ); + return $ui_mapping; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc new file mode 100644 index 000000000..66e1a1076 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc @@ -0,0 +1,157 @@ + '

    ' . t('The settings below will not have any effect because AdvAgg is currently in development mode. Once the cache settings have been set to normal or aggressive, CSS minification will take place.', array('@devel' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-cache-level')))) . '

    ', + ); + } + + // Tell user to update library if a new version is available. + $library_name = 'YUI-CSS-compressor-PHP-port'; + $module_name = 'advagg_css_compress'; + list($description) = advagg_get_version_description($library_name, $module_name); + if (!empty($description)) { + $form['advagg_version_msg'] = array( + '#markup' => "

    {$description}

    ", + ); + } + + list($options, $description) = advagg_css_compress_configuration(); + + $form['advagg_css_compressor'] = array( + '#type' => 'radios', + '#title' => t('File Compression: Select a Compressor'), + '#default_value' => variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR), + '#options' => $options, + '#description' => filter_xss($description), + ); + $inline_options = $options; + unset($inline_options[-1]); + $inline_options[0] = t('Disabled'); + $form['advagg_css_compress_inline'] = array( + '#type' => 'radios', + '#title' => t('Inline Compression: Select a Compressor'), + '#default_value' => variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE), + '#options' => $inline_options, + '#description' => filter_xss($description), + ); + $form['advagg_css_compress_inline_if_not_cacheable'] = array( + '#type' => 'checkbox', + '#title' => t('Inline Compression: Use even if this page is not cacheable'), + '#default_value' => variable_get('advagg_css_compress_inline_if_not_cacheable', ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE), + '#description' => t('By checking this box, all Inline CSS will be compressed regardless of the state of drupal_page_is_cacheable().', array('@link' => 'http://api.drupal.org/api/drupal/includes!bootstrap.inc/function/drupal_page_is_cacheable/7')), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_css_compress_inline"]' => array('value' => "0"), + ), + ), + ); + + $options[ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS] = t('Default'); + ksort($options); + + $form['per_file_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Per File Settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + // Get filename and filename_hash. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename')) + ->condition('filetype', 'css') + ->orderBy('filename', 'ASC') + ->execute(); + $file_settings = variable_get('advagg_css_compressor_file_settings', array()); + foreach ($results as $row) { + $dir = dirname($row->filename); + if (!isset($form['per_file_settings'][$dir])) { + $form['per_file_settings'][$dir] = array( + '#type' => 'fieldset', + '#title' => check_plain($dir), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + } + $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $row->filename); + $form['per_file_settings'][$dir]['advagg_css_compressor_file_settings_' . $form_api_filename] = array( + '#type' => 'radios', + '#title' => t('%filename: Select a Compressor', array('%filename' => $row->filename)), + '#default_value' => isset($file_settings[$form_api_filename]) ? $file_settings[$form_api_filename] : ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS, + '#options' => $options, + ); + if ($form['per_file_settings'][$dir]['advagg_css_compressor_file_settings_' . $form_api_filename]['#default_value'] != ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS) { + $form['per_file_settings'][$dir]['#collapsed'] = FALSE; + $form['per_file_settings']['#collapsed'] = FALSE; + } + } + + // No css files are found. + if (empty($results)) { + $form['per_file_settings']['#description'] = t('No CSS files have been aggregated. You need to enable aggregation. No css files where found in the advagg_files table.'); + } + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_css_compress_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback that clears out the advagg cache bin. + * + * Also remove default settings inside of the per_file_settings fieldgroup. + * + * @ingroup advagg_forms_callback + */ +function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { + // Clear caches. + advagg_cache_clear_admin_submit(); + + // Get current defaults. + $file_settings = variable_get('advagg_css_compressor_file_settings', array()); + + // Save per file settings. + $new_settings = array(); + foreach ($form_state['values'] as $key => $value) { + // Skip if not advagg_css_compressor_file_settings. + if (strpos($key, 'advagg_css_compressor_file_settings_') === FALSE) { + continue; + } + // Do not process default settings. + if ($value == ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS) { + unset($form_state['values'][$key]); + continue; + } + $new_settings[substr($key, 36)] = $value; + + // Do not save this field into its own variable. + unset($form_state['values'][$key]); + } + if (!empty($new_settings) || !empty($file_settings)) { + if (empty($new_settings)) { + variable_del('advagg_css_compressor_file_settings'); + } + else { + variable_set('advagg_css_compressor_file_settings', $new_settings); + } + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.advagg.inc b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.advagg.inc new file mode 100644 index 000000000..d152670a4 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.advagg.inc @@ -0,0 +1,110 @@ + $settings) { + if (!empty($aggregate_settings['variables']['advagg_css_compressor_file_settings'])) { + $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $filename); + if (isset($aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename])) { + $aggregate_settings['variables']['advagg_css_compressor'] = $aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename]; + // If one file can not be compressed then the whole aggregrate can not + // be compressed. + if ($aggregate_settings['variables']['advagg_css_compressor'] == 0) { + break; + } + } + } + } + + // Do nothing if the compressor is disabled. + if (empty($aggregate_settings['variables']['advagg_css_compressor'])) { + return; + } + // Do nothing if the cache settings are set to Development. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + return; + } + + list(, , , $functions) = advagg_css_compress_configuration(); + + if (isset($functions[$aggregate_settings['variables']['advagg_css_compressor']])) { + $run = $functions[$aggregate_settings['variables']['advagg_css_compressor']]; + if (function_exists($run)) { + $functions[$aggregate_settings['variables']['advagg_css_compressor']]($data); + } + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Use the CSSmin library from YUI to compress the CSS. + */ +function advagg_css_compress_yui_cssmin(&$data) { + // Try libraries for YUI. + if (is_callable('libraries_load')) { + libraries_load('YUI-CSS-compressor-PHP-port'); + if (class_exists('tubalmartin\CssMin\Minifier')) { + // The "use" alias requires php 5.3. + // @codingStandardsIgnoreLine + $cssmin = new tubalmartin\CssMin\Minifier(); + } + elseif (class_exists('CSSmin')) { + $cssmin = new CSSmin(); + } + } + if (!isset($cssmin)) { + // Load CSSMin.inc if the CSSmin class variable is not set. + if (!class_exists('CSSmin')) { + include drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; + } + $cssmin = new CSSmin(); + } + if (!isset($cssmin)) { + return; + } + + // Set line break to 4k of text. + if (method_exists($cssmin, 'setLineBreakPosition')) { + $cssmin->setLineBreakPosition(4096); + } + // Compress the CSS splitting lines after 4k of text. + if (method_exists($cssmin, 'run')) { + $compressed = $cssmin->run($data, 4096); + } + if (!empty($compressed)) { + $data = $compressed; + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info new file mode 100644 index 000000000..aba0cc76e --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info @@ -0,0 +1,14 @@ +name = AdvAgg Compress CSS +description = Compress CSS with a 3rd party compressor, YUI currently. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg +recommends[] = libraries + +configure = admin/config/development/performance/advagg/css-compress + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install new file mode 100644 index 000000000..b4daf3f6b --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install @@ -0,0 +1,128 @@ + $t('AdvAgg CSS Compressor'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('AdvAgg CSS Compression is disabled.'), + 'description' => $t('Go to the advagg css compress settings page and select a compressor, or go to the modules page and disable the "AdvAgg Compress CSS" module.', array( + '@settings' => url($config_path . '/advagg/css-compress'), + '@modules' => url('admin/modules', array( + 'fragment' => 'edit-modules-advanced-cssjs-aggregation', + )), + )), + ); + } + } + + // Check version. + $lib_name = 'YUI-CSS-compressor-PHP-port'; + $module_name = 'advagg_css_compress'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + + return $requirements; +} + +/** + * Upgrade AdvAgg CSS Compress versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. + */ +function advagg_css_compress_update_7200(&$sandbox) { + // Bail if old DB Table does not exist. + if (!db_table_exists('cache_advagg_css_compress_inline')) { + return t('Nothing needed to happen in AdvAgg CSS Compress.'); + } + + // Remove all old advagg css compress variables. + db_delete('variable') + ->condition('name', 'advagg_css%compress%', 'LIKE') + ->execute(); + + // Remove old schema. + db_drop_table('cache_advagg_css_compress_inline'); + + return t('Upgraded AdvAgg CSS Compress to 7.x-2.x.'); +} + +/** + * Change variable names so they are prefixed with the modules name. + */ +function advagg_css_compress_update_7201(&$sandbox) { + // Rename advagg_css_inline_compressor to advagg_css_compress_inline. + $old = variable_get('advagg_css_inline_compressor', NULL); + if (!is_null($old)) { + variable_del('advagg_css_inline_compressor'); + } + if ($old !== ADVAGG_CSS_COMPRESS_INLINE) { + variable_set('advagg_css_compress_inline', $old); + } + + // Rename advagg_css_inline_compress_if_not_cacheable to + // advagg_css_compress_inline_if_not_cacheable. + $old = variable_get('advagg_css_inline_compress_if_not_cacheable', NULL); + if (!is_null($old)) { + variable_del('advagg_css_inline_compress_if_not_cacheable'); + } + if ($old !== ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE) { + variable_set('advagg_css_compress_inline_if_not_cacheable', $old); + } +} + +/** + * Remove unused variables from the variable table. + */ +function advagg_css_compress_update_7202(&$sandbox) { + // Remove all old advagg css compress variables. + db_delete('variable') + ->condition('name', 'advagg_css_compressor_file_settings_%', 'LIKE') + ->execute(); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.module b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.module new file mode 100644 index 000000000..5f6fbb231 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.module @@ -0,0 +1,231 @@ + 'CSS Compression', + 'description' => 'Adjust CSS Compression settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_css_compress_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_css_compress.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_current_hooks_hash_array_alter(). + */ +function advagg_css_compress_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + $aggregate_settings['variables']['advagg_css_compressor'] = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR); + $aggregate_settings['variables']['advagg_css_compressor_file_settings'] = variable_get('advagg_css_compressor_file_settings', array()); +} + +/** + * Implements hook_advagg_modify_css_pre_render_alter(). + * + * Used to compress inline css. + */ +function advagg_css_compress_advagg_modify_css_pre_render_alter(&$children, &$elements) { + // Get variables. + $compressor = variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE); + + // Do nothing if the compressor is disabled. + if (empty($compressor)) { + return; + } + + // Do nothing if the page is not cacheable and inline compress if not + // cacheable is not checked. + if (!variable_get('advagg_css_compress_inline_if_not_cacheable', ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE) && !drupal_page_is_cacheable()) { + return; + } + + module_load_include('inc', 'advagg_css_compress', 'advagg_css_compress.advagg'); + if ($compressor == 2) { + // Compress any inline CSS with YUI. + foreach ($children as &$values) { + if (!empty($values['#value'])) { + advagg_css_compress_yui_cssmin($values['#value']); + } + } + unset($values); + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_css_compress_libraries_info() { + $libraries['YUI-CSS-compressor-PHP-port'] = array( + // Only used in administrative UI of Libraries API. + 'name' => 'YUI CSS compressor PHP port', + 'vendor url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port', + 'download url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/archive/master.zip', + 'version callback' => 'advagg_css_compress_libraries_get_version', + 'version arguments' => array( + 'file' => 'README.md', + 'pattern' => '/###\s+v([0-9a-zA-Z\.-]+)/', + 'lines' => 1000, + 'cols' => 20, + 'default_version' => '2.4.8', + ), + 'local version' => '2.4.8-p10', + 'remote' => array( + 'callback' => 'advagg_get_github_version_txt', + 'url' => 'https://cdn.jsdelivr.net/gh/tubalmartin/YUI-CSS-compressor-PHP-port@master/README.md', + ), + 'versions' => array( + '2' => array( + 'files' => array( + 'php' => array( + 'cssmin.php', + 'data/hex-to-named-color-map.php', + 'data/named-to-hex-color-map.php', + ), + ), + ), + '3' => array( + 'files' => array( + 'php' => array( + 'src/Minifier.php', + 'src/Utils.php', + 'src/Colors.php', + 'src/data/hex-to-named-color-map.php', + 'src/data/named-to-hex-color-map.php', + ), + ), + ), + '4' => array( + 'files' => array( + 'php' => array( + 'src/Minifier.php', + 'src/Utils.php', + 'src/Colors.php', + ), + ), + ), + ), + ); + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * Try libraries_get_version(), on failure use the passed in default_version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * + * @return string + * Version number. + */ +function advagg_css_compress_libraries_get_version(array $library, array $options) { + $return = libraries_get_version($library, $options); + if (empty($return) && !empty($options['default_version'])) { + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + if (is_readable($file)) { + return $options['default_version']; + } + } + return $return; +} + +/** + * Generate the js compress configuration. + * + * @return array + * Array($options, $description, $compressors, $functions). + */ +function advagg_css_compress_configuration() { + $description = ''; + $options = array( + -1 => t('Disable Core'), + 0 => t('Core'), + 2 => t('YUI'), + ); + + $compressors = array(); + $functions = array( + 2 => 'advagg_css_compress_yui_cssmin', + ); + + // Allow for other modules to alter this list. + $options_desc = array($options, $description); + drupal_alter('advagg_css_compress_configuration', $options_desc, $compressors, $functions); + list($options, $description) = $options_desc; + + return array($options, $description, $compressors, $functions); +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc new file mode 100644 index 000000000..88df45130 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc @@ -0,0 +1,1263 @@ +memoryLimit = 128 * 1048576; // 128MB in bytes + $this->pcreBacktrackLimit = 1000 * 1000; + $this->pcreRecursionLimit = 500 * 1000; + + $this->raisePhpLimits = (bool) $raisePhpLimits; + + $this->numRegex = '(?:\+|-)?\d*\.?\d+' . $this->unitsGroupRegex .'?'; + } + + /** + * Minifies a string of CSS + * @param string $css + * @param int|bool $linebreakPos + * @return string + */ + public function run($css = '', $linebreakPos = false) + { + if (empty($css)) { + return ''; + } + + if ($this->raisePhpLimits) { + $this->doRaisePhpLimits(); + } + + $this->comments = array(); + $this->atRuleBlocks = array(); + $this->preservedTokens = array(); + + // process data urls + $css = $this->processDataUrls($css); + + // process comments + $css = preg_replace_callback('/(?chunkLength} chars aprox. + // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit" + // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really + // long strings and a (sub)pattern matches a number of chars greater than + // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently + // returning NULL and $css would be empty. + $charset = ''; + $charsetRegexp = '/(@charset)( [^;]+;)/i'; + $cssChunks = array(); + $l = strlen($css); + + // if the number of characters is <= {$this->chunkLength}, do not chunk + if ($l <= $this->chunkLength) { + $cssChunks[] = $css; + } else { + // chunk css code securely + for ($startIndex = 0, $i = $this->chunkLength; $i < $l; $i++) { + if ($css[$i - 1] === '}' && $i - $startIndex >= $this->chunkLength) { + $cssChunks[] = $this->strSlice($css, $startIndex, $i); + $startIndex = $i; + // Move forward saving iterations when possible! + if ($startIndex + $this->chunkLength < $l) { + $i += $this->chunkLength; + } + } + } + + // Final chunk + $cssChunks[] = $this->strSlice($css, $startIndex); + } + + // Minify each chunk + for ($i = 0, $n = count($cssChunks); $i < $n; $i++) { + $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos); + // Keep the first @charset at-rule found + if (empty($charset) && preg_match($charsetRegexp, $cssChunks[$i], $matches)) { + $charset = strtolower($matches[1]) . $matches[2]; + } + // Delete all @charset at-rules + $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]); + } + + // Update the first chunk and push the charset to the top of the file. + $cssChunks[0] = $charset . $cssChunks[0]; + + return trim(implode('', $cssChunks)); + } + + /** + * Sets the approximate number of characters to use when splitting a string in chunks. + * @param int $length + */ + public function set_chunk_length($length) + { + $length = (int) $length; + $this->chunkLength = $length < $this->minChunkLength ? $this->minChunkLength : $length; + } + + /** + * Sets the memory limit for this script + * @param int|string $limit + */ + public function set_memory_limit($limit) + { + $this->memoryLimit = $this->normalizeInt($limit); + } + + /** + * Sets the maximum execution time for this script + * @param int|string $seconds + */ + public function set_max_execution_time($seconds) + { + $this->maxExecutionTime = (int) $seconds; + } + + /** + * Sets the PCRE backtrack limit for this script + * @param int $limit + */ + public function set_pcre_backtrack_limit($limit) + { + $this->pcreBacktrackLimit = (int) $limit; + } + + /** + * Sets the PCRE recursion limit for this script + * @param int $limit + */ + public function set_pcre_recursion_limit($limit) + { + $this->pcreRecursionLimit = (int) $limit; + } + + /** + * Tries to configure PHP to use at least the suggested minimum settings + * @return void + */ + private function doRaisePhpLimits() + { + $phpLimits = array( + 'memory_limit' => $this->memoryLimit, + 'max_execution_time' => $this->maxExecutionTime, + 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, + 'pcre.recursion_limit' => $this->pcreRecursionLimit + ); + + // If current settings are higher respect them. + foreach ($phpLimits as $name => $suggested) { + $current = $this->normalizeInt(ini_get($name)); + + if ($current > $suggested) { + continue; + } + + // memoryLimit exception: allow -1 for "no memory limit". + if ($name === 'memory_limit' && $current === -1) { + continue; + } + + // maxExecutionTime exception: allow 0 for "no memory limit". + if ($name === 'max_execution_time' && $current === 0) { + continue; + } + + ini_set($name, $suggested); + } + } + + /** + * Registers a preserved token + * @param $token + * @return string The token ID string + */ + private function registerPreservedToken($token) + { + $this->preservedTokens[] = $token; + return self::TOKEN . (count($this->preservedTokens) - 1) .'___'; + } + + /** + * Gets the regular expression to match the specified token ID string + * @param $id + * @return string + */ + private function getPreservedTokenPlaceholderRegexById($id) + { + return '/'. self::TOKEN . $id .'___/'; + } + + /** + * Registers a candidate comment token + * @param $comment + * @return string The comment token ID string + */ + private function registerComment($comment) + { + $this->comments[] = $comment; + return '/*'. self::COMMENT . (count($this->comments) - 1) .'___*/'; + } + + /** + * Gets the candidate comment token ID string for the specified comment token ID + * @param $id + * @return string + */ + private function getCommentPlaceholderById($id) + { + return self::COMMENT . $id .'___'; + } + + /** + * Gets the regular expression to match the specified comment token ID string + * @param $id + * @return string + */ + private function getCommentPlaceholderRegexById($id) + { + return '/'. $this->getCommentPlaceholderById($id) .'/'; + } + + /** + * Registers an at rule block token + * @param $block + * @return string The comment token ID string + */ + private function registerAtRuleBlock($block) + { + $this->atRuleBlocks[] = $block; + return self::AT_RULE_BLOCK . (count($this->atRuleBlocks) - 1) .'___'; + } + + /** + * Gets the regular expression to match the specified at rule block token ID string + * @param $id + * @return string + */ + private function getAtRuleBlockPlaceholderRegexById($id) + { + return '/'. self::AT_RULE_BLOCK . $id .'___/'; + } + + /** + * Minifies the given input CSS string + * @param string $css + * @param int|bool $linebreakPos + * @return string + */ + private function minify($css, $linebreakPos) + { + // Restore preserved at rule blocks + for ($i = 0, $max = count($this->atRuleBlocks); $i < $max; $i++) { + $css = preg_replace( + $this->getAtRuleBlockPlaceholderRegexById($i), + $this->escapeReplacementString($this->atRuleBlocks[$i]), + $css, + 1 + ); + } + + // strings are safe, now wrestle the comments + for ($i = 0, $max = count($this->comments); $i < $max; $i++) { + $comment = $this->comments[$i]; + $commentPlaceholder = $this->getCommentPlaceholderById($i); + $commentPlaceholderRegex = $this->getCommentPlaceholderRegexById($i); + + // ! in the first position of the comment means preserve + // so push to the preserved tokens keeping the ! + if (preg_match('/^!/', $comment)) { + $preservedTokenPlaceholder = $this->registerPreservedToken($comment); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + // Preserve new lines for /*! important comments + $css = preg_replace('/\R+\s*(\/\*'. $preservedTokenPlaceholder .')/', self::NL.'$1', $css); + $css = preg_replace('/('. $preservedTokenPlaceholder .'\*\/)\s*\R+/', '$1'.self::NL, $css); + continue; + } + + // \ in the last position looks like hack for Mac/IE5 + // shorten that to /*\*/ and the next one to /**/ + if (preg_match('/\\\\$/', $comment)) { + $preservedTokenPlaceholder = $this->registerPreservedToken('\\'); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + $i = $i + 1; // attn: advancing the loop + $preservedTokenPlaceholder = $this->registerPreservedToken(''); + $css = preg_replace($this->getCommentPlaceholderRegexById($i), $preservedTokenPlaceholder, $css, 1); + continue; + } + + // keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (strlen($comment) === 0) { + $startIndex = $this->indexOf($css, $commentPlaceholder); + if ($startIndex > 2) { + if (substr($css, $startIndex - 3, 1) === '>') { + $preservedTokenPlaceholder = $this->registerPreservedToken(''); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + continue; + } + } + } + + // in all other cases kill the comment + $css = preg_replace('/\/\*' . $commentPlaceholder . '\*\//', '', $css, 1); + } + + // Normalize all whitespace strings to single spaces. Easier to work with that way. + $css = preg_replace('/\s+/', ' ', $css); + + // Remove spaces before & after newlines + $css = preg_replace('/\s*'. self::NL .'\s*/', self::NL, $css); + + // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters + $css = preg_replace_callback( + '/\s*filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/', + array($this, 'processOldIeSpecificMatrixDefinition'), + $css + ); + + // Shorten & preserve calculations calc(...) since spaces are important + $css = preg_replace_callback('/calc(\(((?:[^()]+|(?1))*)\))/i', array($this, 'processCalc'), $css); + + // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed + // +1.2em to 1.2em, +.8px to .8px, +2% to 2% + $css = preg_replace('/((? -9.0 to -9 + $css = preg_replace('/((?+()\]~=,])/', '$1', $css); + + // Restore spaces for !important + $css = preg_replace('/!important/i', ' !important', $css); + + // bring back the colon + $css = preg_replace('/'. self::CLASSCOLON .'/', ':', $css); + + // retain space for special IE6 cases + $css = preg_replace_callback('/:first-(line|letter)(\{|,)/i', array($this, 'lowercasePseudoFirst'), $css); + + // no space after the end of a preserved comment + $css = preg_replace('/\*\/ /', '*/', $css); + + // lowercase some popular @directives + $css = preg_replace_callback( + '/@(document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|namespace|page|' . + 'supports|viewport)/i', + array($this, 'lowercaseDirectives'), + $css + ); + + // lowercase some more common pseudo-elements + $css = preg_replace_callback( + '/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|' . + 'last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', + array($this, 'lowercasePseudoElements'), + $css + ); + + // lowercase some more common functions + $css = preg_replace_callback( + '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', + array($this, 'lowercaseCommonFunctions'), + $css + ); + + // lower case some common function that can be values + // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us + $css = preg_replace_callback( + '/([:,( ]\s*)(attr|color-stop|from|rgba|to|url|-webkit-gradient|' . + '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient))/iS', + array($this, 'lowercaseCommonFunctionsValues'), + $css + ); + + // Put the space back in some cases, to support stuff like + // @media screen and (-webkit-min-device-pixel-ratio:0){ + $css = preg_replace_callback('/(\s|\)\s)(and|not|or)\(/i', array($this, 'processAtRulesOperators'), $css); + + // Remove the spaces after the things that should not have spaces after them. + $css = preg_replace('/([!{}:;>+(\[~=,])\s+/S', '$1', $css); + + // remove unnecessary semicolons + $css = preg_replace('/;+\}/', '}', $css); + + // Fix for issue: #2528146 + // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack) + // to avoid issues on Symbian S60 3.x browsers. + $css = preg_replace('/(\*[a-z0-9\-]+\s*:[^;}]+)(\})/', '$1;$2', $css); + + // Shorten zero values for safe properties only + $css = $this->shortenZeroValues($css); + + // Shorten font-weight values + $css = preg_replace('/(font-weight:)bold\b/i', '${1}700', $css); + $css = preg_replace('/(font-weight:)normal\b/i', '${1}400', $css); + + // Shorten suitable shorthand properties with repeated non-zero values + $css = preg_replace( + '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') (?:\2) (?:\3)(;|\}| !)/i', + '$1:$2 $3$4', + $css + ); + $css = preg_replace( + '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') (?:\3)(;|\}| !)/i', + '$1:$2 $3 $4$5', + $css + ); + + // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) + // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) + // This makes it more likely that it'll get further compressed in the next step. + $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'rgbToHex'), $css); + $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'hslToHex'), $css); + + // Shorten colors from #AABBCC to #ABC or shorter color name. + $css = $this->shortenHexColors($css); + + // Shorten long named colors: white -> #fff. + $css = $this->shortenNamedColors($css); + + // shorter opacity IE filter + $css = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $css); + + // Find a fraction that is used for Opera's -o-device-pixel-ratio query + // Add token to add the "\" back in later + $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); + + // Patch new lines to avoid being removed when followed by empty rules cases + $css = preg_replace('/'. self::NL .'/', self::NL .'}', $css); + + // Remove empty rules. + $css = preg_replace('/[^{};\/]+\{\}/S', '', $css); + + // Restore new lines for /*! important comments + $css = preg_replace('/'. self::NL .'}/', "\n", $css); + + // Add "/" back to fix Opera -o-device-pixel-ratio query + $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css); + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + $css = preg_replace('/;;+/', ';', $css); + + // Lowercase all uppercase properties + $css = preg_replace_callback('/(\{|;)([A-Z\-]+)(:)/', array($this, 'lowercaseProperties'), $css); + + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + if ($linebreakPos !== false && (int) $linebreakPos >= 0) { + $linebreakPos = (int) $linebreakPos; + for ($startIndex = $i = 1, $l = strlen($css); $i < $l; $i++) { + if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) { + $css = $this->strSlice($css, 0, $i) . "\n" . $this->strSlice($css, $i); + $l = strlen($css); + $startIndex = $i; + } + } + } + + // restore preserved comments and strings in reverse order + for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) { + $css = preg_replace( + $this->getPreservedTokenPlaceholderRegexById($i), + $this->escapeReplacementString($this->preservedTokens[$i]), + $css, + 1 + ); + } + + // Trim the final string for any leading or trailing white space but respect newlines! + $css = preg_replace('/(^ | $)/', '', $css); + + return $css; + } + + /** + * Searches & replaces all data urls with tokens before we start compressing, + * to avoid performance issues running some of the subsequent regexes against large string chunks. + * @param string $css + * @return string + */ + private function processDataUrls($css) + { + // Leave data urls alone to increase parse performance. + $maxIndex = strlen($css) - 1; + $appenIndex = $index = $lastIndex = $offset = 0; + $sb = array(); + $pattern = '/url\(\s*(["\']?)data:/i'; + + // Since we need to account for non-base64 data urls, we need to handle + // ' and ) being part of the data string. Hence switching to indexOf, + // to determine whether or not we have matching string terminators and + // handling sb appends directly, instead of using matcher.append* methods. + while (preg_match($pattern, $css, $m, 0, $offset)) { + $index = $this->indexOf($css, $m[0], $offset); + $lastIndex = $index + strlen($m[0]); + $startIndex = $index + 4; // "url(".length() + $endIndex = $lastIndex - 1; + $terminator = $m[1]; // ', " or empty (not quoted) + $terminatorFound = false; + + if (strlen($terminator) === 0) { + $terminator = ')'; + } + + while ($terminatorFound === false && $endIndex+1 <= $maxIndex) { + $endIndex = $this->indexOf($css, $terminator, $endIndex + 1); + // endIndex == 0 doesn't really apply here + if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') { + $terminatorFound = true; + if (')' !== $terminator) { + $endIndex = $this->indexOf($css, ')', $endIndex); + } + } + } + + // Enough searching, start moving stuff over to the buffer + $sb[] = $this->strSlice($css, $appenIndex, $index); + + if ($terminatorFound) { + $token = $this->strSlice($css, $startIndex, $endIndex); + // Remove all spaces only for base64 encoded URLs. + $token = preg_replace_callback( + '/.+base64,.+/s', + array($this, 'removeSpacesFromDataUrls'), + trim($token) + ); + $preservedTokenPlaceholder = $this->registerPreservedToken($token); + $sb[] = 'url('. $preservedTokenPlaceholder .')'; + $appenIndex = $endIndex + 1; + } else { + // No end terminator found, re-add the whole match. Should we throw/warn here? + $sb[] = $this->strSlice($css, $index, $lastIndex); + $appenIndex = $lastIndex; + } + + $offset = $lastIndex; + } + + $sb[] = $this->strSlice($css, $appenIndex); + + return implode('', $sb); + } + + /** + * Shortens all zero values for a set of safe properties + * e.g. padding: 0px 1px; -> padding:0 1px + * e.g. padding: 0px 0rem 0em 0.0pc; -> padding:0 + * @param string $css + * @return string + */ + private function shortenZeroValues($css) + { + $unitsGroupReg = $this->unitsGroupRegex; + $numOrPosReg = '('. $this->numRegex .'|top|left|bottom|right|center)'; + $oneZeroSafeProperties = array( + '(?:line-)?height', + '(?:(?:min|max)-)?width', + 'top', + 'left', + 'background-position', + 'bottom', + 'right', + 'border(?:-(?:top|left|bottom|right))?(?:-width)?', + 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', + 'column-(?:gap|width)', + 'margin(?:-(?:top|left|bottom|right))?', + 'outline-width', + 'padding(?:-(?:top|left|bottom|right))?' + ); + $nZeroSafeProperties = array( + 'margin', + 'padding', + 'background-position' + ); + + $regStart = '/(;|\{)'; + $regEnd = '/i'; + + // First zero regex start + $oneZeroRegStart = $regStart .'('. implode('|', $oneZeroSafeProperties) .'):'; + + // Multiple zeros regex start + $nZerosRegStart = $regStart .'('. implode('|', $nZeroSafeProperties) .'):'; + + $css = preg_replace( + array( + $oneZeroRegStart .'0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd + ), + array( + '$1$2:0', + '$1$2:$3 0', + '$1$2:$3 $4 0', + '$1$2:$3 $4 $5 0' + ), + $css + ); + + // Remove background-position + array_pop($nZeroSafeProperties); + + // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0 for safe properties only. + $css = preg_replace( + '/('. implode('|', $nZeroSafeProperties) .'):0(?: 0){1,3}(;|\}| !)'. $regEnd, + '$1:0$2', + $css + ); + + // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. + $css = preg_replace('/(background-position):0(?: 0){2,3}(;|\}| !)'. $regEnd, '$1:0 0$2', $css); + + return $css; + } + + /** + * Shortens all named colors with a shorter HEX counterpart for a set of safe properties + * e.g. white -> #fff + * @param string $css + * @return string + */ + private function shortenNamedColors($css) + { + $patterns = array(); + $replacements = array(); + $longNamedColors = array( + 'aliceblue' => '#f0f8ff', + 'antiquewhite' => '#faebd7', + 'aquamarine' => '#7fffd4', + 'black' => '#000', + 'blanchedalmond' => '#ffebcd', + 'blueviolet' => '#8a2be2', + 'burlywood' => '#deb887', + 'cadetblue' => '#5f9ea0', + 'chartreuse' => '#7fff00', + 'chocolate' => '#d2691e', + 'cornflowerblue' => '#6495ed', + 'cornsilk' => '#fff8dc', + 'darkblue' => '#00008b', + 'darkcyan' => '#008b8b', + 'darkgoldenrod' => '#b8860b', + 'darkgray' => '#a9a9a9', + 'darkgreen' => '#006400', + 'darkgrey' => '#a9a9a9', + 'darkkhaki' => '#bdb76b', + 'darkmagenta' => '#8b008b', + 'darkolivegreen' => '#556b2f', + 'darkorange' => '#ff8c00', + 'darkorchid' => '#9932cc', + 'darksalmon' => '#e9967a', + 'darkseagreen' => '#8fbc8f', + 'darkslateblue' => '#483d8b', + 'darkslategray' => '#2f4f4f', + 'darkslategrey' => '#2f4f4f', + 'darkturquoise' => '#00ced1', + 'darkviolet' => '#9400d3', + 'deeppink' => '#ff1493', + 'deepskyblue' => '#00bfff', + 'dodgerblue' => '#1e90ff', + 'firebrick' => '#b22222', + 'floralwhite' => '#fffaf0', + 'forestgreen' => '#228b22', + 'fuchsia' => '#f0f', + 'gainsboro' => '#dcdcdc', + 'ghostwhite' => '#f8f8ff', + 'goldenrod' => '#daa520', + 'greenyellow' => '#adff2f', + 'honeydew' => '#f0fff0', + 'indianred' => '#cd5c5c', + 'lavender' => '#e6e6fa', + 'lavenderblush' => '#fff0f5', + 'lawngreen' => '#7cfc00', + 'lemonchiffon' => '#fffacd', + 'lightblue' => '#add8e6', + 'lightcoral' => '#f08080', + 'lightcyan' => '#e0ffff', + 'lightgoldenrodyellow' => '#fafad2', + 'lightgray' => '#d3d3d3', + 'lightgreen' => '#90ee90', + 'lightgrey' => '#d3d3d3', + 'lightpink' => '#ffb6c1', + 'lightsalmon' => '#ffa07a', + 'lightseagreen' => '#20b2aa', + 'lightskyblue' => '#87cefa', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#b0c4de', + 'lightyellow' => '#ffffe0', + 'limegreen' => '#32cd32', + 'mediumaquamarine' => '#66cdaa', + 'mediumblue' => '#0000cd', + 'mediumorchid' => '#ba55d3', + 'mediumpurple' => '#9370db', + 'mediumseagreen' => '#3cb371', + 'mediumslateblue' => '#7b68ee', + 'mediumspringgreen' => '#00fa9a', + 'mediumturquoise' => '#48d1cc', + 'mediumvioletred' => '#c71585', + 'midnightblue' => '#191970', + 'mintcream' => '#f5fffa', + 'mistyrose' => '#ffe4e1', + 'moccasin' => '#ffe4b5', + 'navajowhite' => '#ffdead', + 'olivedrab' => '#6b8e23', + 'orangered' => '#ff4500', + 'palegoldenrod' => '#eee8aa', + 'palegreen' => '#98fb98', + 'paleturquoise' => '#afeeee', + 'palevioletred' => '#db7093', + 'papayawhip' => '#ffefd5', + 'peachpuff' => '#ffdab9', + 'powderblue' => '#b0e0e6', + 'rebeccapurple' => '#663399', + 'rosybrown' => '#bc8f8f', + 'royalblue' => '#4169e1', + 'saddlebrown' => '#8b4513', + 'sandybrown' => '#f4a460', + 'seagreen' => '#2e8b57', + 'seashell' => '#fff5ee', + 'slateblue' => '#6a5acd', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'springgreen' => '#00ff7f', + 'steelblue' => '#4682b4', + 'turquoise' => '#40e0d0', + 'white' => '#fff', + 'whitesmoke' => '#f5f5f5', + 'yellow' => '#ff0', + 'yellowgreen' => '#9acd32' + ); + $propertiesWithColors = array( + 'color', + 'background(?:-color)?', + 'border(?:-(?:top|right|bottom|left|color)(?:-color)?)?', + 'outline(?:-color)?', + '(?:text|box)-shadow' + ); + + $regStart = '/(;|\{)('. implode('|', $propertiesWithColors) .'):([^;}]*)\b'; + $regEnd = '\b/iS'; + + foreach ($longNamedColors as $colorName => $colorCode) { + $patterns[] = $regStart . $colorName . $regEnd; + $replacements[] = '$1$2:$3'. $colorCode; + } + + // Run at least 4 times to cover most cases (same color used several times for the same property) + for ($i = 0; $i < 4; $i++) { + $css = preg_replace($patterns, $replacements, $css); + } + + return $css; + } + + /** + * Compresses HEX color values of the form #AABBCC to #ABC or short color name. + * + * DOES NOT compress CSS ID selectors which match the above pattern (which would break things). + * e.g. #AddressForm { ... } + * + * DOES NOT compress IE filters, which have hex color values (which would break things). + * e.g. filter: chroma(color="#FFFFFF"); + * + * DOES NOT compress invalid hex values. + * e.g. background-color: #aabbccdd + * + * @param string $css + * @return string + */ + private function shortenHexColors($css) + { + // Look for hex colors inside { ... } (to avoid IDs) and + // which don't have a =, or a " in front of them (to avoid filters) + $pattern = + '/(=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; + $_index = $index = $lastIndex = $offset = 0; + $longHexColors = array( + '#f0ffff' => 'azure', + '#f5f5dc' => 'beige', + '#ffe4c4' => 'bisque', + '#a52a2a' => 'brown', + '#ff7f50' => 'coral', + '#ffd700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4b0082' => 'indigo', + '#fffff0' => 'ivory', + '#f0e68c' => 'khaki', + '#faf0e6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#fdf5e6' => 'oldlace', + '#808000' => 'olive', + '#ffa500' => 'orange', + '#da70d6' => 'orchid', + '#cd853f' => 'peru', + '#ffc0cb' => 'pink', + '#dda0dd' => 'plum', + '#800080' => 'purple', + '#f00' => 'red', + '#fa8072' => 'salmon', + '#a0522d' => 'sienna', + '#c0c0c0' => 'silver', + '#fffafa' => 'snow', + '#d2b48c' => 'tan', + '#008080' => 'teal', + '#ff6347' => 'tomato', + '#ee82ee' => 'violet', + '#f5deb3' => 'wheat' + ); + $sb = array(); + + while (preg_match($pattern, $css, $m, 0, $offset)) { + $index = $this->indexOf($css, $m[0], $offset); + $lastIndex = $index + strlen($m[0]); + $isFilter = $m[1] !== null && $m[1] !== ''; + + $sb[] = $this->strSlice($css, $_index, $index); + + if ($isFilter) { + // Restore, maintain case, otherwise filter will break + $sb[] = $m[1] .'#'. $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; + } else { + if (strtolower($m[2]) == strtolower($m[3]) && + strtolower($m[4]) == strtolower($m[5]) && + strtolower($m[6]) == strtolower($m[7])) { + // Compress. + $hex = '#'. strtolower($m[3] . $m[5] . $m[7]); + } else { + // Non compressible color, restore but lower case. + $hex = '#'. strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); + } + // replace Hex colors with shorter color names + $sb[] = array_key_exists($hex, $longHexColors) ? $longHexColors[$hex] : $hex; + } + + $_index = $offset = $lastIndex - strlen($m[8]); + } + + $sb[] = $this->strSlice($css, $_index); + + return implode('', $sb); + } + + // --------------------------------------------------------------------------------------------- + // CALLBACKS + // --------------------------------------------------------------------------------------------- + + private function processComments($matches) + { + $match = !empty($matches[1]) ? $matches[1] : ''; + return $this->registerComment($match); + } + + private function processStrings($matches) + { + $match = $matches[0]; + $quote = substr($match, 0, 1); + $match = $this->strSlice($match, 1, -1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (($pos = strpos($match, self::COMMENT)) !== false) { + for ($i = 0, $max = count($this->comments); $i < $max; $i++) { + $match = preg_replace( + $this->getCommentPlaceholderRegexById($i), + $this->escapeReplacementString($this->comments[$i]), + $match, + 1 + ); + } + } + + // minify alpha opacity in filter strings + $match = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $match); + + $preservedTokenPlaceholder = $this->registerPreservedToken($match); + return $quote . $preservedTokenPlaceholder . $quote; + } + + private function processAtRuleBlocks($matches) + { + return $this->registerAtRuleBlock($matches[0]); + } + + private function processCalc($matches) + { + $token = preg_replace( + '/\)([+\-]{1})/', + ') $1', + preg_replace( + '/([+\-]{1})\(/', + '$1 (', + trim(preg_replace('/\s*([*\/(),])\s*/', '$1', $matches[2])) + ) + ); + $preservedTokenPlaceholder = $this->registerPreservedToken($token); + return 'calc('. $preservedTokenPlaceholder .')'; + } + + private function processOldIeSpecificMatrixDefinition($matches) + { + $preservedTokenPlaceholder = $this->registerPreservedToken($matches[1]); + return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $preservedTokenPlaceholder .')'; + } + + private function processColon($matches) + { + return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); + } + + private function removeSpacesFromDataUrls($matches) + { + return preg_replace('/\s+/', '', $matches[0]); + } + + private function rgbToHex($matches) + { + $hexColors = array(); + $rgbColors = explode(',', $matches[1]); + + // Values outside the sRGB color space should be clipped (0-255) + for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { + $hexColors[$i] = sprintf("%02x", $this->clampNumberSrgb($this->rgbPercentageToRgbInteger($rgbColors[$i]))); + } + + // Fix for issue #2528093 + if (!preg_match('/[\s,);}]/', $matches[2])) { + $matches[2] = ' '. $matches[2]; + } + + return '#'. implode('', $hexColors) . $matches[2]; + } + + private function hslToHex($matches) + { + $hslValues = explode(',', $matches[1]); + + $rgbColors = $this->hslToRgb($hslValues); + + return $this->rgbToHex(array('', implode(',', $rgbColors), $matches[2])); + } + + private function processAtRulesOperators($matches) + { + return $matches[1] . strtolower($matches[2]) .' ('; + } + + private function lowercasePseudoFirst($matches) + { + return ':first-'. strtolower($matches[1]) .' '. $matches[2]; + } + + private function lowercaseDirectives($matches) + { + return '@'. strtolower($matches[1]); + } + + private function lowercasePseudoElements($matches) + { + return ':'. strtolower($matches[1]); + } + + private function lowercaseCommonFunctions($matches) + { + return ':'. strtolower($matches[1]) .'('; + } + + private function lowercaseCommonFunctionsValues($matches) + { + return $matches[1] . strtolower($matches[2]); + } + + private function lowercaseProperties($matches) + { + return $matches[1] . strtolower($matches[2]) . $matches[3]; + } + + // --------------------------------------------------------------------------------------------- + // HELPERS + // --------------------------------------------------------------------------------------------- + + /** + * Clamps a number between a minimum and a maximum value. + * @param int|float $n the number to clamp + * @param int|float $min the lower end number allowed + * @param int|float $max the higher end number allowed + * @return int|float + */ + private function clampNumber($n, $min, $max) + { + return min(max($n, $min), $max); + } + + /** + * Clamps a RGB color number outside the sRGB color space + * @param int|float $n the number to clamp + * @return int|float + */ + private function clampNumberSrgb($n) + { + return $this->clampNumber($n, 0, 255); + } + + /** + * Escapes backreferences such as \1 and $1 in a regular expression replacement string + * @param $string + * @return string + */ + private function escapeReplacementString($string) + { + return addcslashes($string, '\\$'); + } + + /** + * Converts a HSL color into a RGB color + * @param array $hslValues + * @return array + */ + private function hslToRgb($hslValues) + { + $h = floatval($hslValues[0]); + $s = floatval(str_replace('%', '', $hslValues[1])); + $l = floatval(str_replace('%', '', $hslValues[2])); + + // Wrap and clamp, then fraction! + $h = ((($h % 360) + 360) % 360) / 360; + $s = $this->clampNumber($s, 0, 100) / 100; + $l = $this->clampNumber($l, 0, 100) / 100; + + if ($s == 0) { + $r = $g = $b = $this->roundNumber(255 * $l); + } else { + $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); + $v1 = (2 * $l) - $v2; + $r = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h + (1/3))); + $g = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h)); + $b = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h - (1/3))); + } + + return array($r, $g, $b); + } + + /** + * Tests and selects the correct formula for each RGB color channel + * @param $v1 + * @param $v2 + * @param $vh + * @return mixed + */ + private function hueToRgb($v1, $v2, $vh) + { + $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); + + if ($vh * 6 < 1) { + return $v1 + ($v2 - $v1) * 6 * $vh; + } + + if ($vh * 2 < 1) { + return $v2; + } + + if ($vh * 3 < 2) { + return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; + } + + return $v1; + } + + /** + * PHP port of Javascript's "indexOf" function for strings only + * Author: Tubal Martin + * + * @param string $haystack + * @param string $needle + * @param int $offset index (optional) + * @return int + */ + private function indexOf($haystack, $needle, $offset = 0) + { + $index = strpos($haystack, $needle, $offset); + + return ($index !== false) ? $index : -1; + } + + /** + * Convert strings like "64M" or "30" to int values + * @param mixed $size + * @return int + */ + private function normalizeInt($size) + { + if (is_string($size)) { + $letter = substr($size, -1); + $size = intval($size); + switch ($letter) { + case 'M': + case 'm': + return (int) $size * 1048576; + case 'K': + case 'k': + return (int) $size * 1024; + case 'G': + case 'g': + return (int) $size * 1073741824; + } + } + return (int) $size; + } + + /** + * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 + * @param $rgbPercentage + * @return int + */ + private function rgbPercentageToRgbInteger($rgbPercentage) + { + if (strpos($rgbPercentage, '%') !== false) { + $rgbPercentage = $this->roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); + } + + return intval($rgbPercentage, 10); + } + + /** + * Rounds a number to its closest integer + * @param $n + * @return int + */ + private function roundNumber($n) + { + return intval(round(floatval($n)), 10); + } + + /** + * PHP port of Javascript's "slice" function for strings only + * Author: Tubal Martin + * + * @param string $str + * @param int $start index + * @param int|bool $end index (optional) + * @return string + */ + private function strSlice($str, $start = 0, $end = false) + { + if ($end !== false && ($start < 0 || $end <= 0)) { + $max = strlen($str); + + if ($start < 0) { + if (($start = $max + $start) < 0) { + return ''; + } + } + + if ($end < 0) { + if (($end = $max + $end) < 0) { + return ''; + } + } + + if ($end <= $start) { + return ''; + } + } + + $slice = ($end === false) ? substr($str, $start) : substr($str, $start, $end - $start); + return ($slice === false) ? '' : $slice; + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc new file mode 100644 index 000000000..ec9457a1c --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc @@ -0,0 +1,83 @@ + 'fieldset', + '#title' => t('@title', array('@title' => $params[1])), + ); + $form[$params[0]]['cmd'] = array( + '#type' => 'fieldset', + '#title' => t('Command Line'), + ); + + $description = t('{%CWD%} = DRUPAL_ROOT.
    {%IN%} = input file.
    {%IN_URL_ENC%} = url pointing to the input file that has been url encoded.
    {%OUT%} = output file.

    '); + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $description .= ' ' . t('Example using the Microsoft Ajax Minifier.

    @code1

    ', array( + '@link1' => 'http://ajaxmin.codeplex.com/', + '@code1' => 'AjaxMinifier {%IN%} -o {%OUT%}', + )); + } + + if ($params[0] === 'js') { + $description .= ' ' . t('Example using the Google Closure Compiler.

    @code1

    ', array( + '@link1' => 'https://developers.google.com/closure/compiler/docs/gettingstarted_app', + '@code1' => 'java -jar compiler.jar --js {%CWD%}/{%IN%} --js_output_file {%OUT%}', + )); + + $description .= ' ' . t('Example using curl to compress via the Online Google Closure Compiler.

    @code1

    ', array( + '@link1' => 'https://developers.google.com/closure/compiler/docs/api-ref', + '@code1' => 'curl -o {%OUT%} -d output_info=compiled_code -d code_url={%IN_URL_ENC%} http://closure-compiler.appspot.com/compile', + )); + } + if ($params[0] === 'css') { + $description .= ' ' . t('Example using the YUI Compressor.

    @code1

    ', array( + '@link1' => 'http://yui.github.io/yuicompressor/', + '@code1' => 'java -jar yuicompressor-x.y.z.jar --type css --line-break 4096 {%CWD%}/{%IN%} -o {%OUT%}', + )); + + $description .= ' ' . t('Example using curl to compress via an online CSS Compressor.

    @code1

    ', array( + '@link1' => 'http://cnvyr.io/', + '@code1' => 'curl -o {%OUT%} -F \'files0=@{%IN%}\' http://srv.cnvyr.io/v1?min=css', + )); + } + + $form[$params[0]]['cmd']['advagg_ext_compress_' . $params[0] . '_cmd'] = array( + '#type' => 'textfield', + '#title' => t('Command to run'), + '#default_value' => variable_get('advagg_ext_compress_' . $params[0] . '_cmd', ''), + '#description' => $description, + ); +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.info b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.info new file mode 100644 index 000000000..955a0c570 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.info @@ -0,0 +1,13 @@ +name = AdvAgg External Compression +description = Compress Javascript and/or CSS with a command line compressor. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +configure = admin/config/development/performance/advagg/ext-compress + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.module b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.module new file mode 100644 index 000000000..5bfe00f4b --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_ext_compress/advagg_ext_compress.module @@ -0,0 +1,241 @@ + 'External Compression', + 'description' => 'Adjust External Compression settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_ext_compress_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_ext_compress.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_js_compress_configuration_alter(). + */ +function advagg_ext_compress_advagg_js_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { + list($options, $description) = $options_desc; + + $key = 10; + while (isset($options[$key])) { + $key++; + } + $options[$key] = t('AdvAgg Command Line Compressor'); + $compressors[$key] = 'advagg_cmdline'; + $functions[$key] = 'advagg_ext_compress_js_compress'; + + $options_desc = array($options, $description); +} + +/** + * Implements hook_advagg_css_compress_configuration_alter(). + */ +function advagg_ext_compress_advagg_css_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { + list($options, $description) = $options_desc; + + $key = 10; + while (isset($options[$key])) { + $key++; + } + $options[$key] = t('AdvAgg Command Line Compressor'); + $functions[$key] = 'advagg_ext_compress_css_compress'; + + $options_desc = array($options, $description); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Compress Javascript using via command line. + * + * @param string $input_file + * The file containing the uncompressed css/js data. + * @param string $ext + * The string css or js. + * @param array $debug + * Optional debug array. + * + * @return string + * The filename containing the compressed css/js data. + */ +function advagg_ext_compress_execute_cmd($input_file, $ext = '', array &$debug = array()) { + $run = variable_get("advagg_ext_compress_{$ext}_cmd", ''); + if (empty($run)) { + return FALSE; + } + + // Get file extension. + if (empty($ext)) { + $ext = strtolower(pathinfo($input_file, PATHINFO_EXTENSION)); + if ($ext !== 'css' && $ext !== 'js') { + // Get the $ext from the database. + $row = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $input_file) + ->execute()->fetchAssoc(); + if (!empty($row['filetype'])) { + $ext = $row['filetype']; + } + if ($ext === 'less') { + $ext = 'css'; + } + } + } + + // Generate temp file. + $temp_file = drupal_tempnam('temporary://', 'advagg_file_'); + $new_temp_file = $temp_file . '.' . basename($input_file); + @rename($temp_file, $new_temp_file); + // Set the permissions on the temp file. + drupal_chmod($new_temp_file); + $output = advagg_get_relative_path($new_temp_file); + + // Create command to run. + $cmd = str_replace(array( + '{%CWD%}', + '{%IN%}', + '{%IN_URL_ENC%}', + '{%OUT%}', + ), array( + DRUPAL_ROOT, + $input_file, + urlencode(file_create_url($input_file)), + escapeshellarg(realpath($output)), + ), $run); + + // Run command and return the output file. + $shell_output = array(); + $return_var = 0; + $shell = exec($cmd, $shell_output, $return_var); + $debug = array($cmd, $shell_output, $return_var, $shell); + + // Cleanup leftover files. + if (file_exists($temp_file)) { + @unlink($temp_file); + } + return $output; +} + +/** + * Compress Javascript using via command line. + * + * @param string $contents + * The JavaScript to compress. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * FALSE if this failed. + */ +function advagg_ext_compress_js_compress(&$contents, $log_errors) { + return advagg_ext_compress_string($contents, 'js', $log_errors); +} + +/** + * Compress CSS using via command line. + * + * @param string $contents + * The CSS to compress. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * FALSE if this failed. + */ +function advagg_ext_compress_css_compress(&$contents, $log_errors) { + return advagg_ext_compress_string($contents, 'css', $log_errors); +} + +/** + * Compress CSS using via command line. + * + * @param string $contents + * The data to compress. + * @param string $type + * Should be css or js. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * TRUE on success. + */ +function advagg_ext_compress_string(&$contents, $type, $log_errors) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type === 'css') { + $dir = $css_path[0]; + } + else { + $dir = $js_path[0]; + } + $new_temp_file = $dir . '/advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . '.' . $type; + $temp_file_full = advagg_get_relative_path($new_temp_file); + + file_put_contents($new_temp_file, $contents); + // Set the permissions on the temp file. + drupal_chmod($new_temp_file); + $debug = array(); + $output = advagg_ext_compress_execute_cmd($temp_file_full, $type, $debug); + if (empty($output)) { + return FALSE; + } + $new_contents = advagg_file_get_contents($output); + if (strpos($new_contents, 'Error') === 0) { + if ($log_errors) { + watchdog('advagg_ext_compress', "@a \n
    \n
    @b", array( + // Only log 4k of data. + '@a' => substr($new_contents, 0, 4096), + '@b' => print_r($debug, TRUE), + )); + } + $return = FALSE; + } + else { + $contents = $new_contents; + $return = TRUE; + } + + // Cleanup. + if (file_exists($new_temp_file)) { + unlink($new_temp_file); + } + if (file_exists($temp_file_full)) { + unlink($temp_file_full); + } + if (file_exists($output)) { + unlink($output); + } + return $return; +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.admin.inc b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.admin.inc new file mode 100644 index 000000000..32406e585 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.admin.inc @@ -0,0 +1,209 @@ + t('Disabled'), + 6 => t('Externally load the latest from github (version: @version)', array('@version' => $version)), + ); + $description = t('This will use the fallback font until the font has been downloaded. See fontfaceobserver for more info.', array( + '@link' => $library['vendor url'], + )); + if (function_exists('libraries_info')) { + if ($library['installed']) { + $options += array( + 2 => t('Inline javascript (version: @version)', array('@version' => $library['version'])), + 4 => t('Local file included in aggregate (version: @version)', array('@version' => $library['version'])), + ); + } + elseif (!is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { + $description .= ' ' . t('To use fontfaceobserver locally fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js and package.json can be found at that location.', array( + '@url' => 'https://www.drupal.org/project/libraries', + )); + } + else { + $description .= ' ' . t('Go to the library report page and make sure fontfaceobserver is installed correctly.', array( + '@url' => url('admin/reports/libraries'), + )); + } + } + elseif (is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { + $description .= ' ' . t('To use fontfaceobserver locally the libraries api module needs to be installed.', array( + '@url' => 'https://www.drupal.org/project/libraries', + )); + } + else { + $description .= ' ' . t('To use fontfaceobserver locally the libraries api module needs to be installed and then fontfaceobserver needs to be placed inside the sites/all/libraries directory so sites/all/libraries/fontfaceobserver/fontfaceobserver.js and package.json can be found at that location.', array( + '@url' => 'https://www.drupal.org/project/libraries', + )); + } + + ksort($options); + $form['advagg_font_fontfaceobserver'] = array( + '#type' => 'radios', + '#title' => t('Use font face observer to load fonts asynchronously.'), + '#default_value' => variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER), + '#options' => $options, + '#description' => $description, + ); + + $form['container'] = array( + '#type' => 'container', + '#states' => array( + 'invisible' => array( + ':input[name="advagg_font_fontfaceobserver"]' => array('value' => '0'), + ), + ), + ); + $form['container']['advagg_font_storage'] = array( + '#type' => 'checkbox', + '#title' => t('Use localStorage so the flash of unstyled text (FOUT) only happens once.'), + '#default_value' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), + '#description' => t('Data is stored in localStorage under advagg_fonts. If this is a problem you can disable localStorage from being used; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if cookies is also not being used.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + ); + $form['container']['advagg_font_cookie'] = array( + '#type' => 'checkbox', + '#title' => t('Set a cookie so the flash of unstyled text (FOUT) only happens once.'), + '#default_value' => variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE), + '#description' => t('Cookies are name like @cookie. If this is a problem you can disable cookies from being set; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if localStorage is also not being used.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + ); + $form['container']['advagg_font_no_fout'] = array( + '#type' => 'checkbox', + '#title' => t('Prevent the Flash of Unstyled Text.'), + '#default_value' => variable_get('advagg_font_no_fout', ADVAGG_FONT_NO_FOUT), + '#description' => t('The font will not be changed unless the browser already has the font downloaded. Font gets downloaded on the first page view.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + '#states' => array( + 'disabled' => array( + '#edit-advagg-font-cookie' => array('checked' => FALSE), + '#edit-advagg-font-storage' => array('checked' => FALSE), + ), + ), + ); + + // Get all css files and scan for quoted fonts. + $form['fonts'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Async Loaded fonts in CSS'), + '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks.'), + ); + $form['fonts_not_async'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('NOT Async Loaded in CSS'), + '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks. If there is no fallback it will apear below.'), + ); + // Get filename, filename_hash, and changes. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'filename_hash', 'changes')) + ->condition('filetype', 'css') + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + if (!file_exists($row['filename'])) { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($row['filename']); + // Get font names. + list($replacements, $fonts_with_no_replacements) = advagg_font_get_replacements_array($file_contents); + if (!empty($replacements)) { + $fonts = array(); + foreach ($replacements as $key => $replacement) { + // Do not display !important after the fallback font name. + $replacement[5] = str_replace(' !important', '', $replacement[5]); + $fonts[$key . ' ' . $replacement[3]] = $replacement[5]; + } + + $form['fonts'][$row['filename_hash']] = array( + '#markup' => '
    ' . t('%file - @replacements
    ', array( + '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), + '%file' => $row['filename'], + )) . '
    ', + ); + } + if (!empty($fonts_with_no_replacements)) { + $fonts = array(); + foreach ($fonts_with_no_replacements as $key => $replacement) { + // Do not display !important after the fallback font name. + $replacement = str_replace(' !important', '', $replacement); + $fonts[$key . ' ' . $replacement] = $replacement; + } + + $form['fonts_not_async'][$row['filename_hash']] = array( + '#markup' => '
    ' . t('%file - @replacements
    ', array( + '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), + '%file' => $row['filename'], + )) . '
    ', + ); + } + } + $children = element_children($form['fonts']); + + // If no fonts are found; disable this module. + if (count($children) == 0) { + $form['advagg_font_fontfaceobserver']['#default_value'] = 0; + $form['advagg_font_fontfaceobserver']['#disabled'] = TRUE; + + if (empty($results)) { + $form['fonts'] = array( + '#type' => 'fieldset', + '#title' => t('No CSS files have been aggregated.'), + '#description' => t('You need to enable aggregation. No css files where found in the advagg_files table.'), + ); + } + else { + $form['fonts'] = array( + '#type' => 'fieldset', + '#title' => t('No CSS files with external fonts found.'), + '#description' => t('Currently this module is not doing anything. Recommend uninstalling it as advagg is not processing any css files that use an external font file.'), + ); + } + } + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_font_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback, clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_font_admin_settings_form_submit($form, &$form_state) { + // Clear caches. + advagg_cache_clear_admin_submit(); + + // Disable cookie and local storage if ffo is disabled. + if (empty($form_state['values']['advagg_font_fontfaceobserver'])) { + $form_state['values']['advagg_font_cookie'] = 0; + $form_state['values']['advagg_font_storage'] = 0; + } + // Disable no fout if cookies and local storage are disabled. + if (empty($form_state['values']['advagg_font_cookie']) + && empty($form_state['values']['advagg_font_storage']) + ) { + $form_state['values']['advagg_font_no_fout'] = 0; + } +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.advagg.inc b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.advagg.inc new file mode 100644 index 000000000..ef1ef3a04 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.advagg.inc @@ -0,0 +1,63 @@ += current_time) { + // Only allow alpha numeric class names. + window.document.documentElement.className += ' ' + key.replace(/[^a-zA-Z0-9-]/g, ''); + } + } + } +} + + +// Check cookies ASAP and set class. +advagg_font_inline(); diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.install b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.install new file mode 100644 index 000000000..24081aba9 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.install @@ -0,0 +1,61 @@ + $t('AdvAgg Font'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('AdvAgg async font loading is disabled.'), + 'description' => $t('Go to the AdvAgg Async Font Loader settings page and select an option other than disabled, or go to the modules page and disable the "AdvAgg Async Font Loader" module.', array( + '@settings' => url($config_path . '/advagg/font'), + '@modules' => url('admin/modules', array( + 'fragment' => 'edit-modules-advanced-cssjs-aggregation', + )), + )), + ); + } + + // Check version. + $lib_name = 'fontfaceobserver'; + $module_name = 'advagg_css_compress'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + + return $requirements; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.js b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.js new file mode 100644 index 000000000..8b031464f --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.js @@ -0,0 +1,110 @@ +/** + * @file + * Used to add a class to the top level element when an external font is ready. + */ + +/* global Drupal:false */ + +/** + * Run the check. + * + * @param {string} key + * The class name to add to the html tag. + * @param {string} value + * The font name. + */ +function advagg_run_check(key, value) { + 'use strict'; + // Only run if window.FontFaceObserver is defined. + if (window.FontFaceObserver) { + // Only alpha numeric value. + key = key.replace(/[^a-zA-Z0-9-]/g, ''); + if (typeof window.FontFaceObserver.prototype.load === 'function') { + new window.FontFaceObserver(value).load().then(function () { + advagg_run_check_inner(key, value); + }, function () {}); + } + else { + new window.FontFaceObserver(value).check().then(function () { + advagg_run_check_inner(key, value); + }, function () {}); + } + } + else { + // Try again in 100 ms. + window.setTimeout(function () { + advagg_run_check(key, value); + }, 100); + } +} + +/** + * Run the check. + * + * @param {string} key + * The class name to add to the html tag. + * @param {string} value + * The font name. + */ +function advagg_run_check_inner(key, value) { + 'use strict'; + // Set Class. + if (parseInt(Drupal.settings.advagg_font_no_fout, 10) !== 1) { + window.document.documentElement.className += ' ' + key; + } + + // Set for a day. + var expire_date = new Date().getTime() + 86400 * 1000; + + if (Storage !== void 0 && parseInt(Drupal.settings.advagg_font_storage, 10) === 1) { + // Use local storage. + var fonts = JSON.parse(localStorage.getItem('advagg_fonts')); + if (!fonts) { + fonts = {}; + } + fonts[key] = expire_date; + localStorage.setItem('advagg_fonts', JSON.stringify(fonts)); + } + else if (parseInt(Drupal.settings.advagg_font_cookie, 10) === 1) { + // Use cookies if enabled and local storage not available. + expire_date = new Date(expire_date).toUTCString(); + document.cookie = 'advaggfont_' + key + '=' + value + + '; expires=' + expire_date + + '; domain=.' + document.location.hostname + + '; path=/'; + } +} + +/** + * Get the list of fonts to check for. + */ +function advagg_font_add_font_classes_on_load() { + 'use strict'; + for (var key in Drupal.settings.advagg_font) { + if (Drupal.settings.advagg_font.hasOwnProperty(key)) { + var html_class = (' ' + window.document.documentElement.className + ' ').indexOf(' ' + key + ' '); + // If the class already exists in the html element do nothing. + if (html_class === -1) { + // Wait till the font is downloaded, then set cookie & class. + advagg_run_check(key, Drupal.settings.advagg_font[key]); + } + } + } +} + +/** + * Make sure window.Drupal.settings.advagg_font is defined before running. + */ +function advagg_font_check() { + 'use strict'; + if (window.Drupal && window.Drupal.settings && window.Drupal.settings.advagg_font) { + advagg_font_add_font_classes_on_load(); + } + else { + // Try again in 20 ms. + window.setTimeout(advagg_font_check, 20); + } +} + +// Start the process. +advagg_font_check(); diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.module b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.module new file mode 100644 index 000000000..9f3a9c868 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_font/advagg_font.module @@ -0,0 +1,542 @@ + array(), + 'advagg_font_storage' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), + 'advagg_font_cookie' => variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE), + 'advagg_font_no_fout' => variable_get('advagg_font_no_fout', ADVAGG_FONT_NO_FOUT), + ), array('type' => 'setting')); + + // Add inline script for reading the cookies and adding the fonts already + // loaded to the html class. + if (variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE) || variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE)) { + $inline_script_min = 'for(var fonts=document.cookie.split("advaggf"),i=0;i=current_time&&(window.document.documentElement.className+=" "+key.replace(/[^a-zA-Z0-9\-]/g,""))}'; + drupal_add_js($inline_script_min, array( + 'type' => 'inline', + 'group' => JS_LIBRARY - 1, + 'weight' => -50000, + 'scope' => 'above_css', + 'scope_lock' => TRUE, + 'movable' => FALSE, + 'no_defer' => TRUE, + )); + } + + // Get library data for fontfaceobserver. + $library = advagg_get_library('fontfaceobserver', 'advagg_font'); + // If libraries_load() does not exist load library externally. + if (!is_callable('libraries_load')) { + $advagg_font_ffo = 6; + } + // Add fontfaceobserver.js. + if ($advagg_font_ffo != 6 && empty($library['installed'])) { + // The fontfaceobserver library is not installed; use external variant. + $advagg_font_ffo = 6; + } + if ($advagg_font_ffo == 6) { + // Use the external variant. + foreach ($library['variants']['external']['files']['js'] as $data => $options) { + drupal_add_js($data, $options); + } + } + else { + // Load the fontfaceobserver library. + if ($advagg_font_ffo == 2) { + // Use the inline variant. + libraries_load('fontfaceobserver', 'inline'); + } + else { + libraries_load('fontfaceobserver'); + } + } + + // Add advagg_font.js; sets cookie and changes the class of the top level + // element once a font has been downloaded. + $file_path = drupal_get_path('module', 'advagg_font') . '/advagg_font.js'; + drupal_add_js($file_path, array( + 'async' => TRUE, + 'defer' => TRUE, + )); +} + +/** + * Implements hook_css_alter(). + */ +function advagg_css_alter(&$css) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + + // Skip if fontface is disabled. + if (empty(variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER))) { + return; + } + + // Skip if fonts added to critical css is disabled. + if (empty(variable_get('advagg_font_add_to_critical_css', ADVAGG_FONT_ADD_TO_CRITICAL_CSS))) { + return; + } + + $critical_css_key = NULL; + foreach ($css as $key => $values) { + if (!empty($values['critical-css']) && $values['type'] === 'inline') { + $critical_css_key = $key; + } + } + + // Skip if no critical css. + if (is_null($critical_css_key)) { + return; + } + + module_load_include('inc', 'advagg', 'advagg'); + $css_to_add = ''; + foreach ($css as $key => $values) { + if ($values['type'] === 'file') { + $info = advagg_get_info_on_file($key); + if (!empty($info['advagg_font'])) { + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + if (empty($file_contents)) { + continue; + } + list($replacements) = advagg_font_get_replacements_array($file_contents); + foreach ($replacements as $replace) { + $css_to_add .= $replace[2]; + } + } + } + } + if (!empty($css_to_add)) { + $css[$critical_css_key]['data'] .= "\n{$css_to_add}"; + } +} + +/** + * Implements hook_menu(). + */ +function advagg_font_menu() { + $file_path = drupal_get_path('module', 'advagg_font'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/font'] = array( + 'title' => 'Async Font Loader', + 'description' => 'Load external fonts in a non blocking manner.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_font_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_font.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_font_libraries_info() { + $libraries['fontfaceobserver'] = array( + // Only used in administrative UI of Libraries API. + 'name' => 'fontfaceobserver', + 'vendor url' => 'https://github.com/bramstein/fontfaceobserver', + 'download url' => 'https://github.com/bramstein/fontfaceobserver/archive/master.zip', + 'version arguments' => array( + 'file' => 'package.json', + // 1.50. : "version": "1.5.0". + 'pattern' => '/"version":\\s+"([0-9\.]+)"/', + 'lines' => 10, + ), + 'remote' => array( + 'callback' => 'advagg_get_github_version_json', + 'url' => 'https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@master/package.json', + ), + 'files' => array( + 'js' => array( + 'fontfaceobserver.js' => array( + 'type' => 'file', + 'group' => JS_LIBRARY, + 'async' => TRUE, + 'defer' => TRUE, + ), + ), + ), + 'variants' => array(), + ); + // Get the latest tagged version for external file loading. + $version = advagg_get_remote_libraries_version('fontfaceobserver', $libraries['fontfaceobserver']); + $libraries['fontfaceobserver']['variants'] += array( + 'external' => array( + 'files' => array( + 'js' => array( + "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js" => array( + 'type' => 'external', + 'data' => "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js", + 'async' => TRUE, + 'defer' => TRUE, + ), + ), + ), + ), + ); + // Inline if local js is there. + $libraries_paths = array(); + if (is_callable('libraries_get_libraries')) { + $libraries_paths = libraries_get_libraries(); + } + if (!empty($libraries_paths['fontfaceobserver']) && is_readable($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js')) { + $libraries['fontfaceobserver']['variants'] += array( + 'inline' => array( + 'files' => array( + 'js' => array( + 'loadCSS_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js'), + 'no_defer' => TRUE, + ), + ), + ), + ), + ); + } + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_current_hooks_hash_array_alter(). + */ +function advagg_font_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + $aggregate_settings['variables']['advagg_font_fontfaceobserver'] = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Get the replacements array for the css. + * + * @param string $css_string + * String of CSS. + * + * @return array + * An array containing the replacemnts and the font class name. + */ +function advagg_font_get_replacements_array($css_string) { + // Get the CSS that contains a font-family rule. + $length = strlen($css_string); + $property_position = 0; + $property = 'font'; + $property_alt = 'font-family'; + $replacements = array(); + $fonts_with_no_replacements = array(); + $lower = strtolower($css_string); + $safe_fonts_list = array( + 'georgia' => TRUE, + 'palatino' => TRUE, + 'times new roman' => TRUE, + 'times' => TRUE, + + 'arial' => TRUE, + 'helvetica' => TRUE, + 'gadget' => TRUE, + 'verdana' => TRUE, + 'geneva' => TRUE, + 'tahoma' => TRUE, + 'garamond' => TRUE, + 'bookman' => TRUE, + 'comic sans ms' => TRUE, + 'cursive' => TRUE, + 'trebuchet ms' => TRUE, + 'arial black' => TRUE, + 'impact' => TRUE, + 'charcoal' => TRUE, + + 'courier new' => TRUE, + 'courier' => TRUE, + 'monaco' => TRUE, + + 'system' => TRUE, + ); + + while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { + // Find the start of the values for the property. + $start_of_values = strpos($css_string, ':', $property_position); + // Get the property at this location of the css. + $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); + + // Make sure this property is one of the ones we're looking for. + if ($property_in_loop !== $property && $property_in_loop !== $property_alt) { + $property_position += strlen($property); + continue; + } + + // Get position of the last closing bracket plus 1 (start of this section). + $start = strrpos($css_string, '}', -($length - $property_position)); + if ($start === FALSE) { + // Property is in the first selector and a declaration block (full rule + // set). + $start = 0; + } + else { + // Add one to start after the }. + $start++; + } + + // Get closing bracket (end of this section). + $end = strpos($css_string, '}', $property_position); + if ($end === FALSE) { + // The end is the end of this file. + $end = $length; + } + + // Get closing ; in order to get the end of the declaration of the property. + $declaration_end_a = strpos($css_string, ';', $property_position); + $declaration_end_b = strpos($css_string, '}', $property_position); + if ($declaration_end_a === FALSE) { + $declaration_end = $declaration_end_b; + } + else { + $declaration_end = min($declaration_end_a, $declaration_end_b); + } + if ($declaration_end > $end) { + $declaration_end = $end; + } + // Add one in order to capture the } when we ge the full rule set. + $end++; + // Advance position for the next run of the while loop. + $property_position = $end; + + // Get values assigned to this property. + $values_string = substr($css_string, $start_of_values + 1, $declaration_end - ($start_of_values + 1)); + // Parse values string into an array of values. + $values_array = explode(',', $values_string); + if (empty($values_array)) { + continue; + } + + // Values array, first element is a quoted string. + $dq = strpos($values_array[0], '"'); + $sq = strpos($values_array[0], "'"); + $quote_pos = ($sq !== FALSE) ? $sq : $dq; + // Skip if the first font is not quoted. + if ($quote_pos === FALSE) { + continue; + } + + $values_array[0] = trim($values_array[0]); + // Skip if only one font is listed. + if (count($values_array) === 1) { + $fonts_with_no_replacements[$values_array[0]] = ''; + continue; + } + + // Save the first value to a variable; starting at the quote. + $removed_value_original = substr($values_array[0], max($quote_pos - 1, 0)); + + // Resave first value. + if ($quote_pos > 1) { + $values_array[0] = trim(substr($values_array[0], 0, $quote_pos - 1)); + } + + // Get value as a classname. Remove quotes, trim, lowercase, and replace + // spaces with dashes. + $removed_value_classname = strtolower(trim(str_replace(array('"', "'"), '', $removed_value_original))); + $removed_value_classname = str_replace(' ', '-', $removed_value_classname); + + // Remove value if it contains a quote. + $values_array_copy = $values_array; + foreach ($values_array as $key => $value) { + if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { + unset($values_array[$key]); + } + elseif ($key !== 0) { + break; + } + } + + if (empty($values_array)) { + // See if there's a "safe" fallback that is quoted. + $values_array = $values_array_copy; + foreach ($values_array as $key => $value) { + if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { + if ($key !== 0) { + $lower_key = trim(trim(strtolower(trim($value)), '"'), "'"); + if (!empty($safe_fonts_list[$lower_key])) { + break; + } + } + unset($values_array[$key]); + } + elseif ($key !== 0) { + break; + } + } + if (empty($values_array)) { + // No unquoted values left; do not modify the css. + $key = array_shift($values_array_copy); + $fonts_with_no_replacements[$key] = implode(',', $values_array_copy); + continue; + } + } + + $extra = ''; + if (isset($values_array[0])) { + $extra = $values_array[0] . ' '; + unset($values_array[0]); + } + // Rezero the keys. + $values_array = array_values($values_array); + // Save next value. + $next_value_original = trim($values_array[0]); + // Create the values string. + $new_values_string = $extra . implode(',', $values_array); + + // Get all selectors. + $end_of_selectors = strpos($css_string, '{', $start); + $selectors = substr($css_string, $start, $end_of_selectors - $start); + // Ensure selectors is not a media query. + if (stripos($selectors, "@media") !== FALSE) { + // Move the start to the end of the media query. + $start = $end_of_selectors + 1; + // Get the selectors again. + $end_of_selectors = strpos($css_string, '{', $start); + $selectors = substr($css_string, $start, $end_of_selectors - $start); + } + + // From advagg_load_stylesheet_content(). + // Perform some safe CSS optimizations. + // Regexp to match comment blocks. + // Regexp to match double quoted strings. + // Regexp to match single quoted strings. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; + // Strip all comment blocks, but keep double/single quoted strings. + $selectors_stripped = preg_replace( + "<($double_quot|$single_quot)|$comment>Ss", + "$1", + $selectors + ); + + // Add css class to all the selectors. + $selectors_array = explode(',', $selectors_stripped); + foreach ($selectors_array as &$selector) { + // Remove extra whitespace. + $selector = trim($selector); + $selector = " .{$removed_value_classname} {$selector}"; + } + $new_selectors = implode(',', $selectors_array); + + // Get full rule set. + $full_rule_set = substr($css_string, $start, $end - $start); + // Replace values. + $new_values_full_rule_set = str_replace($values_string, $new_values_string, $full_rule_set); + // Add in old rule set with new selectors. + $new_selectors_full_rule_set = $new_selectors . '{' . $property_in_loop . ': ' . $values_string . ';}'; + + // Record info. + $replacements[] = array( + $full_rule_set, + $new_values_full_rule_set, + $new_selectors_full_rule_set, + $removed_value_original, + $removed_value_classname, + $next_value_original, + ); + } + return array($replacements, $fonts_with_no_replacements); +} diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info new file mode 100644 index 000000000..d7a6735cf --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info @@ -0,0 +1,11 @@ +name = AdvAgg CDN Javascript +description = Use a shared CDN for javascript libraries, Google Libraries API currently. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.install b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.install new file mode 100644 index 000000000..39502ea5c --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.install @@ -0,0 +1,69 @@ +jquery update settings page and select a CDN instead of using this module.', array( + '@settings' => url('admin/config/development/jquery_update', array( + 'fragment' => 'edit-jquery-update-jquery-cdn', + )), + )); + } + else { + $jquery_description = $t('The jquery update module is already configured to use the external CDN "@cdn".', array('@cdn' => $jquery_cdn)); + } + + $requirements['advagg_js_cdn_jquery_update'] = array( + 'title' => $t('Adv JS CDN - jquery update'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Duplicate Functionality: Use jquery update instead of the advagg_js_cdn sub module.'), + 'description' => $jquery_description . ' ' . $t('You should go to the modules page and disable the "AdvAgg CDN JS" module.', array( + '@modules' => url('admin/modules', array( + 'fragment' => 'edit-modules-advanced-cssjs-aggregation', + )), + )), + ); + } + + if (empty($requirements)) { + $description = ''; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $description = $t('Will be using the unminified version due to AdvAgg being in Development mode.'); + } + $requirements['advagg_js_cdn'] = array( + 'title' => $t('Adv JS CDN'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('OK'), + 'description' => $t('jQuery and jQuery UI JS should be coming from a CDN.') . ' ' . $description, + ); + } + + return $requirements; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module new file mode 100644 index 000000000..bcb164d83 --- /dev/null +++ b/frontend/drupal/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module @@ -0,0 +1,162 @@ + $values) { + // Only modify if + // advagg_js_cdn_jquery is enabled, + // name is misc/jquery.js, + // and type is file. + if (variable_get('advagg_js_cdn_jquery', ADVAGG_JS_CDN_JQUERY) + && $name === 'misc/jquery.js' + && $javascript[$name]['type'] === 'file' + ) { + // Add in backup. + $values['weight'] += 0.00001; + $values['data'] = 'window.jQuery || document.write("