From 6b6daf98e99b2ae8b957e3fb758175da5998e584 Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Wed, 28 Aug 2013 15:24:23 +0200 Subject: [PATCH 01/35] Minor - format/cleaning code --- main/newscorm/lp_add_audio.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/main/newscorm/lp_add_audio.php b/main/newscorm/lp_add_audio.php index ed29aa1368..4b3ae5098b 100644 --- a/main/newscorm/lp_add_audio.php +++ b/main/newscorm/lp_add_audio.php @@ -28,7 +28,9 @@ $action = isset($_GET['action']) ? $_GET['action'] : null; // Using the resource linker as a tool for adding resources to the learning path. if ($action == 'add' && $type == 'learnpathitem') { - $htmlHeadXtra[] = ""; + $htmlHeadXtra[] = ""; } if ((!$is_allowed_to_edit) || ($isStudentView)) { error_log('New LP - User not authorized in lp_add_item.php'); @@ -92,17 +94,24 @@ echo ''; echo '
'; $lp_item = new learnpathItem($lp_item_id); + $form = new FormValidator('add_audio', 'post', api_get_self().'?action=add_audio&id='.$lp_item_id, null, array('enctype' => 'multipart/form-data')); + +$form->addElement('header', get_lang('RecordYourVoice')); + + $form->addElement('header', get_lang('UplUpload')); + $form->addElement('html', $lp_item->get_title()); $form->addElement('file', 'file', get_lang('AudioFile'), 'style="width: 250px"'); $form->addElement('hidden', 'id', $lp_item_id); -if (isset($lp_item->audio) && !empty($lp_item->audio)) { +if (isset($lp_item->audio) && !empty($lp_item->audio)) { $form->addElement('checkbox', 'delete_file', null, get_lang('RemoveAudio')); $player = ''; - $player .= '
'; - $output .= ' -
'; + $output .= $player; + $output .= ''; } return $output; } + /** * This function checks if the learnpath is visible for student after the progress of its prerequisite is completed, and considering time availability * @param int Learnpath id @@ -5304,7 +5312,8 @@ class learnpath { * This function builds the action menu * @return void */ - public function build_action_menu() { + public function build_action_menu($returnContent = false) + { $is_allowed_to_edit = api_is_allowed_to_edit(null,true); $gradebook = isset($_GET['gradebook']) ? Security :: remove_XSS($_GET['gradebook']) : null; @@ -5334,6 +5343,10 @@ class learnpath { ); $return .= Display::group_button(get_lang('PrerequisitesOptions'), $buttons); $return .= ''; + + if ($returnContent) { + return $return; + } echo $return; } diff --git a/main/newscorm/lp_add_audio.php b/main/newscorm/lp_add_audio.php index 4b3ae5098b..6a7e56e50a 100644 --- a/main/newscorm/lp_add_audio.php +++ b/main/newscorm/lp_add_audio.php @@ -13,8 +13,9 @@ $this_section = SECTION_COURSES; api_protect_course_script(); -include 'learnpath_functions.inc.php'; -include 'resourcelinker.inc.php'; +require_once 'learnpath_functions.inc.php'; +require_once 'resourcelinker.inc.php'; + $is_allowed_to_edit = api_is_allowed_to_edit(null, true); @@ -45,9 +46,9 @@ if (isset($_SESSION['gradebook'])) { if (!empty($gradebook) && $gradebook == 'view') { $interbreadcrumb[] = array ( - 'url' => '../gradebook/'.$_SESSION['gradebook_dest'], - 'name' => get_lang('ToolGradebook') - ); + 'url' => '../gradebook/'.$_SESSION['gradebook_dest'], + 'name' => get_lang('ToolGradebook') + ); } $interbreadcrumb[] = array('url' => 'lp_controller.php?action=list', 'name' => get_lang('LearningPaths')); @@ -66,7 +67,7 @@ switch ($type) { break; } -if ($action == 'add_item' && $type == 'document' ) { +if ($action == 'add_item' && $type == 'document') { $interbreadcrumb[]= array ('url' => '#', 'name' => get_lang('NewDocumentCreated')); } @@ -77,21 +78,21 @@ if (empty($lp_item_id)) { api_not_allowed(); } -Display::display_header(null, 'Path'); - $suredel = trim(get_lang('AreYouSureToDelete')); /* DISPLAY SECTION */ -echo $_SESSION['oLP']->build_action_menu(); +$tpl = new Template($tool_name); + +$page = $_SESSION['oLP']->build_action_menu(true); -echo '
'; -echo '
'; -echo $_SESSION['oLP']->return_new_tree(null, true); +$page .= '
'; +$page .= '
'; +$page .= $_SESSION['oLP']->return_new_tree(null, true); // Show the template list. -echo '
'; +$page .= '
'; -echo '
'; +$page .= '
'; $lp_item = new learnpathItem($lp_item_id); @@ -99,7 +100,10 @@ $form = new FormValidator('add_audio', 'post', api_get_self().'?action=add_audio $form->addElement('header', get_lang('RecordYourVoice')); +$tpl->assign('lp_item_id', $lp_item_id); +$voiceContent = $tpl->fetch('default/learnpath/record_voice.tpl'); +$form->addElement('html', $voiceContent); $form->addElement('header', get_lang('UplUpload')); $form->addElement('html', $lp_item->get_title()); @@ -108,28 +112,29 @@ $form->addElement('hidden', 'id', $lp_item_id); if (isset($lp_item->audio) && !empty($lp_item->audio)) { $form->addElement('checkbox', 'delete_file', null, get_lang('RemoveAudio')); - - $player = ''; - $player .= '
- '; - $form->addElement('label', get_lang('Preview'), $player); + $file = '../../courses/'.$_course['path'].'/document/audio/'.$lp_item->audio; + if (!file_exists($file)) { + $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info()); + $file = '../../courses/'.$_course['path'].'/document'.$lpPathInfo['dir'].$lp_item->audio; + } + $audioPlayer = '
'.Display::getMediaPlayer($file)."
"; + $form->addElement('label', get_lang('Preview'), $audioPlayer); } $form->addElement('button', 'submit', get_lang('Edit')); $course_info = api_get_course_info(); $document_tree = DocumentManager::get_document_preview($course_info, null, null, 0, false, '/audio', 'lp_controller.php?action=add_audio&id='.$lp_item_id); -$form->display(); +$page .= $form->return_form(); + +$page .= ''.get_lang('SelectAnAudioFileFromDocuments').''; +$page .= $document_tree; + +$page .= '
'; -echo ''.get_lang('SelectAnAudioFileFromDocuments').''; -echo $document_tree; -echo '
'; +$page .= '
'; -echo '
'; -Display::display_footer(); +$tpl->assign('content', $page); +$content = $tpl->fetch('default/learnpath/lp_upload_audio.tpl'); +$tpl->display_one_col_template(); diff --git a/main/template/default/learnpath/lp_upload_audio.tpl b/main/template/default/learnpath/lp_upload_audio.tpl new file mode 100644 index 0000000000..cddd07099f --- /dev/null +++ b/main/template/default/learnpath/lp_upload_audio.tpl @@ -0,0 +1 @@ +{{ content }} diff --git a/main/template/default/learnpath/record_voice.tpl b/main/template/default/learnpath/record_voice.tpl new file mode 100644 index 0000000000..ae6bfd27d2 --- /dev/null +++ b/main/template/default/learnpath/record_voice.tpl @@ -0,0 +1,120 @@ + + + + +
+ + + + + + + From 70c2f2dc3a8e6726db7a39f3867e96e990f63fc9 Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Thu, 29 Aug 2013 14:32:51 +0200 Subject: [PATCH 07/35] Minor - format code --- main/inc/lib/document.lib.php | 6 ++-- main/inc/lib/fileUpload.lib.php | 58 +++++++++++++++++---------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/main/inc/lib/document.lib.php b/main/inc/lib/document.lib.php index 9e353a820d..297ac73136 100644 --- a/main/inc/lib/document.lib.php +++ b/main/inc/lib/document.lib.php @@ -521,6 +521,7 @@ class DocumentManager { if ($result !== false && Database::num_rows($result) != 0) { while ($row = Database::fetch_array($result, 'ASSOC')) { + if (api_is_coach()) { //Looking for course items that are invisible to hide it in the session if (in_array($row['id'], array_keys($doc_list))) { @@ -553,7 +554,8 @@ class DocumentManager { $document_data[$row['id']] = $row; } - //Only for the student we filter the results see BT#1652 + // Only for the student we filter the results see BT#1652 + if (!api_is_coach() && !$is_allowed_to_edit) { $ids_to_remove = array(); $my_repeat_ids = $temp = array(); @@ -579,7 +581,6 @@ class DocumentManager { } } } - foreach ($doc_list as $key => $row) { if (in_array($row['visibility'], array('0', '2')) && !in_array($row['id'], $my_repeat_ids)) { $ids_to_remove[] = $row['id']; @@ -2249,6 +2250,7 @@ class DocumentManager { if (isset($files['file'])) { $upload_ok = process_uploaded_file($files['file'], $show_output); + if ($upload_ok) { // File got on the server without problems, now process it $new_path = handle_uploaded_document($course_info, $files['file'], $base_work_dir, $path, api_get_user_id(), api_get_group_id(), null, $unzip, $if_exists, $show_output); diff --git a/main/inc/lib/fileUpload.lib.php b/main/inc/lib/fileUpload.lib.php index ad52be4130..87df886184 100644 --- a/main/inc/lib/fileUpload.lib.php +++ b/main/inc/lib/fileUpload.lib.php @@ -89,34 +89,36 @@ function get_document_title($name) { */ function process_uploaded_file($uploaded_file, $show_output = true) { // Checking the error code sent with the file upload. - - switch ($uploaded_file['error']) { - case 1: - // The uploaded file exceeds the upload_max_filesize directive in php.ini. - if ($show_output) - Display::display_error_message(get_lang('UplExceedMaxServerUpload').ini_get('upload_max_filesize')); - return false; - case 2: - // The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. - // Not used at the moment, but could be handy if we want to limit the size of an upload (e.g. image upload in html editor). - $max_file_size = intval($_POST['MAX_FILE_SIZE']); - if ($show_output) { - Display::display_error_message(get_lang('UplExceedMaxPostSize'). format_file_size($max_file_size)); - } - return false; - case 3: - // The uploaded file was only partially uploaded. - if ($show_output) { - Display::display_error_message(get_lang('UplPartialUpload').' '.get_lang('PleaseTryAgain')); - } - return false; - case 4: - // No file was uploaded. - if ($show_output) { - Display::display_error_message(get_lang('UplNoFileUploaded').' '. get_lang('UplSelectFileFirst')); - } - return false; - } + if (isset($uploaded_file['error'])) { + switch ($uploaded_file['error']) { + case 1: + // The uploaded file exceeds the upload_max_filesize directive in php.ini. + if ($show_output) { + Display::display_error_message(get_lang('UplExceedMaxServerUpload').ini_get('upload_max_filesize')); + } + return false; + case 2: + // The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. + // Not used at the moment, but could be handy if we want to limit the size of an upload (e.g. image upload in html editor). + $max_file_size = intval($_POST['MAX_FILE_SIZE']); + if ($show_output) { + Display::display_error_message(get_lang('UplExceedMaxPostSize'). format_file_size($max_file_size)); + } + return false; + case 3: + // The uploaded file was only partially uploaded. + if ($show_output) { + Display::display_error_message(get_lang('UplPartialUpload').' '.get_lang('PleaseTryAgain')); + } + return false; + case 4: + // No file was uploaded. + if ($show_output) { + Display::display_error_message(get_lang('UplNoFileUploaded').' '. get_lang('UplSelectFileFirst')); + } + return false; + } + } if (!file_exists($uploaded_file['tmp_name'])) { // No file was uploaded. From 6eea2df50f806a04a80beeb20417f88688595d27 Mon Sep 17 00:00:00 2001 From: Hubert Borderiou Date: Thu, 29 Aug 2013 17:12:42 +0200 Subject: [PATCH 08/35] Random questions quizzes in learnpath prerequisites show with max score - ref #6624 --- main/exercice/exercise.class.php | 75 ++++++++++++++++++++++++++----- main/newscorm/learnpath.class.php | 9 ++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/main/exercice/exercise.class.php b/main/exercice/exercise.class.php index e76f7f2fe9..2a5f9e98d5 100644 --- a/main/exercice/exercise.class.php +++ b/main/exercice/exercise.class.php @@ -376,7 +376,7 @@ class Exercise { /** * returns random answers status. * - * @author - Juan Carlos Ra�a + * @author - Juan Carlos Rana */ function selectRandomAnswers() { return $this->random_answers; @@ -634,7 +634,7 @@ class Exercise { /** * sets to 0 if answers are not selected randomly * if answers are selected randomly - * @author - Juan Carlos Ra�a + * @author - Juan Carlos Rana * @param - integer $random_answers - random answers */ function updateRandomAnswers($random_answers) { @@ -1264,6 +1264,7 @@ class Exercise { $this->random_answers=0; } $this->save($type); + } function search_engine_save() { @@ -1540,7 +1541,7 @@ class Exercise { $lp_id = 0; } if (empty($lp_item_id)) { - $lp_item_id = 0; + $lp_item_id = 0; } if (empty($lp_item_view_id)) { $lp_item_view_id = 0; @@ -3681,16 +3682,16 @@ class Exercise { $questionList = array(); $tabCategoryQuestions = Testcategory::getQuestionsByCat($this->id); $isRandomByCategory = $this->selectRandomByCat(); - // on tri les categories en fonction du terme entre [] en tête de la description de la catégorie + // on tri les categories en fonction du terme entre [] en tete de la description de la categorie /* * ex de catégories : - * [biologie] Maîtriser les mécanismes de base de la génétique - * [biologie] Relier les moyens de défenses et les agents infectieux - * [biologie] Savoir où est produite l'énergie dans les cellules et sous quelle forme - * [chimie] Classer les molécules suivant leur pouvoir oxydant ou réducteur - * [chimie] Connaître la définition de la théorie acide/base selon Brönsted - * [chimie] Connaître les charges des particules - * On veut dans l'ordre des groupes définis par le terme entre crochet au début du titre de la catégorie + * [biologie] Maitriser les m袡nismes de base de la g诩tique + * [biologie] Relier les moyens de depenses et les agents infectieux + * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme + * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur + * [chimie] Connaître la denition de la theoie acide/base selon Brönsted + * [chimie] Connaître les charges des particules + * On veut dans l'ordre des groupes definis par le terme entre crochet au debut du titre de la categorie */ // If test option is Grouped By Categories if ($isRandomByCategory == 2) { @@ -3897,4 +3898,56 @@ class Exercise { } return $list; } + + + /** + * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option + */ + public function get_max_score() { + $out_max_score = 0; + $tab_question_list = $this->selectQuestionList(true); // list of question's id !!! the array key start at 1 !!! + // test is randomQuestions - see field random of test + if ($this->random > 0 && $this->randomByCat == 0) { + $nb_random_questions = $this->random; + $tab_questions_score = array(); + for ($i=1; $i <= count($tab_question_list); $i++) { + $tmpobj_question = Question::read($tab_question_list[$i]); + $tab_questions_score[] = $tmpobj_question->weighting; + } + rsort($tab_questions_score); + // add the first $nb_random_questions value of score array to get max_score + for ($i=0; $i < min($nb_random_questions, count($tab_questions_score)); $i++) { + $out_max_score += $tab_questions_score[$i]; + } + } + // test is random by category + // get the $nb_random_questions best score question of each category + else if ($this->random > 0 && $this->randomByCat > 0) { + $nb_random_questions = $this->random; + $tab_categories_scores = array(); + for ($i=1; $i <= count($tab_question_list); $i++) { + $question_category_id = Testcategory::getCategoryForQuestion($tab_question_list[$i]); + if (!is_array($tab_categories_scores[$question_category_id])) { + $tab_categories_scores[$question_category_id] = array(); + } + $tmpobj_question = Question::read($tab_question_list[$i]); + $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting; + } + // here we've got an array with first key, the category_id, second key, score of question for this cat + while (list($key, $tab_scores) = each($tab_categories_scores)) { + rsort($tab_scores); + for ($i=0; $i < min($nb_random_questions, count($tab_scores)); $i++) { + $out_max_score += $tab_scores[$i]; + } + } + } + // standart test, just add each question score + else { + for ($i=1; $i <= count($tab_question_list); $i++) { + $tmpobj_question = Question::read($tab_question_list[$i]); + $out_max_score += $tmpobj_question->weighting; + } + } + return $out_max_score; + } } diff --git a/main/newscorm/learnpath.class.php b/main/newscorm/learnpath.class.php index 27f0f2f70d..8da2c0765d 100644 --- a/main/newscorm/learnpath.class.php +++ b/main/newscorm/learnpath.class.php @@ -7893,6 +7893,15 @@ class learnpath { //$return .= ''; if ($arrLP[$i]['item_type'] == TOOL_QUIZ) { + // lets update max_score Quiz information depending of the Quiz Advanced properties + require_once api_get_path(LIBRARY_PATH)."lp_item.lib.php"; + $tmp_obj_lp_item = new LpItem($course_id, $arrLP[$i]['id']); + $tmp_obj_exercice = new Exercise(); + $tmp_obj_exercice->read($tmp_obj_lp_item->path); + $tmp_obj_lp_item->max_score = $tmp_obj_exercice->get_max_score(); + $tmp_obj_lp_item->update_in_bdd(); + $arrLP[$i]['max_score'] = $tmp_obj_lp_item->max_score; + $return .= ''; $return .= '
'; $return .= ''; From 40aa045dbd536d6108614f7a624f185e49404dcd Mon Sep 17 00:00:00 2001 From: Hubert Borderiou Date: Thu, 29 Aug 2013 17:19:59 +0200 Subject: [PATCH 09/35] Random questions quizzes in learnpath prerequisites - ref #6624 --- main/exercice/exercise.class.php | 2 +- main/inc/lib/lp_item.lib.php | 101 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 main/inc/lib/lp_item.lib.php diff --git a/main/exercice/exercise.class.php b/main/exercice/exercise.class.php index 2a5f9e98d5..b93d108c0b 100644 --- a/main/exercice/exercise.class.php +++ b/main/exercice/exercise.class.php @@ -3685,7 +3685,7 @@ class Exercise { // on tri les categories en fonction du terme entre [] en tete de la description de la categorie /* * ex de catégories : - * [biologie] Maitriser les m袡nismes de base de la g诩tique + * [biologie] Maitriser les mecanismes de base de la genetique * [biologie] Relier les moyens de depenses et les agents infectieux * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur diff --git a/main/inc/lib/lp_item.lib.php b/main/inc/lib/lp_item.lib.php new file mode 100644 index 0000000000..4f2eaae3c6 --- /dev/null +++ b/main/inc/lib/lp_item.lib.php @@ -0,0 +1,101 @@ + 0 && $in_id >0) { + $item_view_table = Database::get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * FROM $item_view_table WHERE c_id=".Database::escape_string($in_c_id)." + AND id=".Database::escape_string($in_id); + + $res = Database::query($sql); + $data = Database::fetch_array($res); + if (Database::num_rows($res) > 0) { + $this->c_id = $data['c_id']; + $this->id = $data['id']; + $this->lp_id = $data['lp_id']; + $this->item_type = $data['item_type']; + $this->ref = $data['ref']; + $this->title = $data['title']; + $this->description = $data['description']; + $this->path = $data['path']; + $this->min_score = $data['min_score']; + $this->max_score = $data['max_score']; + $this->mastery_score = $data['mastery_score']; + $this->parent_item_id = $data['parent_item_id']; + $this->previous_item_id = $data['previous_item_id']; + $this->next_item_id = $data['next_item_id']; + $this->display_order = $data['display_order']; + $this->prerequisite = $data['prerequisite']; + $this->parameters = $data['parameters']; + $this->launch_data = $data['launch_data']; + $this->max_time_allowed = $data['max_time_allowed']; + $this->terms = $data['terms']; + $this->search_did = $data['search_did']; + $this->audio = $data['audio']; + } + } + } + + public function update_in_bdd() { + $item_view_table = Database::get_course_table(TABLE_LP_ITEM); + if ($this->c_id > 0 && $this->id > 0) { + $sql = "UPDATE $item_view_table SET + lp_id = '".Database::escape_string($this->lp_id)."' , + item_type = '".Database::escape_string($this->item_type)."' , + ref = '".Database::escape_string($this->ref)."' , + title = '".Database::escape_string($this->title)."' , + description = '".Database::escape_string($this->description)."' , + path = '".Database::escape_string($this->path)."' , + min_score = '".Database::escape_string($this->min_score)."' , + max_score = '".Database::escape_string($this->max_score)."' , + mastery_score = '".Database::escape_string($this->mastery_score)."' , + parent_item_id = '".Database::escape_string($this->parent_item_id)."' , + previous_item_id = '".Database::escape_string($this->previous_item_id)."' , + next_item_id = '".Database::escape_string($this->next_item_id)."' , + display_order = '".Database::escape_string($this->display_order)."' , + prerequisite = '".Database::escape_string($this->prerequisite)."' , + parameters = '".Database::escape_string($this->parameters)."' , + launch_data = '".Database::escape_string($this->launch_data)."' , + max_time_allowed = '".Database::escape_string($this->max_time_allowed)."' , + terms = '".Database::escape_string($this->terms)."' , + search_did = '".Database::escape_string($this->search_did)."' , + audio = '".Database::escape_string($this->audio)."' + WHERE c_id=".$this->c_id." AND id=".$this->id; + Database::query($sql); + } + } + +} From 31103acb96ba3f7ace833ba2ae1201378cb612ac Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 30 Aug 2013 13:02:36 +0200 Subject: [PATCH 10/35] Minor - format code --- .../inc/lib/wami-recorder/record_document.php | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/main/inc/lib/wami-recorder/record_document.php b/main/inc/lib/wami-recorder/record_document.php index 720bb87621..7e41ba6762 100644 --- a/main/inc/lib/wami-recorder/record_document.php +++ b/main/inc/lib/wami-recorder/record_document.php @@ -1,29 +1,31 @@ \ No newline at end of file +$doc_id = add_document($_course, $wamidir.'/'.$waminame_to_save, 'file', filesize($documentPath), $title_to_save); +api_item_property_update( + $_course, + TOOL_DOCUMENT, + $doc_id, + 'DocumentAdded', + $_user['user_id'], + $groupId, + null, + null, + null, + $current_session_id +); From d72b99a23395c554838d40f6f76addb20517d5dd Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 30 Aug 2013 15:29:27 +0200 Subject: [PATCH 11/35] Visibility works in latest chrome/firefox. --- main/inc/lib/wami-recorder/recorder.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main/inc/lib/wami-recorder/recorder.js b/main/inc/lib/wami-recorder/recorder.js index 299e084b46..50cf0802ed 100644 --- a/main/inc/lib/wami-recorder/recorder.js +++ b/main/inc/lib/wami-recorder/recorder.js @@ -75,7 +75,9 @@ Wami.setup = function(options) { // Detecting the OS is a big no-no in Javascript programming, but // I can't think of a better way to know if wmode is supported or // not... since NOT supporting it (like Flash on Ubuntu) is a bug. - return (navigator.platform.indexOf("Linux") == -1); + //return (navigator.platform.indexOf("Linux") == -1); + // Chamilo change + return true; } function setOptions(options) { @@ -125,7 +127,7 @@ Wami.setup = function(options) { var container = document.createElement('div'); container.style.position = 'absolute'; - _options.cid = Wami.createID(); + _options.cid = Wami.createID(); container.setAttribute('id', _options.cid); var swfdiv = document.createElement('div'); From 68053abcb91cda3c9d4aebc1244b8cd3740b21ba Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 30 Aug 2013 15:30:45 +0200 Subject: [PATCH 12/35] Adding wami support if browser is not chrome. see BT#6613 --- .../inc/lib/wami-recorder/record_document.php | 74 +++++++-- main/newscorm/learnpath.class.php | 5 +- main/newscorm/lp_add_audio.php | 34 ++-- .../default/learnpath/record_voice.tpl | 154 +++++++++++++----- 4 files changed, 189 insertions(+), 78 deletions(-) diff --git a/main/inc/lib/wami-recorder/record_document.php b/main/inc/lib/wami-recorder/record_document.php index 7e41ba6762..05efff625a 100644 --- a/main/inc/lib/wami-recorder/record_document.php +++ b/main/inc/lib/wami-recorder/record_document.php @@ -26,7 +26,6 @@ if ($wamiuserid != api_get_user_id() || api_get_user_id() == 0 || $wamiuserid == die(); } - //clean $waminame = Security::remove_XSS($waminame); $waminame = Database::escape_string($waminame); @@ -34,9 +33,12 @@ $waminame = addslashes(trim($waminame)); $waminame = replace_dangerous_char($waminame, 'strict'); $waminame = disable_dangerous_file($waminame); $wamidir = Security::remove_XSS($wamidir); - $content = file_get_contents('php://input'); +if (empty($content)) { + exit; +} + //security extension $ext = explode('.', $waminame); $ext = strtolower($ext[sizeof($ext) - 1]); @@ -50,7 +52,7 @@ if ($ext != 'wav') { $dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'; $saveDir = $dirBaseDocuments.$wamidir; $current_session_id = api_get_session_id(); -$groupId = $_SESSION['_gid']; +$groupId = api_get_group_id(); //avoid duplicates $waminame_to_save = $waminame; @@ -68,22 +70,58 @@ if (file_exists($saveDir.'/'.$waminame_noex.'.'.$ext)) { $documentPath = $saveDir.'/'.$waminame_to_save; -//add to disk +// Add to disk $fh = fopen($documentPath, 'w') or die("can't open file"); fwrite($fh, $content); fclose($fh); -//add document to database -$doc_id = add_document($_course, $wamidir.'/'.$waminame_to_save, 'file', filesize($documentPath), $title_to_save); -api_item_property_update( - $_course, - TOOL_DOCUMENT, - $doc_id, - 'DocumentAdded', - $_user['user_id'], - $groupId, - null, - null, - null, - $current_session_id -); +$addToLP = false; + +if (isset($_REQUEST['lp_item_id']) && !empty($_REQUEST['lp_item_id'])) { + $lpItemId = $_REQUEST['lp_item_id']; + $lp = isset($_SESSION['oLP']) ? $_SESSION['oLP'] : null; + + if (!empty($lp)) { + $addToLP = true; + // Converts wav into mp3 + require_once '../../../../vendor/autoload.php'; + $ffmpeg = \FFMpeg\FFMpeg::create(); + $oldWavFile = $documentPath; + if (file_exists($oldWavFile)) { + $video = $ffmpeg->open($documentPath); + $waminame_to_save = str_replace('wav', 'mp3', $waminame_to_save); + $documentPath = $saveDir.'/'.$waminame_to_save; + $title_to_save = $waminame_to_save; + + $result = $video->save(new FFMpeg\Format\Audio\Mp3(), $documentPath); + if ($result) { + unlink($oldWavFile); + } + } + } +} + +if (file_exists($documentPath)) { + // Add document to database + $newDocId = add_document($_course, $wamidir.'/'.$waminame_to_save, 'file', filesize($documentPath), $title_to_save); + + api_item_property_update( + $_course, + TOOL_DOCUMENT, + $newDocId, + 'DocumentAdded', + api_get_user_id(), + $groupId, + null, + null, + null, + $current_session_id + ); + + if ($addToLP) { + $lp->set_modified_on(); + $lpItem = new learnpathItem($lpItemId); + $lpItem->add_audio_from_documents($newDocId); + } +} + diff --git a/main/newscorm/learnpath.class.php b/main/newscorm/learnpath.class.php index 27f0f2f70d..c0a1e1b593 100644 --- a/main/newscorm/learnpath.class.php +++ b/main/newscorm/learnpath.class.php @@ -4922,7 +4922,8 @@ class learnpath { public function return_new_tree($update_audio = 'false', $drop_element_here = false) { $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php'; - echo ' - - -
- - + + + + +
+ + +
+ {{ 'RecordAudio' | get_lang }} + +
+ +
+ {{ 'ActivateAudioRecorder' | get_lang }} +
+
+
+ +
+ - - - From 08130773c4feeeff876cd4065d7dbd085e6b7dc5 Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 30 Aug 2013 15:38:40 +0200 Subject: [PATCH 13/35] adding vendors see BT#6613 --- composer.json | 5 + vendor/alchemy/binary-driver/.travis.yml | 11 + vendor/alchemy/binary-driver/CHANGELOG.md | 61 + vendor/alchemy/binary-driver/LICENSE | 21 + vendor/alchemy/binary-driver/README.md | 190 +++ vendor/alchemy/binary-driver/composer.json | 38 + vendor/alchemy/binary-driver/phpunit.xml.dist | 31 + .../Alchemy/BinaryDriver/AbstractBinary.php | 220 +++ .../BinaryDriver/BinaryDriverTestCase.php | 76 + .../Alchemy/BinaryDriver/BinaryInterface.php | 66 + .../Alchemy/BinaryDriver/Configuration.php | 107 ++ .../ConfigurationAwareInterface.php | 29 + .../BinaryDriver/ConfigurationInterface.php | 58 + .../Exception/ExceptionInterface.php | 16 + .../Exception/ExecutableNotFoundException.php | 16 + .../Exception/ExecutionFailureException.php | 16 + .../Exception/InvalidArgumentException.php | 16 + .../BinaryDriver/Listeners/DebugListener.php | 58 + .../Listeners/ListenerInterface.php | 32 + .../BinaryDriver/Listeners/Listeners.php | 88 ++ .../BinaryDriver/ProcessBuilderFactory.php | 177 +++ .../ProcessBuilderFactoryAwareInterface.php | 29 + .../ProcessBuilderFactoryInterface.php | 65 + .../Alchemy/BinaryDriver/ProcessRunner.php | 104 ++ .../ProcessRunnerAwareInterface.php | 29 + .../BinaryDriver/ProcessRunnerInterface.php | 33 + .../Tests/BinaryDriver/AbstractBinaryTest.php | 297 ++++ .../AbstractProcessBuilderFactoryTest.php | 97 ++ .../Tests/BinaryDriver/ConfigurationTest.php | 78 + .../LTSProcessBuilderFactoryTest.php | 66 + .../Listeners/DebugListenerTest.php | 33 + .../BinaryDriver/Listeners/ListenersTest.php | 92 ++ .../NONLTSProcessBuilderFactoryTest.php | 15 + .../Tests/BinaryDriver/ProcessRunnerTest.php | 208 +++ .../alchemy/binary-driver/tests/bootstrap.php | 4 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 246 ++++ vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_namespaces.php | 16 + vendor/composer/autoload_real.php | 43 + vendor/composer/installed.json | 397 +++++ vendor/doctrine/cache/.travis.yml | 9 + vendor/doctrine/cache/LICENSE | 19 + vendor/doctrine/cache/README.md | 9 + vendor/doctrine/cache/composer.json | 29 + .../lib/Doctrine/Common/Cache/ApcCache.php | 91 ++ .../lib/Doctrine/Common/Cache/ArrayCache.php | 93 ++ .../cache/lib/Doctrine/Common/Cache/Cache.php | 111 ++ .../Doctrine/Common/Cache/CacheProvider.php | 240 +++ .../Doctrine/Common/Cache/CouchbaseCache.php | 121 ++ .../lib/Doctrine/Common/Cache/FileCache.php | 158 ++ .../Doctrine/Common/Cache/FilesystemCache.php | 113 ++ .../Doctrine/Common/Cache/MemcacheCache.php | 121 ++ .../Doctrine/Common/Cache/MemcachedCache.php | 124 ++ .../Doctrine/Common/Cache/PhpFileCache.php | 107 ++ .../lib/Doctrine/Common/Cache/RedisCache.php | 130 ++ .../lib/Doctrine/Common/Cache/RiakCache.php | 250 ++++ .../Doctrine/Common/Cache/WinCacheCache.php | 91 ++ .../lib/Doctrine/Common/Cache/XcacheCache.php | 109 ++ .../Doctrine/Common/Cache/ZendDataCache.php | 83 ++ vendor/doctrine/cache/phpunit.xml.dist | 31 + .../Tests/Common/Cache/ApcCacheTest.php | 20 + .../Tests/Common/Cache/ArrayCacheTest.php | 21 + .../Doctrine/Tests/Common/Cache/CacheTest.php | 103 ++ .../Tests/Common/Cache/CouchbaseCacheTest.php | 47 + .../Tests/Common/Cache/FileCacheTest.php | 107 ++ .../Common/Cache/FilesystemCacheTest.php | 102 ++ .../Tests/Common/Cache/MemcacheCacheTest.php | 45 + .../Tests/Common/Cache/MemcachedCacheTest.php | 48 + .../Tests/Common/Cache/PhpFileCacheTest.php | 154 ++ .../Tests/Common/Cache/RedisCacheTest.php | 30 + .../Tests/Common/Cache/RiakCacheTest.php | 64 + .../Tests/Common/Cache/WinCacheCacheTest.php | 20 + .../Tests/Common/Cache/XcacheCacheTest.php | 20 + .../Tests/Common/Cache/ZendDataCacheTest.php | 28 + .../tests/Doctrine/Tests/DoctrineTestCase.php | 10 + .../cache/tests/Doctrine/Tests/TestInit.php | 23 + vendor/evenement/evenement/.travis.yml | 11 + vendor/evenement/evenement/LICENSE | 19 + vendor/evenement/evenement/README.md | 74 + vendor/evenement/evenement/composer.json | 20 + vendor/evenement/evenement/phpunit.xml.dist | 25 + .../evenement/src/Evenement/EventEmitter.php | 73 + .../evenement/src/Evenement/EventEmitter2.php | 114 ++ .../src/Evenement/EventEmitterInterface.php | 22 + .../Evenement/Tests/EventEmitter2Test.php | 160 ++ .../Evenement/Tests/EventEmitterTest.php | 236 +++ .../tests/Evenement/Tests/Listener.php | 38 + .../evenement/evenement/tests/bootstrap.php | 28 + vendor/monolog/monolog/CHANGELOG.mdown | 110 ++ vendor/monolog/monolog/LICENSE | 19 + vendor/monolog/monolog/README.mdown | 248 ++++ vendor/monolog/monolog/composer.json | 39 + vendor/monolog/monolog/doc/extending.md | 76 + vendor/monolog/monolog/doc/sockets.md | 37 + vendor/monolog/monolog/doc/usage.md | 158 ++ vendor/monolog/monolog/phpunit.xml.dist | 15 + .../monolog/src/Monolog/ErrorHandler.php | 209 +++ .../Monolog/Formatter/ChromePHPFormatter.php | 79 + .../Monolog/Formatter/FormatterInterface.php | 36 + .../Formatter/GelfMessageFormatter.php | 94 ++ .../src/Monolog/Formatter/JsonFormatter.php | 38 + .../src/Monolog/Formatter/LineFormatter.php | 102 ++ .../Monolog/Formatter/LogstashFormatter.php | 98 ++ .../Monolog/Formatter/NormalizerFormatter.php | 137 ++ .../Monolog/Formatter/WildfireFormatter.php | 102 ++ .../src/Monolog/Handler/AbstractHandler.php | 174 +++ .../Handler/AbstractProcessingHandler.php | 66 + .../src/Monolog/Handler/AmqpHandler.php | 69 + .../src/Monolog/Handler/BufferHandler.php | 98 ++ .../src/Monolog/Handler/ChromePHPHandler.php | 183 +++ .../src/Monolog/Handler/CouchDBHandler.php | 72 + .../src/Monolog/Handler/CubeHandler.php | 145 ++ .../Handler/DoctrineCouchDBHandler.php | 45 + .../src/Monolog/Handler/ErrorLogHandler.php | 46 + .../ActivationStrategyInterface.php | 28 + .../ChannelLevelActivationStrategy.php | 57 + .../ErrorLevelActivationStrategy.php | 32 + .../Monolog/Handler/FingersCrossedHandler.php | 118 ++ .../src/Monolog/Handler/FirePHPHandler.php | 184 +++ .../src/Monolog/Handler/GelfHandler.php | 66 + .../src/Monolog/Handler/GroupHandler.php | 80 + .../src/Monolog/Handler/HandlerInterface.php | 88 ++ .../src/Monolog/Handler/HipChatHandler.php | 153 ++ .../src/Monolog/Handler/MailHandler.php | 55 + .../Handler/MissingExtensionException.php | 22 + .../src/Monolog/Handler/MongoDBHandler.php | 55 + .../Monolog/Handler/NativeMailerHandler.php | 68 + .../src/Monolog/Handler/NewRelicHandler.php | 61 + .../src/Monolog/Handler/NullHandler.php | 45 + .../src/Monolog/Handler/PushoverHandler.php | 120 ++ .../src/Monolog/Handler/RavenHandler.php | 167 +++ .../src/Monolog/Handler/RedisHandler.php | 58 + .../Monolog/Handler/RotatingFileHandler.php | 124 ++ .../src/Monolog/Handler/SocketHandler.php | 285 ++++ .../src/Monolog/Handler/StreamHandler.php | 76 + .../Monolog/Handler/SwiftMailerHandler.php | 55 + .../src/Monolog/Handler/SyslogHandler.php | 127 ++ .../src/Monolog/Handler/TestHandler.php | 140 ++ .../Monolog/Handler/ZendMonitorHandler.php | 95 ++ vendor/monolog/monolog/src/Monolog/Logger.php | 574 ++++++++ .../Processor/IntrospectionProcessor.php | 58 + .../Processor/MemoryPeakUsageProcessor.php | 40 + .../src/Monolog/Processor/MemoryProcessor.php | 50 + .../Processor/MemoryUsageProcessor.php | 40 + .../Monolog/Processor/ProcessIdProcessor.php | 40 + .../Processor/PsrLogMessageProcessor.php | 42 + .../src/Monolog/Processor/UidProcessor.php | 38 + .../src/Monolog/Processor/WebProcessor.php | 62 + .../tests/Monolog/ErrorHandlerTest.php | 31 + .../Formatter/ChromePHPFormatterTest.php | 158 ++ .../Formatter/GelfMessageFormatterTest.php | 158 ++ .../Monolog/Formatter/JsonFormatterTest.php | 41 + .../Monolog/Formatter/LineFormatterTest.php | 164 +++ .../Formatter/LogstashFormatterTest.php | 160 ++ .../Formatter/NormalizerFormatterTest.php | 182 +++ .../Formatter/WildfireFormatterTest.php | 111 ++ .../Functional/Handler/FirePHPHandlerTest.php | 32 + .../Monolog/Handler/AbstractHandlerTest.php | 104 ++ .../Handler/AbstractProcessingHandlerTest.php | 79 + .../Monolog/Handler/AmqpExchangeMock.php | 38 + .../tests/Monolog/Handler/AmqpHandlerTest.php | 74 + .../Monolog/Handler/BufferHandlerTest.php | 149 ++ .../Monolog/Handler/ChromePHPHandlerTest.php | 139 ++ .../Monolog/Handler/CouchDBHandlerTest.php | 41 + .../Handler/DoctrineCouchDBHandlerTest.php | 52 + .../Handler/FingersCrossedHandlerTest.php | 189 +++ .../Monolog/Handler/FirePHPHandlerTest.php | 94 ++ .../tests/Monolog/Handler/Fixtures/.gitkeep | 0 .../tests/Monolog/Handler/GelfHandlerTest.php | 94 ++ .../tests/Monolog/Handler/GelfMocks.php | 25 + .../Monolog/Handler/GroupHandlerTest.php | 89 ++ .../Monolog/Handler/HipChatHandlerTest.php | 110 ++ .../tests/Monolog/Handler/MailHandlerTest.php | 75 + .../tests/Monolog/Handler/MockRavenClient.php | 26 + .../Monolog/Handler/MongoDBHandlerTest.php | 63 + .../Handler/NativeMailerHandlerTest.php | 43 + .../Monolog/Handler/NewRelicHandlerTest.php | 65 + .../tests/Monolog/Handler/NullHandlerTest.php | 33 + .../Monolog/Handler/PushoverHandlerTest.php | 142 ++ .../Monolog/Handler/RavenHandlerTest.php | 135 ++ .../Monolog/Handler/RedisHandlerTest.php | 71 + .../Handler/RotatingFileHandlerTest.php | 99 ++ .../Monolog/Handler/SocketHandlerTest.php | 283 ++++ .../Monolog/Handler/StreamHandlerTest.php | 88 ++ .../Monolog/Handler/SyslogHandlerTest.php | 43 + .../tests/Monolog/Handler/TestHandlerTest.php | 56 + .../Handler/ZendMonitorHandlerTest.php | 69 + .../monolog/tests/Monolog/LoggerTest.php | 409 ++++++ .../Processor/IntrospectionProcessorTest.php | 65 + .../MemoryPeakUsageProcessorTest.php | 29 + .../Processor/MemoryUsageProcessorTest.php | 29 + .../Processor/ProcessIdProcessorTest.php | 30 + .../Monolog/Processor/UidProcessorTest.php | 27 + .../Monolog/Processor/WebProcessorTest.php | 68 + .../tests/Monolog/PsrLogCompatTest.php | 47 + .../monolog/tests/Monolog/TestCase.php | 58 + vendor/monolog/monolog/tests/bootstrap.php | 13 + vendor/php-ffmpeg/php-ffmpeg | 1 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 120 ++ .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 17 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 22 + vendor/psr/log/Psr/Log/LoggerInterface.php | 114 ++ vendor/psr/log/Psr/Log/LoggerTrait.php | 131 ++ vendor/psr/log/Psr/Log/NullLogger.php | 27 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 116 ++ vendor/psr/log/README.md | 45 + vendor/psr/log/composer.json | 17 + .../Symfony/Component/Process/CHANGELOG.md | 26 + .../Process/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../Process/Exception/LogicException.php | 21 + .../Exception/ProcessFailedException.php | 49 + .../Process/Exception/RuntimeException.php | 21 + .../Component/Process/ExecutableFinder.php | 90 ++ .../process/Symfony/Component/Process/LICENSE | 19 + .../Component/Process/PhpExecutableFinder.php | 62 + .../Symfony/Component/Process/PhpProcess.php | 73 + .../Symfony/Component/Process/Process.php | 1291 +++++++++++++++++ .../Component/Process/ProcessBuilder.php | 174 +++ .../Component/Process/ProcessUtils.php | 64 + .../Symfony/Component/Process/README.md | 47 + .../Process/Tests/AbstractProcessTest.php | 641 ++++++++ .../Process/Tests/NonStopableProcess.php | 37 + .../Process/Tests/PhpExecutableFinderTest.php | 64 + .../Process/Tests/PhpProcessTest.php | 29 + .../Process/Tests/ProcessBuilderTest.php | 200 +++ .../Tests/ProcessFailedExceptionTest.php | 83 ++ .../Tests/ProcessInSigchildEnvironment.php | 22 + .../Process/Tests/ProcessTestHelper.php | 63 + .../Process/Tests/ProcessUtilsTest.php | 44 + .../Tests/SigchildDisabledProcessTest.php | 188 +++ .../Tests/SigchildEnabledProcessTest.php | 116 ++ .../Process/Tests/SignalListener.php | 16 + .../Process/Tests/SimpleProcessTest.php | 147 ++ .../Symfony/Component/Process/composer.json | 31 + .../Component/Process/phpunit.xml.dist | 28 + 240 files changed, 21421 insertions(+) create mode 100644 composer.json create mode 100644 vendor/alchemy/binary-driver/.travis.yml create mode 100644 vendor/alchemy/binary-driver/CHANGELOG.md create mode 100644 vendor/alchemy/binary-driver/LICENSE create mode 100644 vendor/alchemy/binary-driver/README.md create mode 100644 vendor/alchemy/binary-driver/composer.json create mode 100644 vendor/alchemy/binary-driver/phpunit.xml.dist create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php create mode 100644 vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php create mode 100644 vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ProcessRunnerTest.php create mode 100644 vendor/alchemy/binary-driver/tests/bootstrap.php create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/doctrine/cache/.travis.yml create mode 100644 vendor/doctrine/cache/LICENSE create mode 100644 vendor/doctrine/cache/README.md create mode 100644 vendor/doctrine/cache/composer.json create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php create mode 100644 vendor/doctrine/cache/phpunit.xml.dist create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php create mode 100644 vendor/doctrine/cache/tests/Doctrine/Tests/TestInit.php create mode 100644 vendor/evenement/evenement/.travis.yml create mode 100644 vendor/evenement/evenement/LICENSE create mode 100644 vendor/evenement/evenement/README.md create mode 100644 vendor/evenement/evenement/composer.json create mode 100644 vendor/evenement/evenement/phpunit.xml.dist create mode 100644 vendor/evenement/evenement/src/Evenement/EventEmitter.php create mode 100644 vendor/evenement/evenement/src/Evenement/EventEmitter2.php create mode 100644 vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php create mode 100644 vendor/evenement/evenement/tests/Evenement/Tests/EventEmitter2Test.php create mode 100644 vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php create mode 100644 vendor/evenement/evenement/tests/Evenement/Tests/Listener.php create mode 100644 vendor/evenement/evenement/tests/bootstrap.php create mode 100644 vendor/monolog/monolog/CHANGELOG.mdown create mode 100644 vendor/monolog/monolog/LICENSE create mode 100644 vendor/monolog/monolog/README.mdown create mode 100644 vendor/monolog/monolog/composer.json create mode 100644 vendor/monolog/monolog/doc/extending.md create mode 100644 vendor/monolog/monolog/doc/sockets.md create mode 100644 vendor/monolog/monolog/doc/usage.md create mode 100644 vendor/monolog/monolog/phpunit.xml.dist create mode 100644 vendor/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 vendor/monolog/monolog/src/Monolog/Logger.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/LoggerTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php create mode 100644 vendor/monolog/monolog/tests/Monolog/TestCase.php create mode 100644 vendor/monolog/monolog/tests/bootstrap.php create mode 160000 vendor/php-ffmpeg/php-ffmpeg create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md create mode 100644 vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/LICENSE create mode 100644 vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/PhpProcess.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Process.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/README.md create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/Tests/SimpleProcessTest.php create mode 100644 vendor/symfony/process/Symfony/Component/Process/composer.json create mode 100644 vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000..b9f18bab03 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php-ffmpeg/php-ffmpeg": "0.3.x-dev@dev" + } +} diff --git a/vendor/alchemy/binary-driver/.travis.yml b/vendor/alchemy/binary-driver/.travis.yml new file mode 100644 index 0000000000..beea6866e6 --- /dev/null +++ b/vendor/alchemy/binary-driver/.travis.yml @@ -0,0 +1,11 @@ +language: php + +before_script: + - composer self-update + - composer install --dev --prefer-source + +php: + - 5.3.3 + - 5.3 + - 5.4 + - 5.5 diff --git a/vendor/alchemy/binary-driver/CHANGELOG.md b/vendor/alchemy/binary-driver/CHANGELOG.md new file mode 100644 index 0000000000..621ed80e84 --- /dev/null +++ b/vendor/alchemy/binary-driver/CHANGELOG.md @@ -0,0 +1,61 @@ +CHANGELOG +--------- + +* 1.5.0 (2013-06-21) + + * BC Break : ConfigurationInterface::get does not throw exceptions anymore + in case the key does not exist. Second argument is a default value to return + in case the key does not exist. + +* 1.4.1 (2013-05-23) + + * Add third parameter to BinaryInterface::command method to pass a listener or + an array of listener that will be registered just the time of the command. + +* 1.4.0 (2013-05-11) + + * Extract process run management to ProcessRunner. + * Add support for process listeners. + * Provides bundled DebugListener. + * Add BinaryInterface::command method. + * BC break : ProcessRunnerInterface::run now takes an SplObjectStorage containing + listeners as second argument. + * BC break : BinaryInterface no longer implements LoggerAwareInterface + as it is now supported by ProcessRunner. + +* 1.3.4 (2013-04-26) + + * Add BinaryDriver::run method. + +* 1.3.3 (2013-04-26) + + * Add BinaryDriver::createProcessMock method. + +* 1.3.2 (2013-04-26) + + * Add BinaryDriverTestCase for testing BinaryDriver implementations. + +* 1.3.1 (2013-04-24) + + * Add timeouts handling + +* 1.3.0 (2013-04-24) + + * Add BinaryInterface and AbstractBinary + +* 1.2.1 (2013-04-24) + + * Add ConfigurationAwareInterface + * Add ProcessBuilderAwareInterface + +* 1.2.0 (2013-04-24) + + * Add BinaryDriver\Configuration + +* 1.1.0 (2013-04-24) + + * Add support for timeouts via `setTimeout` method + +* 1.0.0 (2013-04-23) + + * First stable version. diff --git a/vendor/alchemy/binary-driver/LICENSE b/vendor/alchemy/binary-driver/LICENSE new file mode 100644 index 0000000000..e7bb314c25 --- /dev/null +++ b/vendor/alchemy/binary-driver/LICENSE @@ -0,0 +1,21 @@ +BinaryDriver is released with MIT License : + +Copyright (c) 2013 Alchemy + +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/vendor/alchemy/binary-driver/README.md b/vendor/alchemy/binary-driver/README.md new file mode 100644 index 0000000000..05b3a137f0 --- /dev/null +++ b/vendor/alchemy/binary-driver/README.md @@ -0,0 +1,190 @@ +# Binary Driver + +Binary-Driver is a set of PHP tools to build binary drivers. + +[![Build Status](https://travis-ci.org/alchemy-fr/BinaryDriver.png?branch=master)](https://travis-ci.org/alchemy-fr/BinaryDriver) + +## Why ? + +You may wonder *Why building a library while I can use `exec` or +[symfony/process](https://github.com/symfony/Process) ?*. + +Here is a simple answer : + + - If you use `exec`, `passthru`, `system`, `proc_open` or any low level process + handling in PHP, you should have a look to [symfony/process](https://github.com/symfony/Process) + component that will provide an OO portable, testable and secure interface to + deal with this. It seems easy at first approach, but if you look at this + component [unit tests](https://github.com/symfony/Process/tree/master/Tests), + you will see that handling process in a simple interface can easily become a + nightmare. + + - If you already use symfony/process, and want to build binary drivers, you + will always have the same common set of methods and objects to configure, log, + debug, and generate processes. + This library is a base to implement any binary driver with this common set of + needs. + +## AbstractBinary + +`AbstractBinary` provides an abstract class to build a binary driver. It implements +`BinaryInterface`. + +Implementation example : + +```php +use Alchemy\BinaryDriver\AbstractBinary; + +class LsDriver extends AbstractBinary +{ + public function getName() + { + return 'ls driver'; + } +} + +$parser = new LsParser(); + +$driver = Driver::load('ls'); +// will return the output of `ls -a -l` +$parser->parse($driver->command(array('-a', '-l'))); +``` + +### Binary detection troubleshooting + +If you are using Nginx with PHP-fpm, executable detection may not work because of an empty `$_ENV['path']`. +To avoid having an empty `PATH` environment variable, add the following line to your `fastcgi_params` +config file (replace `/your/current/path/` with the output of `printenv PATH`) : + +``` +fastcgi_param PATH /your/current/path +``` + +## Logging + +You can log events with a `Psr\Log\LoggerInterface` by passing it in the load +method as second argument : + +```php +$logger = new Monolog\Logger('driver'); +$driver = Driver::load('ls', $logger); +``` + +## Listeners + +You can add custom listeners on processes. +Listeners are built on top of [Evenement](https://github.com/igorw/evenement) +and must implement `Alchemy\BinaryDriver\ListenerInterface`. + +```php +use Symfony\Component\Process\Process; + +class DebugListener extends EventEmitter implements ListenerInterface +{ + public function handle($type, $data) + { + foreach (explode(PHP_EOL, $data) as $line) { + $this->emit($type === Process::ERR ? 'error' : 'out', array($line)); + } + } + + public function forwardedEvents() + { + // forward 'error' events to the BinaryInterface + return array('error'); + } +} + +$listener = new DebugListener(); + +$driver = CustomImplementation::load('php'); + +// adds listener +$driver->listen($listener); + +$driver->on('error', function ($line) { + echo '[ERROR] ' . $line . PHP_EOL; +}); + +// removes listener +$driver->unlisten($listener); +``` + +### Bundled listeners + +The debug listener is a simple listener to catch `stderr` and `stdout` outputs ; +read the implementation for customization. + +```php +use Alchemy\BinaryDriver\Listeners\DebugListener; + +$driver = CustomImplementation::load('php'); +$driver->listen(new DebugListener()); + +$driver->on('debug', function ($line) { + echo $line; +}); +``` + +## ProcessBuilderFactory + +ProcessBuilderFactory ease spawning processes by generating Symfony [Process] +(http://symfony.com/doc/master/components/process.html) objects. + +```php +use Alchemy\BinaryDriver\ProcessBuilderFactory; + +$factory = new ProcessBuilderFactory('/usr/bin/php'); + +// return a Symfony\Component\Process\Process +$process = $factory->create('-v'); + +// echoes '/usr/bin/php' '-v' +echo $process->getCommandLine(); + +$process = $factory->create(array('-r', 'echo "Hello !";')); + +// echoes '/usr/bin/php' '-r' 'echo "Hello !";' +echo $process->getCommandLine(); +``` + +## Configuration + +A simple configuration object, providing an `ArrayAccess` and `IteratorAggregate` +interface. + +```php +use Alchemy\BinaryDriver\Configuration; + +$conf = new Configuration(array('timeout' => 0)); + +echo $conf->get('timeout'); + +if ($conf->has('param')) { + $conf->remove('param'); +} + +$conf->set('timeout', 20); + +$conf->all(); +``` + +Same example using the `ArrayAccess` interface : + +```php +use Alchemy\BinaryDriver\Configuration; + +$conf = new Configuration(array('timeout' => 0)); + +echo $conf['timeout']; + +if (isset($conf['param'])) { + unset($conf['param']); +} + +$conf['timeout'] = 20; +``` + +## License + +This project is released under the MIT license. diff --git a/vendor/alchemy/binary-driver/composer.json b/vendor/alchemy/binary-driver/composer.json new file mode 100644 index 0000000000..8aa5ce4404 --- /dev/null +++ b/vendor/alchemy/binary-driver/composer.json @@ -0,0 +1,38 @@ +{ + "name": "alchemy/binary-driver", + "type": "library", + "description": "A set of tools to build binary drivers", + "keywords": ["binary", "driver"], + "license": "MIT", + "authors": [ + { + "name": "Nicolas Le Goff", + "email": "legoff.n@gmail.com" + }, + { + "name": "Romain Neutron", + "email": "imprec@gmail.com", + "homepage": "http://www.lickmychip.com/" + }, + { + "name": "Phraseanet Team", + "email": "info@alchemy.fr", + "homepage": "http://www.phraseanet.com/" + } + ], + "require": { + "php" : ">=5.3.3", + "evenement/evenement" : "~1.0", + "monolog/monolog" : "~1.3", + "psr/log" : "~1.0", + "symfony/process" : "~2.0" + }, + "require-dev": { + "phpunit/phpunit" : "~3.7" + }, + "autoload": { + "psr-0": { + "Alchemy": "src" + } + } +} diff --git a/vendor/alchemy/binary-driver/phpunit.xml.dist b/vendor/alchemy/binary-driver/phpunit.xml.dist new file mode 100644 index 0000000000..0c043a9100 --- /dev/null +++ b/vendor/alchemy/binary-driver/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + tests + + + + + vendor + tests + + + + + diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php new file mode 100644 index 0000000000..ee8f8dff15 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException; +use Alchemy\BinaryDriver\Exception\ExecutionFailureException; +use Alchemy\BinaryDriver\Listeners\Listeners; +use Alchemy\BinaryDriver\Listeners\ListenerInterface; +use Evenement\EventEmitter; +use Monolog\Logger; +use Monolog\Handler\NullHandler; +use Psr\Log\LoggerInterface; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; + +abstract class AbstractBinary extends EventEmitter implements BinaryInterface +{ + /** @var ConfigurationInterface */ + protected $configuration; + + /** @var ProcessBuilderFactoryInterface */ + protected $factory; + + /** @var ProcessRunner */ + private $processRunner; + + /** @var Listeners */ + private $listenersManager; + + public function __construct(ProcessBuilderFactoryInterface $factory, LoggerInterface $logger, ConfigurationInterface $configuration) + { + $this->factory = $factory; + $this->configuration = $configuration; + $this->processRunner = new ProcessRunner($logger, $this->getName()); + $this->listenersManager = new Listeners(); + $this->applyProcessConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function listen(ListenerInterface $listener) + { + $this->listenersManager->register($listener, $this); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function unlisten(ListenerInterface $listener) + { + $this->listenersManager->unregister($listener, $this); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return $this->configuration; + } + + /** + * {@inheritdoc} + * + * @return BinaryInterface + */ + public function setConfiguration(ConfigurationInterface $configuration) + { + $this->configuration = $configuration; + $this->applyProcessConfiguration(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProcessBuilderFactory() + { + return $this->factory; + } + + /** + * {@inheritdoc} + * + * @return BinaryInterface + */ + public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory) + { + $this->factory = $factory; + $this->applyProcessConfiguration(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProcessRunner() + { + return $this->processRunner; + } + + /** + * {@inheritdoc} + */ + public function setProcessRunner(ProcessRunnerInterface $runner) + { + $this->processRunner = $runner; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function command($command, $bypassErrors = false, $listeners = null) + { + if (!is_array($command)) { + $command = array($command); + } + + return $this->run($this->factory->create($command), $bypassErrors, $listeners); + } + + /** + * {@inheritdoc} + */ + public static function load($binaries, LoggerInterface $logger = null, $configuration = array()) + { + $finder = new ExecutableFinder(); + $binary = null; + $binaries = is_array($binaries) ? $binaries : array($binaries); + + foreach ($binaries as $candidate) { + if (file_exists($candidate) && is_executable($candidate)) { + $binary = $candidate; + break; + } + if (null !== $binary = $finder->find($candidate)) { + break; + } + } + + if (null === $binary) { + throw new ExecutableNotFoundException(sprintf( + 'Executable not found, proposed : %s', implode(', ', $binaries) + )); + } + + if (null === $logger) { + $logger = new Logger(__NAMESPACE__ . ' logger'); + $logger->pushHandler(new NullHandler()); + } + + $configuration = $configuration instanceof ConfigurationInterface ? $configuration : new Configuration($configuration); + + return new static(new ProcessBuilderFactory($binary), $logger, $configuration); + } + + /** + * Returns the name of the driver + * + * @return string + */ + abstract public function getName(); + + /** + * Executes a process, logs events + * + * @param Process $process + * @param Boolean $bypassErrors Set to true to disable throwing ExecutionFailureExceptions + * @param ListenerInterface|array $listeners A listener or an array of listener to register for this unique run + * + * @return string The Process output + * + * @throws ExecutionFailureException in case of process failure. + */ + protected function run(Process $process, $bypassErrors = false, $listeners = null) + { + if (null !== $listeners) { + if (!is_array($listeners)) { + $listeners = array($listeners); + } + + $listenersManager = clone $this->listenersManager; + + foreach ($listeners as $listener) { + $listenersManager->register($listener, $this); + } + } else { + $listenersManager = $this->listenersManager; + } + + return $this->processRunner->run($process, $listenersManager->storage, $bypassErrors); + } + + private function applyProcessConfiguration() + { + if ($this->configuration->has('timeout')) { + $this->factory->setTimeout($this->configuration->get('timeout')); + } + + return $this; + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php new file mode 100644 index 0000000000..3d0463dc04 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php @@ -0,0 +1,76 @@ +getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface'); + } + + /** + * @param integer $runs The number of runs expected + * @param Boolean $success True if the process expects to be successfull + * @param string $commandLine The commandline executed + * @param string $output The process output + * @param string $error The process error output + * + * @return Process + */ + public function createProcessMock($runs = 1, $success = true, $commandLine = null, $output = null, $error = null, $callback = false) + { + $process = $this->getMockBuilder('Symfony\Component\Process\Process') + ->disableOriginalConstructor() + ->getMock(); + + $builder = $process->expects($this->exactly($runs)) + ->method('run'); + + if (true === $callback) { + $builder->with($this->isInstanceOf('Closure')); + } + + $process->expects($this->any()) + ->method('isSuccessful') + ->will($this->returnValue($success)); + + foreach (array( + 'getOutput' => $output, + 'getErrorOutput' => $error, + 'getCommandLine' => $commandLine, + ) as $command => $value) { + $process + ->expects($this->any()) + ->method($command) + ->will($this->returnValue($value)); + } + + return $process; + } + + /** + * @return LoggerInterface + */ + public function createLoggerMock() + { + return $this->getMock('Psr\Log\LoggerInterface'); + } + + /** + * @return ConfigurationInterface + */ + public function createConfigurationMock() + { + return $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface'); + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php new file mode 100644 index 0000000000..c75cb0c678 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException; +use Alchemy\BinaryDriver\Listeners\ListenerInterface; +use Psr\Log\LoggerInterface; +use Evenement\EventEmitterInterface; + +interface BinaryInterface extends ConfigurationAwareInterface, ProcessBuilderFactoryAwareInterface, ProcessRunnerAwareInterface, EventEmitterInterface +{ + /** + * Adds a listener to the binary driver + * + * @param ListenerInterface $listener + * + * @return BinaryInterface + */ + public function listen(ListenerInterface $listener); + + /** + * Removes a listener from the binary driver + * + * @param ListenerInterface $listener + * + * @return BinaryInterface + */ + public function unlisten(ListenerInterface $listener); + + /** + * Runs a command against the driver. + * + * Calling this method on a `ls` driver with the command `-a` would run `ls -a`. + * + * @param array|string $command A command or an array of command + * @param Boolean $bypassErrors If set to true, an erronous process will not throw an exception + * @param ListenerInterface|array $listeners A listener or an array of listeners to register for this unique run + * + * @return string The command output + * + * @throws ExecutionFailureException in case of process failure. + */ + public function command($command, $bypassErrors = false, $listeners = null); + + /** + * Loads a binary + * + * @param string|array $binaries A binary name or an array of binary names + * @param null||LoggerInterface $logger A Logger + * @param array|ConfigurationInterface $configuration The configuration + * + * @throws ExecutableNotFoundException In case none of the binaries were found + * + * @return BinaryInterface + */ + public static function load($binaries, LoggerInterface $logger = null, $configuration = array()); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php new file mode 100644 index 0000000000..cd0ebbb482 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +class Configuration implements ConfigurationInterface +{ + private $data; + + public function __construct(array $data = array()) + { + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + return isset($this->data[$key]) ? $this->data[$key] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return array_key_exists($key, $this->data); + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + $value = $this->get($key); + unset($this->data[$key]); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php new file mode 100644 index 0000000000..3b14f9e356 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +interface ConfigurationAwareInterface +{ + /** + * Returns the configuration + * + * @return ConfigurationInterface + */ + public function getConfiguration(); + + /** + * Set the configuration + * + * @param ConfigurationInterface $configuration + */ + public function setConfiguration(ConfigurationInterface $configuration); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php new file mode 100644 index 0000000000..71bcb885ac --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +interface ConfigurationInterface extends \ArrayAccess, \IteratorAggregate +{ + /** + * Returns the value given a key from configuration + * + * @param string $key + * @param mixed $default The default value in case the key does not exist + * + * @return mixed + */ + public function get($key, $default = null); + + /** + * Set a value to configuration + * + * @param string $key The key + * @param mixed $value The value corresponding to the key + */ + public function set($key, $value); + + /** + * Tells if Configuration contains `$key` + * + * @param string $key + * + * @return Boolean + */ + public function has($key); + + /** + * Removes a value given a key + * + * @param string $key + * + * @return mixed The previous value + */ + public function remove($key); + + /** + * Returns all values set in the configuration + * + * @return array + */ + public function all(); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..aaa1e321e9 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Exception; + +interface ExceptionInterface +{ +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php new file mode 100644 index 0000000000..6f267ea258 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Exception; + +class ExecutableNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php new file mode 100644 index 0000000000..c25af9751d --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Exception; + +class ExecutionFailureException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..4e9cfe4b3d --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php new file mode 100644 index 0000000000..20773b03b7 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Listeners; + +use Evenement\EventEmitter; +use Symfony\Component\Process\Process; + +class DebugListener extends EventEmitter implements ListenerInterface +{ + private $prefixOut; + private $prefixErr; + private $eventOut; + private $eventErr; + + public function __construct($prefixOut = '[OUT] ', $prefixErr = '[ERROR] ', $eventOut = 'debug', $eventErr = 'debug') + { + $this->prefixOut = $prefixOut; + $this->prefixErr = $prefixErr; + $this->eventOut = $eventOut; + $this->eventErr = $eventErr; + } + + /** + * {@inheritdoc} + */ + public function handle($type, $data) + { + if (Process::ERR === $type) { + $this->emitLines($this->eventErr, $this->prefixErr, $data); + } elseif (Process::OUT === $type) { + $this->emitLines($this->eventOut, $this->prefixOut, $data); + } + } + + /** + * {@inheritdoc} + */ + public function forwardedEvents() + { + return array_unique(array($this->eventErr, $this->eventOut)); + } + + private function emitLines($event, $prefix, $lines) + { + foreach (explode("\n", $lines) as $line) { + $this->emit($event, array($prefix . $line)); + } + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php new file mode 100644 index 0000000000..920a6d5790 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver\Listeners; + +use Evenement\EventEmitterInterface; + +interface ListenerInterface extends EventEmitterInterface +{ + /** + * Handle the output of a ProcessRunner + * + * @param string $type The data type, one of Process::ERR, Process::OUT constants + * @param string $data The output + */ + public function handle($type, $data); + + /** + * An array of events that should be forwarded to BinaryInterface + * + * @return array + */ + public function forwardedEvents(); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php new file mode 100644 index 0000000000..afb7549c02 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php @@ -0,0 +1,88 @@ +storage = new SplObjectStorage(); + } + + public function __clone() + { + $storage = $this->storage; + $this->storage = new SplObjectStorage(); + $this->storage->addAll($storage); + } + + /** + * Registers a listener, pass the listener events to the target. + * + * @param ListenerInterface $listener + * @param null|EventEmitter $target + * + * @return ListenersInterface + */ + public function register(ListenerInterface $listener, EventEmitter $target = null) + { + $EElisteners = array(); + + if (null !== $target) { + $EElisteners = $this->forwardEvents($listener, $target, $listener->forwardedEvents()); + } + + $this->storage->attach($listener, $EElisteners); + + return $this; + } + + /** + * Unregisters a listener, removes the listener events from the target. + * + * @param ListenerInterface $listener + * + * @return ListenersInterface + * + * @throws InvalidArgumentException In case the listener is not registered + */ + public function unregister(ListenerInterface $listener) + { + if (!isset($this->storage[$listener])) { + throw new InvalidArgumentException('Listener is not registered.'); + } + + foreach ($this->storage[$listener] as $event => $EElistener) { + $listener->removeListener($event, $EElistener); + } + + $this->storage->detach($listener); + + return $this; + } + + private function forwardEvents($source, $target, array $events) + { + $EElisteners = array(); + + foreach ($events as $event) { + $listener = $this->createListener($event, $target); + $source->on($event, $EElisteners[$event] = $listener); + } + + return $EElisteners; + } + + private function createListener($event, $target) + { + return function () use ($event, $target) { + $target->emit($event, func_get_args()); + }; + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php new file mode 100644 index 0000000000..9f01eebdaf --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\InvalidArgumentException; +use Symfony\Component\Process\ProcessBuilder; + +class ProcessBuilderFactory implements ProcessBuilderFactoryInterface +{ + /** + * The binary path + * + * @var String + */ + protected $binary; + + /** + * The timeout for the generated processes + * + * @var integer|float + */ + private $timeout; + + /** + * An internal ProcessBuilder. + * + * Note that this one is used only if Symfony ProcessBuilder has method + * setPrefix (2.3) + * + * @var ProcessBuilder + */ + private $builder; + + /** + * Tells whether Symfony LTS ProcessBuilder should be emulated or not. + * + * This symfony version provided a brand new ::setPrefix method. + * + * @var Boolean + */ + public static $emulateSfLTS; + + /** + * Constructor + * + * @param String $binary The path to the binary + * + * @throws InvalidArgumentException In case binary path is invalid + */ + public function __construct($binary) + { + $this->detectEmulation(); + + if (!self::$emulateSfLTS) { + $this->builder = new ProcessBuilder(); + } + + $this->useBinary($binary); + } + + /** + * Covenient method for unit testing + * + * @return type + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * Covenient method for unit testing + * + * @param ProcessBuilder $builder + * @return ProcessBuilderFactory + */ + public function setBuilder(ProcessBuilder $builder) + { + $this->builder = $builder; + + return $this; + } + + /** + * @inheritdoc + */ + public function getBinary() + { + return $this->binary; + } + + /** + * @inheritdoc + */ + public function useBinary($binary) + { + if (!is_executable($binary)) { + throw new InvalidArgumentException(sprintf('`%s` is not an executable binary', $binary)); + } + + $this->binary = $binary; + + if (!static::$emulateSfLTS) { + $this->builder->setPrefix($binary); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + + if (!static::$emulateSfLTS) { + $this->builder->setTimeout($this->timeout); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * @inheritdoc + */ + public function create($arguments = array()) + { + if (null === $this->binary) { + throw new InvalidArgumentException('No binary set'); + } + + if (!is_array($arguments)) { + $arguments = array($arguments); + } + + if (static::$emulateSfLTS) { + array_unshift($arguments, $this->binary); + + return ProcessBuilder::create($arguments) + ->setTimeout($this->timeout) + ->getProcess(); + } else { + return $this->builder + ->setArguments($arguments) + ->getProcess(); + } + } + + private function detectEmulation() + { + if (null !== static::$emulateSfLTS) { + return $this; + } + + static::$emulateSfLTS = !method_exists('Symfony\Component\Process\ProcessBuilder', 'setPrefix'); + + return $this; + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php new file mode 100644 index 0000000000..1398bb4e6e --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +interface ProcessBuilderFactoryAwareInterface +{ + /** + * Returns the current process builder factory + * + * @return ProcessBuilderFactoryInterface + */ + public function getProcessBuilderFactory(); + + /** + * Set a process builder factory + * + * @param ProcessBuilderFactoryInterface $factory + */ + public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php new file mode 100644 index 0000000000..05a22960f2 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\InvalidArgumentException; +use Symfony\Component\Process\Process; + +interface ProcessBuilderFactoryInterface +{ + /** + * Returns a new instance of Symfony Process + * + * @param string|array $arguments An argument or an array of arguments + * + * @return Process + * + * @throws InvalidArgumentException + */ + public function create($arguments = array()); + + /** + * Returns the path to the binary that is used + * + * @return String + */ + public function getBinary(); + + /** + * Sets the path to the binary + * + * @param String $binary A path to a binary + * + * @return ProcessBuilderFactoryInterface + * + * @throws InvalidArgumentException In case binary is not executable + */ + public function useBinary($binary); + + /** + * Set the default timeout to apply on created processes. + * + * @param integer|float $timeout + * + * @return ProcessBuilderFactoryInterface + * + * @throws InvalidArgumentException In case the timeout is not valid + */ + public function setTimeout($timeout); + + /** + * Returns the current timeout applied to the created processes. + * + * @return integer|float + */ + public function getTimeout(); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php new file mode 100644 index 0000000000..2ebbcc03ef --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\ExecutionFailureException; +use Psr\Log\LoggerInterface; +use SplObjectStorage; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +class ProcessRunner implements ProcessRunnerInterface +{ + /** @var LoggerInterface */ + private $logger; + + /** @var string */ + private $name; + + public function __construct(LoggerInterface $logger, $name) + { + $this->logger = $logger; + $this->name = $name; + } + + /** + * {@inheritdoc} + * + * @return ProcessRunner + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * @return LoggerInterface + */ + public function getLogger() + { + return $this->logger; + } + + /** + * {@inheritdoc} + */ + public function run(Process $process, SplObjectStorage $listeners, $bypassErrors) + { + $this->logger->info(sprintf( + '%s running command %s', $this->name, $process->getCommandLine() + )); + + try { + $process->run($this->buildCallback($listeners)); + } catch (RuntimeException $e) { + if (!$bypassErrors) { + $this->doExecutionFailure($process->getCommandLine(), $e); + } + } + + if (!$bypassErrors && !$process->isSuccessful()) { + $this->doExecutionFailure($process->getCommandLine()); + } elseif (!$process->isSuccessful()) { + $this->logger->error(sprintf( + '%s failed to execute command %s', $this->name, $process->getCommandLine() + )); + + return; + } else { + $this->logger->info(sprintf('%s executed command successfully', $this->name)); + + return $process->getOutput(); + } + } + + private function buildCallback(SplObjectStorage $listeners) + { + return function ($type, $data) use ($listeners) { + foreach ($listeners as $listener) { + $listener->handle($type, $data); + } + }; + } + + private function doExecutionFailure($command, \Exception $e = null) + { + $this->logger->error(sprintf( + '%s failed to execute command %s', $this->name, $command + )); + throw new ExecutionFailureException(sprintf( + '%s failed to execute command %s', $this->name, $command + ), $e ? $e->getCode() : null, $e ?: null); + } +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php new file mode 100644 index 0000000000..807c33e7aa --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +interface ProcessRunnerAwareInterface +{ + /** + * Returns the current process runner + * + * @return ProcessRunnerInterface + */ + public function getProcessRunner(); + + /** + * Sets a process runner + * + * @param ProcessRunnerInterface $runner + */ + public function setProcessRunner(ProcessRunnerInterface $runner); +} diff --git a/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php new file mode 100644 index 0000000000..6605404453 --- /dev/null +++ b/vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\BinaryDriver; + +use Alchemy\BinaryDriver\Exception\ExecutionFailureException; +use Psr\Log\LoggerAwareInterface; +use SplObjectStorage; +use Symfony\Component\Process\Process; + +interface ProcessRunnerInterface extends LoggerAwareInterface +{ + /** + * Executes a process, logs events + * + * @param Process $process + * @param SplObjectStorage $listeners Some listeners + * @param Boolean $bypassErrors Set to true to disable throwing ExecutionFailureExceptions + * + * @return string The Process output + * + * @throws ExecutionFailureException in case of process failure. + */ + public function run(Process $process, SplObjectStorage $listeners, $bypassErrors); +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php new file mode 100644 index 0000000000..dcbd2ae55c --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php @@ -0,0 +1,297 @@ +find('php'); + + if (null === $php) { + $this->markTestSkipped('Unable to find a php binary'); + } + + return $php; + } + + public function testSimpleLoadWithBinaryPath() + { + $php = $this->getPhpBinary(); + $imp = Implementation::load($php); + $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp); + + $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary()); + } + + public function testMultipleLoadWithBinaryPath() + { + $php = $this->getPhpBinary(); + $imp = Implementation::load(array('/zz/path/to/unexisting/command', $php)); + $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp); + + $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary()); + } + + public function testSimpleLoadWithBinaryName() + { + $php = $this->getPhpBinary(); + $imp = Implementation::load('php'); + $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp); + + $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary()); + } + + public function testMultipleLoadWithBinaryName() + { + $php = $this->getPhpBinary(); + $imp = Implementation::load(array('bachibouzouk', 'php')); + $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp); + + $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary()); + } + + /** + * @expectedException Alchemy\BinaryDriver\Exception\ExecutableNotFoundException + */ + public function testLoadWithMultiplePathExpectingAFailure() + { + Implementation::load(array('bachibouzouk', 'moribon')); + } + + /** + * @expectedException Alchemy\BinaryDriver\Exception\ExecutableNotFoundException + */ + public function testLoadWithUniquePathExpectingAFailure() + { + Implementation::load('bachibouzouk'); + } + + public function testLoadWithCustomLogger() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $imp = Implementation::load('php', $logger); + + $this->assertEquals($logger, $imp->getProcessRunner()->getLogger()); + } + + public function testLoadWithCustomConfigurationAsArray() + { + $conf = array('timeout' => 200); + $imp = Implementation::load('php', null, $conf); + + $this->assertEquals($conf, $imp->getConfiguration()->all()); + } + + public function testLoadWithCustomConfigurationAsObject() + { + $conf = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface'); + $imp = Implementation::load('php', null, $conf); + + $this->assertEquals($conf, $imp->getConfiguration()); + } + + public function testProcessBuilderFactoryGetterAndSetters() + { + $imp = Implementation::load('php'); + $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface'); + + $imp->setProcessBuilderFactory($factory); + $this->assertEquals($factory, $imp->getProcessBuilderFactory()); + } + + public function testConfigurationGetterAndSetters() + { + $imp = Implementation::load('php'); + $conf = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface'); + + $imp->setConfiguration($conf); + $this->assertEquals($conf, $imp->getConfiguration()); + } + + public function testTimeoutIsSetOnConstruction() + { + $imp = Implementation::load('php', null, array('timeout' => 42)); + $this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout()); + } + + public function testTimeoutIsSetOnConfigurationSetting() + { + $imp = Implementation::load('php', null); + $imp->setConfiguration(new Configuration(array('timeout' => 42))); + $this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout()); + } + + public function testTimeoutIsSetOnProcessBuilderSetting() + { + $imp = Implementation::load('php', null, array('timeout' => 42)); + + $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface'); + $factory->expects($this->once()) + ->method('setTimeout') + ->with(42); + + $imp->setProcessBuilderFactory($factory); + } + + public function testListenRegistersAListener() + { + $imp = Implementation::load('php'); + + $listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners') + ->disableOriginalConstructor() + ->getMock(); + + $listener = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'); + + $listeners->expects($this->once()) + ->method('register') + ->with($this->equalTo($listener), $this->equalTo($imp)); + + $reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary'); + $prop = $reflexion->getProperty('listenersManager'); + $prop->setAccessible(true); + $prop->setValue($imp, $listeners); + + $imp->listen($listener); + } + + /** + * @dataProvider provideCommandParameters + */ + public function testCommandRunsAProcess($parameters, $bypassErrors, $expectedParameters, $output) + { + $imp = Implementation::load('php'); + $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface'); + $processRunner = $this->getMock('Alchemy\BinaryDriver\ProcessRunnerInterface'); + + $process = $this->getMockBuilder('Symfony\Component\Process\Process') + ->disableOriginalConstructor() + ->getMock(); + + $processRunner->expects($this->once()) + ->method('run') + ->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors)) + ->will($this->returnValue($output)); + + $factory->expects($this->once()) + ->method('create') + ->with($expectedParameters) + ->will($this->returnValue($process)); + + $imp->setProcessBuilderFactory($factory); + $imp->setProcessRunner($processRunner); + + $this->assertEquals($output, $imp->command($parameters, $bypassErrors)); + } + + /** + * @dataProvider provideCommandWithListenersParameters + */ + public function testCommandWithTemporaryListeners($parameters, $bypassErrors, $expectedParameters, $output, $count, $listeners) + { + $imp = Implementation::load('php'); + $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface'); + $processRunner = $this->getMock('Alchemy\BinaryDriver\ProcessRunnerInterface'); + + $process = $this->getMockBuilder('Symfony\Component\Process\Process') + ->disableOriginalConstructor() + ->getMock(); + + $firstStorage = $secondStorage = null; + + $processRunner->expects($this->exactly(2)) + ->method('run') + ->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors)) + ->will($this->returnCallback(function ($process, $storage, $errors) use ($output, &$firstStorage, &$secondStorage) { + if (null === $firstStorage) { + $firstStorage = $storage; + } else { + $secondStorage = $storage; + } + + return $output; + })); + + $factory->expects($this->exactly(2)) + ->method('create') + ->with($expectedParameters) + ->will($this->returnValue($process)); + + $imp->setProcessBuilderFactory($factory); + $imp->setProcessRunner($processRunner); + + $this->assertEquals($output, $imp->command($parameters, $bypassErrors, $listeners)); + $this->assertCount($count, $firstStorage); + $this->assertEquals($output, $imp->command($parameters, $bypassErrors)); + $this->assertCount(0, $secondStorage); + } + + public function provideCommandWithListenersParameters() + { + $listener = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'); + $listener->expects($this->any()) + ->method('forwardedEvents') + ->will($this->returnValue(array())); + + $listener2 = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'); + $listener2->expects($this->any()) + ->method('forwardedEvents') + ->will($this->returnValue(array())); + + return array( + array('-a', false, array('-a'), 'loubda', 2, array($listener, $listener2)), + array('-a', false, array('-a'), 'loubda', 1, array($listener)), + array('-a', false, array('-a'), 'loubda', 1, $listener), + array('-a', false, array('-a'), 'loubda', 0, array()), + ); + } + + public function provideCommandParameters() + { + return array( + array('-a', false, array('-a'), 'loubda'), + array('-a', true, array('-a'), 'loubda'), + array('-a -b', false, array('-a -b'), 'loubda'), + array(array('-a'), false, array('-a'), 'loubda'), + array(array('-a'), true, array('-a'), 'loubda'), + array(array('-a', '-b'), false, array('-a', '-b'), 'loubda'), + ); + } + + public function testUnlistenUnregistersAListener() + { + $imp = Implementation::load('php'); + + $listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners') + ->disableOriginalConstructor() + ->getMock(); + + $listener = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'); + + $listeners->expects($this->once()) + ->method('unregister') + ->with($this->equalTo($listener), $this->equalTo($imp)); + + $reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary'); + $prop = $reflexion->getProperty('listenersManager'); + $prop->setAccessible(true); + $prop->setValue($imp, $listeners); + + $imp->unlisten($listener); + } +} + +class Implementation extends AbstractBinary +{ + public function getName() + { + return 'Implementation'; + } +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php new file mode 100644 index 0000000000..de0b61ce22 --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php @@ -0,0 +1,97 @@ +markTestSkipped('Unable to detect php binary, skipping'); + } + } + + public static function setUpBeforeClass() + { + $finder = new ExecutableFinder(); + static::$phpBinary = $finder->find('php'); + } + + public function testThatBinaryIsSetOnConstruction() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $this->assertEquals(static::$phpBinary, $factory->getBinary()); + } + + public function testGetSetBinary() + { + $finder = new ExecutableFinder(); + $phpUnit = $finder->find('phpunit'); + + if (null === $phpUnit) { + $this->markTestSkipped('Unable to detect phpunit binary, skipping'); + } + + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $factory->useBinary($phpUnit); + $this->assertEquals($phpUnit, $factory->getBinary()); + } + + /** + * @expectedException Alchemy\BinaryDriver\Exception\InvalidArgumentException + */ + public function testUseNonExistantBinary() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $factory->useBinary('itissureitdoesnotexist'); + } + + public function testCreateShouldReturnAProcess() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $process = $factory->create(); + + $this->assertInstanceOf('Symfony\Component\Process\Process', $process); + $this->assertEquals("'".static::$phpBinary."'", $process->getCommandLine()); + } + + public function testCreateWithStringArgument() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $process = $factory->create('-v'); + + $this->assertInstanceOf('Symfony\Component\Process\Process', $process); + $this->assertEquals("'".static::$phpBinary."' '-v'", $process->getCommandLine()); + } + + public function testCreateWithArrayArgument() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $process = $factory->create(array('-r', 'echo "Hello !";')); + + $this->assertInstanceOf('Symfony\Component\Process\Process', $process); + $this->assertEquals("'".static::$phpBinary."' '-r' 'echo \"Hello !\";'", $process->getCommandLine()); + } + + public function testCreateWithTimeout() + { + $factory = $this->getProcessBuilderFactory(static::$phpBinary); + $factory->setTimeout(200); + $process = $factory->create(array('-i')); + + $this->assertInstanceOf('Symfony\Component\Process\Process', $process); + $this->assertEquals(200, $process->getTimeout()); + } +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php new file mode 100644 index 0000000000..b2cd2ddd49 --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php @@ -0,0 +1,78 @@ + 'value')); + + $this->assertTrue(isset($configuration['key'])); + $this->assertEquals('value', $configuration['key']); + + $this->assertFalse(isset($configuration['key2'])); + unset($configuration['key']); + $this->assertFalse(isset($configuration['key'])); + + $configuration['key2'] = 'value2'; + $this->assertTrue(isset($configuration['key2'])); + $this->assertEquals('value2', $configuration['key2']); + } + + public function testGetOnNonExistentKeyShouldReturnDefaultValue() + { + $conf = new Configuration(); + $this->assertEquals('booba', $conf->get('hooba', 'booba')); + $this->assertEquals(null, $conf->get('hooba')); + } + + public function testSetHasGetRemove() + { + $configuration = new Configuration(array('key' => 'value')); + + $this->assertTrue($configuration->has('key')); + $this->assertEquals('value', $configuration->get('key')); + + $this->assertFalse($configuration->has('key2')); + $configuration->remove('key'); + $this->assertFalse($configuration->has('key')); + + $configuration->set('key2', 'value2'); + $this->assertTrue($configuration->has('key2')); + $this->assertEquals('value2', $configuration->get('key2')); + } + + public function testIterator() + { + $data = array( + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ); + + $captured = array(); + $conf = new Configuration($data); + + foreach ($conf as $key => $value) { + $captured[$key] = $value; + } + + $this->assertEquals($data, $captured); + } + + public function testAll() + { + $data = array( + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ); + + $conf = new Configuration($data); + $this->assertEquals($data, $conf->all()); + } + +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php new file mode 100644 index 0000000000..6875f0765a --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php @@ -0,0 +1,66 @@ +setBuilder(new LTSProcessBuilder()); + ProcessBuilderFactory::$emulateSfLTS = false; + $factory->useBinary($binary); + + return $factory; + } +} + +class LTSProcessBuilder extends ProcessBuilder +{ + private $arguments; + private $prefix; + private $timeout; + + public function __construct(array $arguments = array()) + { + $this->arguments = $arguments; + parent::__construct($arguments); + } + + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + public function setPrefix($prefix) + { + $this->prefix = $prefix; + + return $this; + } + + public function setTimeout($timeout) + { + $this->timeout = $timeout; + + return $this; + } + + public function getProcess() + { + if (!$this->prefix && !count($this->arguments)) { + throw new LogicException('You must add() command arguments before calling getProcess().'); + } + + $args = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments; + $script = implode(' ', array_map('escapeshellarg', $args)); + + return new Process($script, null, null, null, $this->timeout); + } +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php new file mode 100644 index 0000000000..69958677da --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php @@ -0,0 +1,33 @@ +on('debug', function ($line) use (&$lines) { + $lines[] = $line; + }); + $listener->handle(Process::ERR, "first line\nsecond line"); + $listener->handle(Process::OUT, "cool output"); + $listener->handle('unknown', "lalala"); + $listener->handle(Process::OUT, "another output\n"); + + $expected = array( + '[ERROR] first line', + '[ERROR] second line', + '[OUT] cool output', + '[OUT] another output', + '[OUT] ', + ); + + $this->assertEquals($expected, $lines); + } +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php new file mode 100644 index 0000000000..2ca21a2abf --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php @@ -0,0 +1,92 @@ +register($listener); + + $n = 0; + $listener->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) { + $n++; + $capturedData = $data; + $capturedType = $type; + }); + + $type = 'type'; + $data = 'data'; + + $listener->handle($type, $data); + $listener->handle($type, $data); + + $listeners->unregister($listener); + + $listener->handle($type, $data); + + $this->assertEquals(3, $n); + $this->assertEquals($type, $capturedType); + $this->assertEquals($data, $capturedData); + } + + public function testRegisterAndForwardThenUnregister() + { + $listener = new TestListener(); + $target = new EventEmitter(); + + $n = 0; + $target->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) { + $n++; + $capturedData = $data; + $capturedType = $type; + }); + + $m = 0; + $listener->on('received', function ($type, $data) use (&$m, &$capturedType2, &$capturedData2) { + $m++; + $capturedData2 = $data; + $capturedType2 = $type; + }); + + $listeners = new Listeners(); + $listeners->register($listener, $target); + + $type = 'type'; + $data = 'data'; + + $listener->handle($type, $data); + $listener->handle($type, $data); + + $listeners->unregister($listener, $target); + + $listener->handle($type, $data); + + $this->assertEquals(2, $n); + $this->assertEquals(3, $m); + $this->assertEquals($type, $capturedType); + $this->assertEquals($data, $capturedData); + $this->assertEquals($type, $capturedType2); + $this->assertEquals($data, $capturedData2); + } +} + +class TestListener extends EventEmitter implements ListenerInterface +{ + public function handle($type, $data) + { + $this->emit('received', array($type, $data)); + } + + public function forwardedEvents() + { + return array('received'); + } +} diff --git a/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php new file mode 100644 index 0000000000..fe5f343bd2 --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Alchemy\Tests\BinaryDriver; + +use Alchemy\BinaryDriver\ProcessRunner; +use Alchemy\BinaryDriver\BinaryDriverTestCase; +use Alchemy\BinaryDriver\Exception\ExecutionFailureException; +use Alchemy\BinaryDriver\Listeners\ListenerInterface; +use Evenement\EventEmitter; +use Symfony\Component\Process\Exception\RuntimeException as ProcessRuntimeException; + +class ProcessRunnerTest extends BinaryDriverTestCase +{ + public function getProcessRunner($logger) + { + return new ProcessRunner($logger, 'test-runner'); + } + + public function testRunSuccessFullProcess() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true); + + $logger + ->expects($this->never()) + ->method('error'); + $logger + ->expects($this->exactly(2)) + ->method('info'); + + $this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), false)); + } + + public function testRunSuccessFullProcessBypassingErrors() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true); + + $logger + ->expects($this->never()) + ->method('error'); + $logger + ->expects($this->exactly(2)) + ->method('info'); + + $this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), true)); + } + + public function testRunFailingProcess() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $process = $this->createProcessMock(1, false, '--helloworld--', null, null, true); + + $logger + ->expects($this->once()) + ->method('error'); + $logger + ->expects($this->once()) + ->method('info'); + + try { + $runner->run($process, new \SplObjectStorage(), false); + $this->fail('An exception should have been raised'); + } catch (ExecutionFailureException $e) { + + } + } + + public function testRunFailingProcessWithException() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $exception = new ProcessRuntimeException('Process Failed'); + $process = $this->getMockBuilder('Symfony\Component\Process\Process') + ->disableOriginalConstructor() + ->getMock(); + $process->expects($this->once()) + ->method('run') + ->will($this->throwException($exception)); + + $logger + ->expects($this->once()) + ->method('error'); + $logger + ->expects($this->once()) + ->method('info'); + + try { + $runner->run($process, new \SplObjectStorage(), false); + $this->fail('An exception should have been raised'); + } catch (ExecutionFailureException $e) { + $this->assertEquals($exception, $e->getPrevious()); + } + } + + public function testRunfailingProcessBypassingErrors() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $process = $this->createProcessMock(1, false, '--helloworld--', 'Hello output', null, true); + + $logger + ->expects($this->once()) + ->method('error'); + $logger + ->expects($this->once()) + ->method('info'); + + $this->assertNull($runner->run($process, new \SplObjectStorage(), true)); + } + + public function testRunFailingProcessWithExceptionBypassingErrors() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $exception = new ProcessRuntimeException('Process Failed'); + $process = $this->getMockBuilder('Symfony\Component\Process\Process') + ->disableOriginalConstructor() + ->getMock(); + $process->expects($this->once()) + ->method('run') + ->will($this->throwException($exception)); + + $logger + ->expects($this->once()) + ->method('error'); + $logger + ->expects($this->once()) + ->method('info'); + + $this->assertNull($runner->run($process, new \SplObjectStorage(), true)); + } + + public function testRunSuccessFullProcessWithHandlers() + { + $logger = $this->createLoggerMock(); + $runner = $this->getProcessRunner($logger); + + $capturedCallback = null; + + $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true); + $process->expects($this->once()) + ->method('run') + ->with($this->isInstanceOf('Closure')) + ->will($this->returnCallback(function ($callback) use (&$capturedCallback) { + $capturedCallback = $callback; + })); + + $logger + ->expects($this->never()) + ->method('error'); + $logger + ->expects($this->exactly(2)) + ->method('info'); + + $listener = new TestListener(); + $storage = new \SplObjectStorage(); + $storage->attach($listener); + + $capturedType = $capturedData = null; + + $listener->on('received', function ($type, $data) use (&$capturedType, &$capturedData) { + $capturedData = $data; + $capturedType = $type; + }); + + $this->assertEquals('Kikoo Romain', $runner->run($process, $storage, false)); + + $type = 'err'; + $data = 'data'; + + $capturedCallback($type, $data); + + $this->assertEquals($data, $capturedData); + $this->assertEquals($type, $capturedType); + } +} + +class TestListener extends EventEmitter implements ListenerInterface +{ + public function handle($type, $data) + { + return $this->emit('received', array($type, $data)); + } + + public function forwardedEvents() + { + return array(); + } +} diff --git a/vendor/alchemy/binary-driver/tests/bootstrap.php b/vendor/alchemy/binary-driver/tests/bootstrap.php new file mode 100644 index 0000000000..b5e2aed9bb --- /dev/null +++ b/vendor/alchemy/binary-driver/tests/bootstrap.php @@ -0,0 +1,4 @@ +add('Alchemy\Tests', __DIR__); diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000000..8d31ac85a1 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + private $prefixes = array(); + private $fallbackDirs = array(); + private $useIncludePath = false; + private $classMap = array(); + + public function getPrefixes() + { + return call_user_func_array('array_merge', $this->prefixes); + } + + public function getFallbackDirs() + { + return $this->fallbackDirs; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of classes, merging with any others previously set. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + * @param bool $prepend Prepend the location(s) + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirs = array_merge( + (array) $paths, + $this->fallbackDirs + ); + } else { + $this->fallbackDirs = array_merge( + $this->fallbackDirs, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixes[$first][$prefix])) { + $this->prefixes[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixes[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixes[$first][$prefix] + ); + } else { + $this->prefixes[$first][$prefix] = array_merge( + $this->prefixes[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of classes, replacing any others previously set. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirs = (array) $paths; + + return; + } + $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths; + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + include $file; + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $className = substr($class, $pos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset($this->prefixes[$first])) { + foreach ($this->prefixes[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + } + } + } + + foreach ($this->fallbackDirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + return $file; + } + + return $this->classMap[$class] = false; + } +} diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000..af4ad5805a --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/process'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log'), + 'Monolog' => array($vendorDir . '/monolog/monolog/src'), + 'FFMpeg' => array($vendorDir . '/php-ffmpeg/php-ffmpeg/src'), + 'Evenement' => array($vendorDir . '/evenement/evenement/src'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib'), + 'Alchemy' => array($vendorDir . '/alchemy/binary-driver/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000000..26e208676d --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,43 @@ + $path) { + $loader->set($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000000..98b72a37d6 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,397 @@ +[ + { + "name": "evenement/evenement", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/fa966683e7df3e5dd5929d984a44abfbd6bafe8d", + "reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2012-05-30 15:01:08", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch", + "homepage": "http://wiedler.ch/igor/" + } + ], + "description": "Événement is a very simple event dispatching library for PHP 5.3", + "keywords": [ + "event-dispatcher" + ] + }, + { + "name": "doctrine/cache", + "version": "v1.1", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "2c9761ff1d13e188d5f7378066c1ce2882d7a336" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/2c9761ff1d13e188d5f7378066c1ce2882d7a336", + "reference": "2c9761ff1d13e188d5f7378066c1ce2882d7a336", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "time": "2013-08-07 16:04:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ] + }, + { + "name": "psr/log", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "time": "2012-12-21 11:40:51", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "symfony/process", + "version": "v2.3.4", + "version_normalized": "2.3.4.0", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b", + "reference": "1e91553e1cedd0b8fb1da6ea4f89b02e21713d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-08-22 06:42:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com" + }, + { + "name": "monolog/monolog", + "version": "1.6.0", + "version_normalized": "1.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "f72392d0e6eb855118f5a84e89ac2d257c704abd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f72392d0e6eb855118f5a84e89ac2d257c704abd", + "reference": "f72392d0e6eb855118f5a84e89ac2d257c704abd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "doctrine/couchdb": "dev-master", + "mlehner/gelf-php": "1.0.*", + "raven/raven": "0.5.*" + }, + "suggest": { + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server" + }, + "time": "2013-07-28 22:38:30", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Monolog": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be", + "role": "Developer" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "alchemy/binary-driver", + "version": "1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/alchemy-fr/BinaryDriver.git", + "reference": "b32c03d4b56ce29f783051eac55887adae654b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alchemy-fr/BinaryDriver/zipball/b32c03d4b56ce29f783051eac55887adae654b41", + "reference": "b32c03d4b56ce29f783051eac55887adae654b41", + "shasum": "" + }, + "require": { + "evenement/evenement": "~1.0", + "monolog/monolog": "~1.3", + "php": ">=5.3.3", + "psr/log": "~1.0", + "symfony/process": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "time": "2013-06-21 15:51:20", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Alchemy": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Neutron", + "email": "imprec@gmail.com", + "homepage": "http://www.lickmychip.com/" + }, + { + "name": "Nicolas Le Goff", + "email": "legoff.n@gmail.com" + }, + { + "name": "Phraseanet Team", + "email": "info@alchemy.fr", + "homepage": "http://www.phraseanet.com/" + } + ], + "description": "A set of tools to build binary drivers", + "keywords": [ + "binary", + "driver" + ] + }, + { + "name": "php-ffmpeg/php-ffmpeg", + "version": "0.3.x-dev", + "version_normalized": "0.3.9999999.9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/alchemy-fr/PHP-FFmpeg.git", + "reference": "9fcb485d497872e674cb14eb3df1386dbda9169b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alchemy-fr/PHP-FFmpeg/zipball/9fcb485d497872e674cb14eb3df1386dbda9169b", + "reference": "9fcb485d497872e674cb14eb3df1386dbda9169b", + "shasum": "" + }, + "require": { + "alchemy/binary-driver": "~1.5", + "doctrine/cache": "~1.0", + "evenement/evenement": "~1.0", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7", + "sami/sami": "~1.0", + "silex/silex": "~1.0" + }, + "suggest": { + "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" + }, + "time": "2013-08-08 10:15:15", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-0": { + "FFMpeg": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Neutron", + "email": "imprec@gmail.com", + "homepage": "http://www.lickmychip.com/" + }, + { + "name": "Phraseanet Team", + "email": "info@alchemy.fr", + "homepage": "http://www.phraseanet.com/" + } + ], + "description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg", + "keywords": [ + "audio", + "audio processing", + "avconv", + "avprobe", + "ffmpeg", + "ffprobe", + "video", + "video processing" + ] + } +] diff --git a/vendor/doctrine/cache/.travis.yml b/vendor/doctrine/cache/.travis.yml new file mode 100644 index 0000000000..ecc35406d2 --- /dev/null +++ b/vendor/doctrine/cache/.travis.yml @@ -0,0 +1,9 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + +before_script: + - composer --prefer-source --dev install diff --git a/vendor/doctrine/cache/LICENSE b/vendor/doctrine/cache/LICENSE new file mode 100644 index 0000000000..4a91f0bf28 --- /dev/null +++ b/vendor/doctrine/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2012 Doctrine Project + +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/vendor/doctrine/cache/README.md b/vendor/doctrine/cache/README.md new file mode 100644 index 0000000000..d315234f3d --- /dev/null +++ b/vendor/doctrine/cache/README.md @@ -0,0 +1,9 @@ +# Doctrine Cache + +Cache component extracted from the Doctrine Common project. + +## Changelog + +### v1.1 + +* Added support for MongoDB as Cache Provider diff --git a/vendor/doctrine/cache/composer.json b/vendor/doctrine/cache/composer.json new file mode 100644 index 0000000000..380540ca36 --- /dev/null +++ b/vendor/doctrine/cache/composer.json @@ -0,0 +1,29 @@ +{ + "name": "doctrine/cache", + "type": "library", + "description": "Caching library offering an object-oriented API for many cache backends", + "keywords": ["cache", "caching"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Cache\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php new file mode 100644 index 0000000000..680440ba1c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APC cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ApcCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apc_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return (bool) apc_store($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return apc_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apc_clear_cache() && apc_clear_cache('user'); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apc_cache_info(); + $sma = apc_sma_info(); + + return array( + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php new file mode 100644 index 0000000000..e9f08a24bb --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php @@ -0,0 +1,93 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Array cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ArrayCache extends CacheProvider +{ + /** + * @var array $data + */ + private $data = array(); + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return (isset($this->data[$id])) ? $this->data[$id] : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return isset($this->data[$id]); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->data[$id] = $data; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + unset($this->data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->data = array(); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 0000000000..0785f263b7 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + */ +interface Cache +{ + const STATS_HITS = 'hits'; + const STATS_MISSES = 'misses'; + const STATS_UPTIME = 'uptime'; + const STATS_MEMORY_USAGE = 'memory_usage'; + const STATS_MEMORY_AVAILABLE = 'memory_available'; + /** + * Only for backward compatibility (may be removed in next major release) + * + * @deprecated + */ + const STATS_MEMORY_AVAILIABLE = 'memory_available'; + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed The cached data or FALSE, if no cache entry exists for the given id. + */ + function fetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + function contains($id); + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param mixed $data The cache entry/data. + * @param int $lifeTime The cache lifetime. + * If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime). + * + * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + function save($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + function delete($id); + + /** + * Retrieves cached information from the data store. + * + * The server's statistics array has the following values: + * + * - hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + function getStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 0000000000..45d9acbc89 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,240 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base class for cache provider implementations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + */ +abstract class CacheProvider implements Cache +{ + const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; + + /** + * The namespace to prefix all cache ids with. + * + * @var string + */ + private $namespace = ''; + + /** + * The namespace version. + * + * @var string + */ + private $namespaceVersion; + + /** + * Sets the namespace to prefix all cache ids with. + * + * @param string $namespace + * + * @return void + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * Flushes all cache entries. + * + * @return boolean TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * Deletes all cache entries. + * + * @return boolean TRUE if the cache entries were successfully deleted, FALSE otherwise. + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + $this->namespaceVersion = $namespaceVersion; + + return $this->doSave($namespaceCacheKey, $namespaceVersion); + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId($id) + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + * + * @return string + */ + private function getNamespaceCacheKey() + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + * + * @return string + */ + private function getNamespaceVersion() + { + if (null !== $this->namespaceVersion) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->doFetch($namespaceCacheKey); + + if (false === $namespaceVersion) { + $namespaceVersion = 1; + + $this->doSave($namespaceCacheKey, $namespaceVersion); + } + + $this->namespaceVersion = $namespaceVersion; + + return $this->namespaceVersion; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return string|bool The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + abstract protected function doContains($id); + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php new file mode 100644 index 0000000000..c21691df96 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Couchbase; + +/** + * Couchbase cache provider. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Michael Nitschinger + */ +class CouchbaseCache extends CacheProvider +{ + /** + * @var Couchbase|null + */ + private $couchbase; + + /** + * Sets the Couchbase instance to use. + * + * @param Couchbase $couchbase + * + * @return void + */ + public function setCouchbase(Couchbase $couchbase) + { + $this->couchbase = $couchbase; + } + + /** + * Gets the Couchbase instance used by the cache. + * + * @return Couchbase|null + */ + public function getCouchbase() + { + return $this->couchbase; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->couchbase->get($id) ?: false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (null !== $this->couchbase->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->couchbase->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->couchbase->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->couchbase->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->couchbase->getStats(); + $servers = $this->couchbase->getServers(); + $server = explode(":", $servers[0]); + $key = $server[0] . ":" . "11210"; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php new file mode 100644 index 0000000000..1aa4d79118 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @@ -0,0 +1,158 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +abstract class FileCache extends CacheProvider +{ + /** + * The cache directory. + * + * @var string + */ + protected $directory; + + /** + * The cache file extension. + * + * @var string|null + */ + protected $extension; + + /** + * Constructor. + * + * @param string $directory The cache directory. + * @param string|null $extension The cache file extension. + * + * @throws \InvalidArgumentException + */ + public function __construct($directory, $extension = null) + { + if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $directory + )); + } + + if ( ! is_writable($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" is not writable.', + $directory + )); + } + + $this->directory = realpath($directory); + $this->extension = $extension ?: $this->extension; + } + + /** + * Gets the cache directory. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the cache file extension. + * + * @return string|null + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param string $id + * + * @return string + */ + protected function getFilename($id) + { + $hash = hash('sha256', $id); + $path = implode(str_split($hash, 16), DIRECTORY_SEPARATOR); + $path = $this->directory . DIRECTORY_SEPARATOR . $path; + $id = preg_replace('@[\\\/:"*?<>|]+@', '', $id); + + return $path . DIRECTORY_SEPARATOR . $id . $this->extension; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return @unlink($this->getFilename($id)); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + foreach ($this->getIterator() as $name => $file) { + @unlink($name); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $usage = 0; + foreach ($this->getIterator() as $name => $file) { + $usage += $file->getSize(); + } + + $free = disk_free_space($this->directory); + + return array( + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $usage, + Cache::STATS_MEMORY_AVAILABLE => $free, + ); + } + + /** + * @return \Iterator + */ + private function getIterator() + { + $pattern = '/^.+\\' . $this->extension . '$/i'; + $iterator = new \RecursiveDirectoryIterator($this->directory); + $iterator = new \RecursiveIteratorIterator($iterator); + return new \RegexIterator($iterator, $pattern); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php new file mode 100644 index 0000000000..5c5a46e944 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php @@ -0,0 +1,113 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Filesystem cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class FilesystemCache extends FileCache +{ + const EXTENSION = '.doctrinecache.data'; + + /** + * {@inheritdoc} + */ + protected $extension = self::EXTENSION; + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $data = ''; + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + if ($lifetime !== 0 && $lifetime < time()) { + fclose($resource); + + return false; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + fclose($resource); + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + fclose($resource); + + return $lifetime === 0 || $lifetime > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $data = serialize($data); + $filename = $this->getFilename($id); + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! is_dir($filepath)) { + mkdir($filepath, 0777, true); + } + + return file_put_contents($filename, $lifeTime . PHP_EOL . $data); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php new file mode 100644 index 0000000000..f839a65918 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcache; + +/** + * Memcache cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcacheCache extends CacheProvider +{ + /** + * @var Memcache|null + */ + private $memcache; + + /** + * Sets the memcache instance to use. + * + * @param Memcache $memcache + * + * @return void + */ + public function setMemcache(Memcache $memcache) + { + $this->memcache = $memcache; + } + + /** + * Gets the memcache instance used by the cache. + * + * @return Memcache|null + */ + public function getMemcache() + { + return $this->memcache; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (bool) $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcache->set($id, $data, 0, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcache->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcache->getStats(); + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php new file mode 100644 index 0000000000..f7e5500a1c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php @@ -0,0 +1,124 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcached; + +/** + * Memcached cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcachedCache extends CacheProvider +{ + /** + * @var Memcached|null + */ + private $memcached; + + /** + * Sets the memcache instance to use. + * + * @param Memcached $memcached + * + * @return void + */ + public function setMemcached(Memcached $memcached) + { + $this->memcached = $memcached; + } + + /** + * Gets the memcached instance used by the cache. + * + * @return Memcached|null + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcached->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== $this->memcached->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcached->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcached->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcached->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcached->getStats(); + $servers = $this->memcached->getServerList(); + $key = $servers[0]['host'] . ':' . $servers[0]['port']; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php new file mode 100644 index 0000000000..83c6b52d88 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Php file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class PhpFileCache extends FileCache +{ + const EXTENSION = '.doctrinecache.php'; + + /** + * {@inheritdoc} + */ + protected $extension = self::EXTENSION; + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $value = include $filename; + + if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) { + return false; + } + + return $value['data']; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $value = include $filename; + + return $value['lifetime'] === 0 || $value['lifetime'] > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + if (is_object($data) && ! method_exists($data, '__set_state')) { + throw new \InvalidArgumentException( + "Invalid argument given, PhpFileCache only allows objects that implement __set_state() " . + "and fully support var_export(). You can use the FilesystemCache to save arbitrary object " . + "graphs using serialize()/deserialize()." + ); + } + + $filename = $this->getFilename($id); + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! is_dir($filepath)) { + mkdir($filepath, 0777, true); + } + + $value = array( + 'lifetime' => $lifeTime, + 'data' => $data + ); + + $value = var_export($value, true); + $code = sprintf('. + */ + +namespace Doctrine\Common\Cache; + +use Redis; + +/** + * Redis cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Osman Ungur + */ +class RedisCache extends CacheProvider +{ + /** + * @var Redis|null + */ + private $redis; + + /** + * Sets the redis instance to use. + * + * @param Redis $redis + * + * @return void + */ + public function setRedis(Redis $redis) + { + $redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue()); + $this->redis = $redis; + } + + /** + * Gets the redis instance used by the cache. + * + * @return Redis|null + */ + public function getRedis() + { + return $this->redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->redis->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + return $this->redis->setex($id, $lifeTime, $data); + } + return $this->redis->set($id, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->redis->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->redis->flushDB(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->redis->info(); + return array( + Cache::STATS_HITS => false, + Cache::STATS_MISSES => false, + Cache::STATS_UPTIME => $info['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ); + } + + /** + * Returns the serializer constant to use. If Redis is compiled with + * igbinary support, that is used. Otherwise the default PHP serializer is + * used. + * + * @return integer One of the Redis::SERIALIZER_* constants + */ + protected function getSerializerValue() + { + return defined('Redis::SERIALIZER_IGBINARY') ? Redis::SERIALIZER_IGBINARY : Redis::SERIALIZER_PHP; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php new file mode 100644 index 0000000000..b8dbfd5673 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php @@ -0,0 +1,250 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Riak\Bucket; +use Riak\Connection; +use Riak\Input; +use Riak\Exception; +use Riak\Object; + +/** + * Riak cache provider. + * + * @link www.doctrine-project.org + * @since 1.1 + * @author Guilherme Blanco + */ +class RiakCache extends CacheProvider +{ + const EXPIRES_HEADER = 'X-Riak-Meta-Expires'; + + /** + * @var \Riak\Bucket + */ + private $bucket; + + /** + * Sets the riak bucket instance to use. + * + * @param \Riak\Bucket $bucket + */ + public function __construct(Bucket $bucket) + { + $this->bucket = $bucket; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + try { + $response = $this->bucket->get(urlencode($id)); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + // Check for attempted siblings + $object = ($response->hasSiblings()) + ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList()) + : $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return unserialize($object->getContent()); + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\ConnectionException + // - Riak\CommunicationException + // - Riak\UnexpectedResponseException + // - Riak\NotFoundException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + try { + // We only need the HEAD, not the entire object + $input = new Input\GetInput(); + + $input->setReturnHead(true); + + $response = $this->bucket->get(urlencode($id), $input); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + $object = $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $object = new Object(urlencode($id)); + + $object->setContent(serialize($data)); + + if ($lifeTime > 0) { + $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime)); + } + + $this->bucket->put($object); + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->bucket->delete(urlencode($id)); + + return true; + } catch (Exception\BadArgumentsException $e) { + // Key did not exist on cluster already + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\Exception\ConnectionException + // - Riak\Exception\CommunicationException + // - Riak\Exception\UnexpectedResponseException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + $keyList = $this->bucket->getKeyList(); + + foreach ($keyList as $key) { + $this->bucket->delete($key); + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // Only exposed through HTTP stats API, not Protocol Buffers API + return null; + } + + /** + * Check if a given Riak Object have expired. + * + * @param \Riak\Object $object + * + * @return boolean + */ + private function isExpired(Object $object) + { + $metadataMap = $object->getMetadataMap(); + + return isset($metadataMap[self::EXPIRES_HEADER]) + && $metadataMap[self::EXPIRES_HEADER] < time(); + } + + /** + * On-read conflict resolution. Applied approach here is last write wins. + * Specific needs may override this method to apply alternate conflict resolutions. + * + * {@internal Riak does not attempt to resolve a write conflict, and store + * it as sibling of conflicted one. By following this approach, it is up to + * the next read to resolve the conflict. When this happens, your fetched + * object will have a list of siblings (read as a list of objects). + * In our specific case, we do not care about the intermediate ones since + * they are all the same read from storage, and we do apply a last sibling + * (last write) wins logic. + * If by any means our resolution generates another conflict, it'll up to + * next read to properly solve it.} + * + * @param string $id + * @param string $vClock + * @param array $objectList + * + * @return \Riak\Object + */ + protected function resolveConflict($id, $vClock, array $objectList) + { + // Our approach here is last-write wins + $winner = $objectList[count($objectList)]; + + $putInput = new Input\PutInput(); + $putInput->setVClock($vClock); + + $mergedObject = new Object(urlencode($id)); + $mergedObject->setContent($winner->getContent()); + + $this->bucket->put($mergedObject, $putInput); + + return $mergedObject; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php new file mode 100644 index 0000000000..ae32772930 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * WinCache cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class WinCacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return wincache_ucache_get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return wincache_ucache_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return (bool) wincache_ucache_set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return wincache_ucache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return wincache_ucache_clear(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = wincache_ucache_info(); + $meminfo = wincache_ucache_meminfo(); + + return array( + Cache::STATS_HITS => $info['total_hit_count'], + Cache::STATS_MISSES => $info['total_miss_count'], + Cache::STATS_UPTIME => $info['total_cache_uptime'], + Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'], + Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php new file mode 100644 index 0000000000..833b02a89b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php @@ -0,0 +1,109 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Xcache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class XcacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->doContains($id) ? unserialize(xcache_get($id)) : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return xcache_isset($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return xcache_set($id, serialize($data), (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return xcache_unset($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->checkAuthorization(); + + xcache_clear_cache(XC_TYPE_VAR, 0); + + return true; + } + + /** + * Checks that xcache.admin.enable_auth is Off. + * + * @return void + * + * @throws \BadMethodCallException When xcache.admin.enable_auth is On. + */ + protected function checkAuthorization() + { + if (ini_get('xcache.admin.enable_auth')) { + throw new \BadMethodCallException('To use all features of \Doctrine\Common\Cache\XcacheCache, you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'); + } + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $this->checkAuthorization(); + + $info = xcache_info(XC_TYPE_VAR, 0); + return array( + Cache::STATS_HITS => $info['hits'], + Cache::STATS_MISSES => $info['misses'], + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $info['size'], + Cache::STATS_MEMORY_AVAILABLE => $info['avail'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php new file mode 100644 index 0000000000..6e35ac8236 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Zend Data Cache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Ralph Schindler + * @author Guilherme Blanco + */ +class ZendDataCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return zend_shm_cache_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== zend_shm_cache_fetch($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return zend_shm_cache_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return zend_shm_cache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $namespace = $this->getNamespace(); + if (empty($namespace)) { + return zend_shm_cache_clear(); + } + return zend_shm_cache_clear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/phpunit.xml.dist b/vendor/doctrine/cache/phpunit.xml.dist new file mode 100644 index 0000000000..900378bf36 --- /dev/null +++ b/vendor/doctrine/cache/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php new file mode 100644 index 0000000000..df81262010 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of APC'); + } + } + + protected function _getCacheDriver() + { + return new ApcCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php new file mode 100644 index 0000000000..6cad8915b6 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php @@ -0,0 +1,21 @@ +_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php new file mode 100644 index 0000000000..8d0e16edfc --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php @@ -0,0 +1,103 @@ +_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out'); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // Test delete + $cache->save('test_key2', 'test2'); + $cache->delete('test_key2'); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testObjects() + { + $cache = $this->_getCacheDriver(); + + // Fetch/save test with objects (Is cache driver serializes/unserializes objects correctly ?) + $cache->save('test_object_key', new \ArrayObject()); + $this->assertTrue($cache->fetch('test_object_key') instanceof \ArrayObject); + } + + public function testDeleteAll() + { + $cache = $this->_getCacheDriver(); + $cache->save('test_key1', '1'); + $cache->save('test_key2', '2'); + $cache->deleteAll(); + + $this->assertFalse($cache->contains('test_key1')); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testFlushAll() + { + $cache = $this->_getCacheDriver(); + $cache->save('test_key1', '1'); + $cache->save('test_key2', '2'); + $cache->flushAll(); + + $this->assertFalse($cache->contains('test_key1')); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testNamespace() + { + $cache = $this->_getCacheDriver(); + $cache->setNamespace('test_'); + $cache->save('key1', 'test'); + + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('test2_'); + + $this->assertFalse($cache->contains('key1')); + } + + /** + * @group DCOM-43 + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertArrayHasKey(Cache::STATS_HITS, $stats); + $this->assertArrayHasKey(Cache::STATS_MISSES, $stats); + $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats); + } + + /** + * Make sure that all supported caches return "false" instead of "null" to be compatible + * with ORM integration. + */ + public function testFalseOnFailedFetch() + { + $cache = $this->_getCacheDriver(); + $result = $cache->fetch('nonexistent_key'); + $this->assertFalse($result); + $this->assertNotNull($result); + } + + /** + * @return \Doctrine\Common\Cache\CacheProvider + */ + abstract protected function _getCacheDriver(); +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php new file mode 100644 index 0000000000..40d5a6934b --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php @@ -0,0 +1,47 @@ +couchbase = new Couchbase('127.0.0.1', 'Administrator', 'password', 'default'); + } catch(Exception $ex) { + $this->markTestSkipped('Could not instantiate the Couchbase cache because of: ' . $ex); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of the couchbase extension'); + } + } + + public function testNoExpire() + { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Couchbase provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + + $this->assertTrue($cache->contains('key'), 'Couchbase provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new CouchbaseCache(); + $driver->setCouchbase($this->couchbase); + return $driver; + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php new file mode 100644 index 0000000000..6f9df8158e --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php @@ -0,0 +1,107 @@ +driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(), '', false + ); + } + + public function getProviderFileName() + { + return array( + //The characters :\/<>"*?| are not valid in Windows filenames. + array('key:1', 'key1'), + array('key\2', 'key2'), + array('key/3', 'key3'), + array('key<4', 'key4'), + array('key>5', 'key5'), + array('key"6', 'key6'), + array('key*7', 'key7'), + array('key?8', 'key8'), + array('key|9', 'key9'), + array('key[0]','key[0]'), + ); + } + + /** + * @dataProvider getProviderFileName + */ + public function testInvalidFilename($key, $expected) + { + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + + $method->setAccessible(true); + + $value = $method->invoke($cache, $key); + $actual = pathinfo($value, PATHINFO_FILENAME); + + $this->assertEquals($expected, $actual); + } + + public function testFilenameCollision() + { + $data['key:0'] = 'key0'; + $data['key\0'] = 'key0'; + $data['key/0'] = 'key0'; + $data['key<0'] = 'key0'; + $data['key>0'] = 'key0'; + $data['key"0'] = 'key0'; + $data['key*0'] = 'key0'; + $data['key?0'] = 'key0'; + $data['key|0'] = 'key0'; + + $paths = array(); + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + + $method->setAccessible(true); + + foreach ($data as $key => $expected) { + $path = $method->invoke($cache, $key); + $actual = pathinfo($path, PATHINFO_FILENAME); + + $this->assertNotContains($path, $paths); + $this->assertEquals($expected, $actual); + + $paths[] = $path; + } + } + + public function testFilenameShouldCreateThePathWithFourSubDirectories() + { + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + $key = 'item-key'; + $expectedDir[] = '84e0e2e893febb73'; + $expectedDir[] = '7a0fee0c89d53f4b'; + $expectedDir[] = 'b7fcb44c57cdf3d3'; + $expectedDir[] = '2ce7363f5d597760'; + $expectedDir = implode(DIRECTORY_SEPARATOR, $expectedDir); + + $method->setAccessible(true); + + $path = $method->invoke($cache, $key); + $filename = pathinfo($path, PATHINFO_FILENAME); + $dirname = pathinfo($path, PATHINFO_DIRNAME); + + $this->assertEquals('item-key', $filename); + $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname); + $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir . DIRECTORY_SEPARATOR . $key, $path); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php new file mode 100644 index 0000000000..ff243ceeff --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php @@ -0,0 +1,102 @@ +assertFalse(is_dir($dir)); + + $this->driver = new FilesystemCache($dir); + $this->assertTrue(is_dir($dir)); + + return $this->driver; + } + + public function testLifetime() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out', 10); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // access private methods + $getFilename = new \ReflectionMethod($cache, 'getFilename'); + $getNamespacedId = new \ReflectionMethod($cache, 'getNamespacedId'); + + $getFilename->setAccessible(true); + $getNamespacedId->setAccessible(true); + + $id = $getNamespacedId->invoke($cache, 'test_key'); + $filename = $getFilename->invoke($cache, $id); + + $data = ''; + $lifetime = 0; + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + $this->assertNotEquals(0, $lifetime, "previous lifetime could not be loaded"); + + // update lifetime + $lifetime = $lifetime - 20; + file_put_contents($filename, $lifetime . PHP_EOL . $data); + + // test expired data + $this->assertFalse($cache->contains('test_key')); + $this->assertFalse($cache->fetch('test_key')); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + public function tearDown() + { + $dir = $this->driver->getDirectory(); + $ext = $this->driver->getExtension(); + $iterator = new \RecursiveDirectoryIterator($dir); + + foreach (new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST) as $file) { + if ($file->isFile()) { + @unlink($file->getRealPath()); + } else { + @rmdir($file->getRealPath()); + } + } + } + +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php new file mode 100644 index 0000000000..36c180c935 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php @@ -0,0 +1,45 @@ +_memcache = new \Memcache; + $ok = @$this->_memcache->connect('localhost', 11211); + if (!$ok) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } + + public function testNoExpire() { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Memcache provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + $this->assertTrue($cache->contains('key'), 'Memcache provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new MemcacheCache(); + $driver->setMemcache($this->_memcache); + return $driver; + } + +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php new file mode 100644 index 0000000000..ecbe5a6001 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php @@ -0,0 +1,48 @@ +memcached = new \Memcached(); + $this->memcached->setOption(\Memcached::OPT_COMPRESSION, false); + $this->memcached->addServer('127.0.0.1', 11211); + + $fh = @fsockopen('127.0.0.1', 11211); + if (!$fh) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } + + public function testNoExpire() { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Memcache provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + + $this->assertTrue($cache->contains('key'), 'Memcached provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new MemcachedCache(); + $driver->setMemcached($this->memcached); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php new file mode 100644 index 0000000000..1f13b27431 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php @@ -0,0 +1,154 @@ +assertFalse(is_dir($dir)); + + $this->driver = new PhpFileCache($dir); + $this->assertTrue(is_dir($dir)); + + return $this->driver; + } + + public function testObjects() + { + $this->markTestSkipped('PhpFileCache does not support saving objects that dont implement __set_state()'); + } + + public function testLifetime() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out', 10); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // access private methods + $getFilename = new \ReflectionMethod($cache, 'getFilename'); + $getNamespacedId = new \ReflectionMethod($cache, 'getNamespacedId'); + + $getFilename->setAccessible(true); + $getNamespacedId->setAccessible(true); + + $id = $getNamespacedId->invoke($cache, 'test_key'); + $path = $getFilename->invoke($cache, $id); + $value = include $path; + + // update lifetime + $value['lifetime'] = $value['lifetime'] - 20; + file_put_contents($path, 'assertFalse($cache->contains('test_key')); + $this->assertFalse($cache->fetch('test_key')); + } + + public function testImplementsSetState() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_set_state', new SetStateClass(array(1,2,3))); + + //Test __set_state call + $this->assertCount(0, SetStateClass::$values); + + // Test fetch + $value = $cache->fetch('test_set_state'); + $this->assertInstanceOf('Doctrine\Tests\Common\Cache\SetStateClass', $value); + $this->assertEquals(array(1,2,3), $value->getValue()); + + //Test __set_state call + $this->assertCount(1, SetStateClass::$values); + + // Test contains + $this->assertTrue($cache->contains('test_set_state')); + } + + public function testNotImplementsSetState() + { + $cache = $this->_getCacheDriver(); + + $this->setExpectedException('InvalidArgumentException'); + $cache->save('test_not_set_state', new NotSetStateClass(array(1,2,3))); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + public function tearDown() + { + if (!$this->driver) { + return; + } + + $dir = $this->driver->getDirectory(); + $ext = $this->driver->getExtension(); + $iterator = new \RecursiveDirectoryIterator($dir); + + foreach (new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST) as $file) { + if ($file->isFile()) { + @unlink($file->getRealPath()); + } else { + @rmdir($file->getRealPath()); + } + } + } + +} + +class NotSetStateClass +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } +} + +class SetStateClass extends NotSetStateClass +{ + public static $values = array(); + + public static function __set_state($data) + { + self::$values = $data; + return new self($data['value']); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php new file mode 100644 index 0000000000..45bbc752af --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php @@ -0,0 +1,30 @@ +_redis = new \Redis(); + $ok = @$this->_redis->connect('127.0.0.1'); + if (!$ok) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of redis'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of redis'); + } + } + + protected function _getCacheDriver() + { + $driver = new RedisCache(); + $driver->setRedis($this->_redis); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php new file mode 100644 index 0000000000..dce8cc00e2 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php @@ -0,0 +1,64 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Riak'); + } + + try { + $this->connection = new Connection('127.0.0.1', 8087); + $this->bucket = new Bucket($this->connection, 'test'); + } catch (Exception\RiakException $e) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of Riak'); + } + } + + /** + * {@inheritdoc} + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + /** + * Retrieve RiakCache instance. + * + * @return \Doctrine\Common\Cache\RiakCache + */ + protected function _getCacheDriver() + { + return new RiakCache($this->bucket); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php new file mode 100644 index 0000000000..cb363df956 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Wincache'); + } + } + + protected function _getCacheDriver() + { + return new WincacheCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php new file mode 100644 index 0000000000..6259848776 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of xcache'); + } + } + + protected function _getCacheDriver() + { + return new XcacheCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php new file mode 100644 index 0000000000..cd66e1578f --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php @@ -0,0 +1,28 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Zend Data Cache which only works in apache2handler SAPI'); + } + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + protected function _getCacheDriver() + { + return new ZendDataCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000000..e8323d2940 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ +on('user.create', function (User $user) use ($logger) { + $logger->log(sprintf("User '%s' was created.", $user->getLogin())); +}); +``` + +### Emitting Events + +```php +emit('user.create', array($user)); +``` + +Tests +----- + + $ phpunit + +License +------- +MIT, see LICENSE. diff --git a/vendor/evenement/evenement/composer.json b/vendor/evenement/evenement/composer.json new file mode 100644 index 0000000000..5426833cd4 --- /dev/null +++ b/vendor/evenement/evenement/composer.json @@ -0,0 +1,20 @@ +{ + "name": "evenement/evenement", + "description": "Événement is a very simple event dispatching library for PHP 5.3", + "keywords": ["event-dispatcher"], + "license": "MIT", + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { + "Evenement": "src" + } + } +} diff --git a/vendor/evenement/evenement/phpunit.xml.dist b/vendor/evenement/evenement/phpunit.xml.dist new file mode 100644 index 0000000000..8be0651849 --- /dev/null +++ b/vendor/evenement/evenement/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/Evenement/ + + + + + + ./src/ + + + diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/vendor/evenement/evenement/src/Evenement/EventEmitter.php new file mode 100644 index 0000000000..df591c8576 --- /dev/null +++ b/vendor/evenement/evenement/src/Evenement/EventEmitter.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +class EventEmitter implements EventEmitterInterface +{ + protected $listeners = array(); + + public function on($event, $listener) + { + if (!is_callable($listener)) { + throw new \InvalidArgumentException('The provided listener was not a valid callable.'); + } + + if (!isset($this->listeners[$event])) { + $this->listeners[$event] = array(); + } + + $this->listeners[$event][] = $listener; + } + + public function once($event, $listener) + { + $that = $this; + + $onceListener = function () use ($that, &$onceListener, $event, $listener) { + $that->removeListener($event, $onceListener); + + call_user_func_array($listener, func_get_args()); + }; + + $this->on($event, $onceListener); + } + + public function removeListener($event, $listener) + { + if (isset($this->listeners[$event])) { + if (false !== $index = array_search($listener, $this->listeners[$event], true)) { + unset($this->listeners[$event][$index]); + } + } + } + + public function removeAllListeners($event = null) + { + if ($event !== null) { + unset($this->listeners[$event]); + } else { + $this->listeners = array(); + } + } + + public function listeners($event) + { + return isset($this->listeners[$event]) ? $this->listeners[$event] : array(); + } + + public function emit($event, array $arguments = array()) + { + foreach ($this->listeners($event) as $listener) { + call_user_func_array($listener, $arguments); + } + } +} diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitter2.php b/vendor/evenement/evenement/src/Evenement/EventEmitter2.php new file mode 100644 index 0000000000..b051609986 --- /dev/null +++ b/vendor/evenement/evenement/src/Evenement/EventEmitter2.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +class EventEmitter2 extends EventEmitter +{ + protected $options; + protected $anyListeners = array(); + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'delimiter' => '.', + ), $options); + } + + public function onAny($listener) + { + $this->anyListeners[] = $listener; + } + + public function offAny($listener) + { + if (false !== $index = array_search($listener, $this->anyListeners, true)) { + unset($this->anyListeners[$index]); + } + } + + public function many($event, $timesToListen, $listener) + { + $that = $this; + + $timesListened = 0; + + if ($timesToListen == 0) { + return; + } + + if ($timesToListen < 0) { + throw new \OutOfRangeException('You cannot listen less than zero times.'); + } + + $manyListener = function () use ($that, &$timesListened, &$manyListener, $event, $timesToListen, $listener) { + if (++$timesListened == $timesToListen) { + $that->removeListener($event, $manyListener); + } + + call_user_func_array($listener, func_get_args()); + }; + + $this->on($event, $manyListener); + } + + public function emit($event, array $arguments = array()) + { + foreach ($this->anyListeners as $listener) { + call_user_func_array($listener, $arguments); + } + + parent::emit($event, $arguments); + } + + public function listeners($event) + { + $matchedListeners = array(); + + foreach ($this->listeners as $name => $listeners) { + foreach ($listeners as $listener) { + if ($this->matchEventName($event, $name)) { + $matchedListeners[] = $listener; + } + } + } + + return $matchedListeners; + } + + protected function matchEventName($matchPattern, $eventName) + { + $patternParts = explode($this->options['delimiter'], $matchPattern); + $nameParts = explode($this->options['delimiter'], $eventName); + + if (count($patternParts) != count($nameParts)) { + return false; + } + + $size = min(count($patternParts), count($nameParts)); + for ($i = 0; $i < $size; $i++) { + $patternPart = $patternParts[$i]; + $namePart = $nameParts[$i]; + + if ('*' === $patternPart || '*' === $namePart) { + continue; + } + + if ($namePart === $patternPart) { + continue; + } + + return false; + } + + return true; + } +} diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php new file mode 100644 index 0000000000..665a9478c3 --- /dev/null +++ b/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +interface EventEmitterInterface +{ + public function on($event, $listener); + public function once($event, $listener); + public function removeListener($event, $listener); + public function removeAllListeners($event = null); + public function listeners($event); + public function emit($event, array $arguments = array()); +} diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitter2Test.php b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitter2Test.php new file mode 100644 index 0000000000..7be4290452 --- /dev/null +++ b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitter2Test.php @@ -0,0 +1,160 @@ +emitter = new EventEmitter2(); + } + + // matching tests from + // test/wildcardEvents/addListener.js + + public function testWildcardMatching7() + { + $listenerCalled = 0; + + $listener = function () use (&$listenerCalled) { + $listenerCalled++; + }; + + $this->emitter->on('*.test', $listener); + $this->emitter->on('*.*', $listener); + $this->emitter->on('*', $listener); + + $this->emitter->emit('other.emit'); + $this->emitter->emit('foo.test'); + + $this->assertSame(3, $listenerCalled); + } + + public function testWildcardMatching8() + { + $listenerCalled = 0; + + $listener = function () use (&$listenerCalled) { + $listenerCalled++; + }; + + $this->emitter->on('foo.test', $listener); + $this->emitter->on('*.*', $listener); + $this->emitter->on('*', $listener); + + $this->emitter->emit('*.*'); + $this->emitter->emit('foo.test'); + $this->emitter->emit('*'); + + $this->assertSame(5, $listenerCalled); + } + + public function testOnAny() + { + $this->emitter->onAny(function () {}); + } + + public function testOnAnyWithEmit() + { + $listenerCalled = 0; + + $this->emitter->onAny(function () use (&$listenerCalled) { + $listenerCalled++; + }); + + $this->assertSame(0, $listenerCalled); + + $this->emitter->emit('foo'); + + $this->assertSame(1, $listenerCalled); + + $this->emitter->emit('bar'); + + $this->assertSame(2, $listenerCalled); + } + + public function testoffAnyWithEmit() + { + $listenerCalled = 0; + + $listener = function () use (&$listenerCalled) { + $listenerCalled++; + }; + + $this->emitter->onAny($listener); + $this->emitter->offAny($listener); + + $this->assertSame(0, $listenerCalled); + $this->emitter->emit('foo'); + $this->assertSame(0, $listenerCalled); + } + + /** + * @dataProvider provideMany + */ + public function testMany($amount) + { + $listenerCalled = 0; + + $this->emitter->many('foo', $amount, function () use (&$listenerCalled) { + $listenerCalled++; + }); + + for ($i = 0; $i < $amount; $i++) { + $this->assertSame($i, $listenerCalled); + $this->emitter->emit('foo'); + } + + $this->emitter->emit('foo'); + $this->assertSame($amount, $listenerCalled); + } + + public function provideMany() + { + return array( + array(0), + array(1), + array(2), + array(3), + array(4), + array(400), + ); + } + + /** + * @expectedException OutOfRangeException + */ + public function testManyWithLessThanZeroTtl() + { + $this->emitter->many('foo', -1, function () {}); + $this->emitter->emit('foo'); + } +} diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php new file mode 100644 index 0000000000..3f6be1dd68 --- /dev/null +++ b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php @@ -0,0 +1,236 @@ +emitter = new EventEmitter(); + } + + public function testAddListenerWithLambda() + { + $this->emitter->on('foo', function () {}); + } + + public function testAddListenerWithMethod() + { + $listener = new Listener(); + $this->emitter->on('foo', array($listener, 'onFoo')); + } + + public function testAddListenerWithStaticMethod() + { + $this->emitter->on('bar', array('Evenement\Tests\Listener', 'onBar')); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testAddListenerWithInvalidListener() + { + $this->emitter->on('foo', 'not a callable'); + } + + public function testOnce() + { + $listenerCalled = 0; + + $this->emitter->once('foo', function () use (&$listenerCalled) { + $listenerCalled++; + }); + + $this->assertSame(0, $listenerCalled); + + $this->emitter->emit('foo'); + + $this->assertSame(1, $listenerCalled); + + $this->emitter->emit('foo'); + + $this->assertSame(1, $listenerCalled); + } + + public function testEmitWithoutArguments() + { + $listenerCalled = false; + + $this->emitter->on('foo', function () use (&$listenerCalled) { + $listenerCalled = true; + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo'); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithOneArgument() + { + $test = $this; + + $listenerCalled = false; + + $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) { + $listenerCalled = true; + + $test->assertSame('bar', $value); + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo', array('bar')); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithTwoArguments() + { + $test = $this; + + $listenerCalled = false; + + $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) { + $listenerCalled = true; + + $test->assertSame('bar', $arg1); + $test->assertSame('baz', $arg2); + }); + + $this->assertSame(false, $listenerCalled); + $this->emitter->emit('foo', array('bar', 'baz')); + $this->assertSame(true, $listenerCalled); + } + + public function testEmitWithNoListeners() + { + $this->emitter->emit('foo'); + $this->emitter->emit('foo', array('bar')); + $this->emitter->emit('foo', array('bar', 'baz')); + } + + public function testEmitWithTwoListeners() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(2, $listenersCalled); + } + + public function testRemoveListenerMatching() + { + $listenersCalled = 0; + + $listener = function () use (&$listenersCalled) { + $listenersCalled++; + }; + + $this->emitter->on('foo', $listener); + $this->emitter->removeListener('foo', $listener); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(0, $listenersCalled); + } + + public function testRemoveListenerNotMatching() + { + $listenersCalled = 0; + + $listener = function () use (&$listenersCalled) { + $listenersCalled++; + }; + + $this->emitter->on('foo', $listener); + $this->emitter->removeListener('bar', $listener); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(1, $listenersCalled); + } + + public function testRemoveAllListenersMatching() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners('foo'); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(0, $listenersCalled); + } + + public function testRemoveAllListenersNotMatching() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners('bar'); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->assertSame(1, $listenersCalled); + } + + public function testRemoveAllListenersWithoutArguments() + { + $listenersCalled = 0; + + $this->emitter->on('foo', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->on('bar', function () use (&$listenersCalled) { + $listenersCalled++; + }); + + $this->emitter->removeAllListeners(); + + $this->assertSame(0, $listenersCalled); + $this->emitter->emit('foo'); + $this->emitter->emit('bar'); + $this->assertSame(0, $listenersCalled); + } +} diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php new file mode 100644 index 0000000000..7032442aa8 --- /dev/null +++ b/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php @@ -0,0 +1,38 @@ +add('Evenement\Tests', __DIR__); diff --git a/vendor/monolog/monolog/CHANGELOG.mdown b/vendor/monolog/monolog/CHANGELOG.mdown new file mode 100644 index 0000000000..2df1e0f1aa --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.mdown @@ -0,0 +1,110 @@ +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000000..5df1c397f7 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Jordi Boggiano + +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/vendor/monolog/monolog/README.mdown b/vendor/monolog/monolog/README.mdown new file mode 100644 index 0000000000..9d20546996 --- /dev/null +++ b/vendor/monolog/monolog/README.mdown @@ -0,0 +1,248 @@ +Monolog - Logging for PHP 5.3+ [![Build Status](https://secure.travis-ci.org/Seldaek/monolog.png)](http://travis-ci.org/Seldaek/monolog) +============================== + +[![Total Downloads](https://poser.pugx.org/monolog/monolog/downloads.png)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://poser.pugx.org/monolog/monolog/v/stable.png)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. + +Usage +----- + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +Core Concepts +------------- + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it handled fully the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to true means that all records will propagate to the +`StreamHandler`, even the errors that are handled by the `MailHandler`. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +Log Levels +---------- + +Monolog supports all 8 logging levels defined in +[RFC 5424](http://tools.ietf.org/html/rfc5424), but unless you specifically +need syslog compatibility, it is advised to only use DEBUG, INFO, WARNING, +ERROR, CRITICAL, ALERT. + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- NOTICE (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- EMERGENCY (600): Emergency: system is unusable. + +Docs +==== + +**See the `doc` directory for more detailed documentation. +The following is only a list of all parts that come with Monolog.** + +Handlers +-------- + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](https://github.com/Seldaek/monolog/blob/master/doc/sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. + +Formatters +---------- + +- _LineFormatter_: Formats a log record into a one-line string. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/1.1.5/). + +Processors +---------- + +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. + +Utilities +--------- + +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +About +===== + +Requirements +------------ + +- Any flavor of PHP 5.3 or above should do +- [optional] PHPUnit 3.5+ to execute the test suite (phpunit --version) + +Submitting bugs and feature requests +------------------------------------ + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +Frameworks Integration +---------------------- + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel4](http://laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. + +Author +------ + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +License +------- + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +Acknowledgements +---------------- + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000000..c89e1886c7 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,39 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "mlehner/gelf-php": "1.0.*", + "raven/raven": "0.5.*", + "doctrine/couchdb": "dev-master" + }, + "suggest": { + "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server" + }, + "autoload": { + "psr-0": {"Monolog": "src/"} + }, + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + } +} diff --git a/vendor/monolog/monolog/doc/extending.md b/vendor/monolog/monolog/doc/extending.md new file mode 100644 index 0000000000..fcd7af2b39 --- /dev/null +++ b/vendor/monolog/monolog/doc/extending.md @@ -0,0 +1,76 @@ +Extending Monolog +================= + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +Writing your own handler +------------------------ + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). diff --git a/vendor/monolog/monolog/doc/sockets.md b/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 0000000000..fad30a9f4b --- /dev/null +++ b/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,37 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/vendor/monolog/monolog/doc/usage.md b/vendor/monolog/monolog/doc/usage.md new file mode 100644 index 0000000000..07efa78a44 --- /dev/null +++ b/vendor/monolog/monolog/doc/usage.md @@ -0,0 +1,158 @@ +Using Monolog +============= + +Installation +------------ + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +Configuring a logger +-------------------- + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +Adding extra data in the records +-------------------------------- + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the README file for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +Leveraging channels +------------------- + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); +``` + +Customizing log format +---------------------- + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. diff --git a/vendor/monolog/monolog/phpunit.xml.dist b/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 0000000000..175457071b --- /dev/null +++ b/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000000..aa5a278a62 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + + private $fatalLevel; + private $reservedMemory; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level === null ? LogLevel::ERROR : $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = $this->defaultErrorLevelMap(); + // merging the map into the defaults by hand because array_merge + // trips up on numeric keys + foreach ($levelMap as $key => $val) { + $this->errorLevelMap[$key] = $val; + } + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level === null ? LogLevel::ALERT : $level; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException(\Exception $e) + { + $this->logger->log($this->uncaughtExceptionLevel, 'Uncaught exception', array('exception' => $e)); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if (!(error_reporting() & $code)) { + return; + } + + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('file' => $file, 'line' => $line)); + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors)) { + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('file' => $lastError['file'], 'line' => $lastError['line']) + ); + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000000..56d3e278a5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file']) && isset($record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file']); + unset($record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000000..b5de751112 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000000..aa01f491eb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setFacility($record['channel']) + ->setHost($this->systemName) + ->setLine(isset($record['extra']['line']) ? $record['extra']['line'] : null) + ->setFile(isset($record['extra']['file']) ? $record['extra']['file'] : null) + ->setLevel($this->logLevels[$record['level']]); + + // Do not duplicate these values in the additional fields + unset($record['extra']['line']); + unset($record['extra']['file']); + + foreach ($record['extra'] as $key => $val) { + $message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : $this->toJson($val)); + } + + foreach ($record['context'] as $key => $val) { + $message->setAdditional($this->contextPrefix . $key, is_scalar($val) ? $val : $this->toJson($val)); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000000..822af0ea43 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter implements FormatterInterface +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return json_encode($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + return json_encode($records); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000000..a96fb27d90 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($format = null, $dateFormat = null) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + parent::__construct($dateFormat); + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->convertToString($val), $output); + unset($vars['extra'][$var]); + } + } + foreach ($vars as $var => $val) { + $output = str_replace('%'.$var.'%', $this->convertToString($val), $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function normalize($data) + { + if (is_bool($data) || is_null($data)) { + return var_export($data, true); + } + + if ($data instanceof \Exception) { + $previousText = ''; + if ($previous = $data->getPrevious()) { + do { + $previousText .= ', '.get_class($previous).': '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + return '[object] ('.get_class($data).': '.$data->getMessage().' at '.$data->getFile().':'.$data->getLine().$previousText.')'; + } + + return parent::normalize($data); + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000000..7aa8ad33cb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + //log stash requires a ISO 8601 format date + parent::__construct('c'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + $message = array( + '@timestamp' => $record['datetime'], + '@message' => $record['message'], + '@tags' => array($record['channel']), + '@source' => $this->systemName + ); + + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + $message['@fields'] = array(); + $message['@fields']['channel'] = $record['channel']; + $message['@fields']['level'] = $record['level']; + + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + + return json_encode($message) . "\n"; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000000..765fed4566 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + return $data; + } + + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + if ($data instanceof Exception) { + return $this->normalizeException($data); + } + + return sprintf("[object] (%s: %s)", get_class($data), $this->toJson($data, true)); + } + + if (is_resource($data)) { + return '[resource]'; + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException(Exception $e) + { + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + $trace = $e->getTrace(); + array_shift($trace); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } else { + $data['trace'][] = json_encode($frame); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return @json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return @json_encode($data); + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000000..b3e9b18644 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $this->logLevels[$record['level']], + 'File' => $file, + 'Line' => $line, + 'Label' => $record['channel'], + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000000..2ea9f5599b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = false; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->level = $level; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param integer $level + */ + public function setLevel($level) + { + $this->level = $level; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return integer + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble True means that bubbling is not permitted. + * False means that this handler allows bubbling. + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean True means that bubbling is not permitted. + * False means that this handler allows bubbling. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000000..e1e5b89311 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000000..00703436c5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var \AMQPExchange $exchange + */ + protected $exchange; + + /** + * @param \AMQPExchange $exchange AMQP exchange, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\AMQPExchange $exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + $this->exchange = $exchange; + $this->exchange->setName($exchangeName); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + + $routingKey = sprintf( + '%s.%s', + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + $this->exchange->publish( + $data, + strtolower($routingKey), + 0, + array( + 'delivery_mode' => 2, + 'Content-type' => 'application/json' + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000000..e9a4dc358b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + + /** + * @param HandlerInterface $handler Handler. + * @param integer $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferSize = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferSize; + $this->flushOnOverflow = $flushOnOverflow; + + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->bufferSize = 0; + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000000..705400b15c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var Boolean + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed) { + return; + } + + if (!self::$initialized) { + self::$sendHeaders = $this->headersAccepted(); + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + + self::$initialized = true; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240*1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + $this->sendHeader(self::HEADER_NAME, $data); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + return !isset($_SERVER['HTTP_USER_AGENT']) + || preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000000..4877b345d6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ) + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000000..0b2f21be0c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection = null; + private $httpConnection = null; + private $scheme = null; + private $host = null; + private $port = null; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws UnexpectedValueException when given url is not a valid url. + * A valid url must consists of three parts : protocol://host:port + * Only valid protocol used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfos = parse_url($url); + + if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfos['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfos['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfos['scheme']; + $this->host = $urlInfos['host']; + $this->port = $urlInfos['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws LogicException when unable to connect to the socket + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.u')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + $this->{'write'.$this->scheme}(json_encode($data)); + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']')) + ); + + return curl_exec($this->httpConnection); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000000..b91ffec905 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000000..477c56546a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + protected $messageType; + + /** + * @param integer $messageType Says where the error should go. + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($messageType = 0, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!in_array($messageType, array(0, 4))) { + throw new \InvalidArgumentException('Only message types 0 and 4 are supported'); + } + $this->messageType = $messageType; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + error_log((string) $record['formatted'], $this->messageType); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000000..c3e42efefa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return Boolean + */ + public function isHandlerActivated(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000000..646d57a8c8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,57 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $categoryToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = $defaultActionLevel; + $this->channelToActionLevel = $channelToActionLevel; + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000000..7cd8ef1b62 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = $actionLevel; + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000000..bc51e4e3be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + if (!is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000000..46a039ad72 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$sendHeaders = $this->headersAccepted(); + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + + self::$initialized = true; + } + + $header = $this->createRecordHeader($record); + $this->sendHeader(key($header), current($header)); + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + return !isset($_SERVER['HTTP_USER_AGENT']) + || preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT']) + || isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000000..34d48e7506 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Monolog\Logger; +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Gelf\IMessagePublisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param Gelf\IMessagePublisher $publisher a publisher object + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(IMessagePublisher $publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->publisher = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000000..99384d35f1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000000..ac15d7decb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record + * + * @return Boolean + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return Boolean True means that this handler handled the record, and that bubbling is not permitted. + * False means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000000..e1402a0357 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * @var string + */ + private $token; + + /** + * @var array + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var boolean + */ + private $notify; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true) + { + $connectionString = $useSSL ? 'ssl://api.hipchat.com:443' : 'api.hipchat.com:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'from' => $this->name, + 'room_id' => $this->room, + 'notify' => $this->notify, + 'message' => $record['formatted'], + 'message_format' => 'text', + 'color' => $this->getAlertColor($record['level']), + ); + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/rooms/message?format=json&auth_token=".$this->token." HTTP/1.1\r\n"; + $header .= "Host: api.hipchat.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param integer $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000000..86292727f3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000000..0cb21cd227 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000000..5a59201a11 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + private $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + $this->mongoCollection->save($record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000000..c7ac63a0e8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + */ +class NativeMailerHandler extends MailHandler +{ + protected $to; + protected $subject; + protected $headers = array( + 'Content-type: text/plain; charset=utf-8' + ); + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param integer $level The minimum logging level at which this handler will be triggered + * @param boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + } + + /** + * @param string|array $headers Custom added headers + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, 70); + $headers = implode("\r\n", $this->headers) . "\r\n"; + foreach ($this->to as $to) { + mail($to, $this->subject, $content, $headers); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000000..0b83b968e8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Class to record a log on a NewRelic application + * + * @see https://newrelic.com/docs/php/new-relic-for-php + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * {@inheritDoc} + */ + public function __construct($level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + foreach ($record['context'] as $key => $parameter) { + newrelic_add_custom_parameter($key, $parameter); + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000000..3754e45dbc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param integer $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000000..c4da70e44f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + + private $highPriorityLevel; + private $emergencyLevel; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param integer $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param integer $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = $highPriorityLevel; + $this->emergencyLevel = $emergencyLevel; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + $message = substr($record['message'], 0, $maxMessageLength); + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp + ); + + if ($record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + } elseif ($record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + public function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000000..8835e666ed --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\AbstractProcessingHandler; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/dcramer/sentry) server + * using raven-php (https://github.com/getsentry/raven-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + private $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function($highest, $record) { + if ($record['level'] >= $highest['level']) { + $highest = $record; + + return $highest; + } + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + $options['extra']['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + + return; + } + + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000000..51a8e7df80 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + + # redis instance, key to use + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000000..0850134d51 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + + /** + * @param string $filename + * @param integer $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + + parent::__construct($this->getTimedFilename(), $level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $fileInfo = pathinfo($this->filename); + $glob = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-*'; + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + $logFiles = glob($glob); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + unlink($file); + } + } + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-'.date('Y-m-d'); + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000000..4faa327d63 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $persistent = false; + private $errno; + private $errstr; + + /** + * @param string $connectionString Socket connection string + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + public function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param type $boolean + */ + public function setPersistent($boolean) + { + $this->persistent = (boolean) $boolean; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return boolean + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return boolean + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds)*1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000000..96ce7fc0c0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + + /** + * @param string $stream + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } else { + $this->url = $stream; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (null === $this->stream) { + if (!$this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $errorMessage = null; + set_error_handler(function ($code, $msg) use (&$errorMessage) { + $errorMessage = preg_replace('{^fopen\(.*?\): }', '', $msg); + }); + $this->stream = fopen($this->url, 'a'); + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$errorMessage, $this->url)); + } + } + fwrite($this->stream, (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000000..ca03ccaa9a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + protected $message; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + $this->mailer = $mailer; + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + + $this->mailer->send($message); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000000..924ccf47d7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractProcessingHandler +{ + protected $ident; + protected $logopts; + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + private $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + private $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param string $ident + * @param mixed $facility + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->ident = $ident; + $this->logopts = $logopts; + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000000..085d9e1719 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function hasEmergency($record) + { + return $this->hasRecord($record, Logger::EMERGENCY); + } + + public function hasAlert($record) + { + return $this->hasRecord($record, Logger::ALERT); + } + + public function hasCritical($record) + { + return $this->hasRecord($record, Logger::CRITICAL); + } + + public function hasError($record) + { + return $this->hasRecord($record, Logger::ERROR); + } + + public function hasWarning($record) + { + return $this->hasRecord($record, Logger::WARNING); + } + + public function hasNotice($record) + { + return $this->hasRecord($record, Logger::NOTICE); + } + + public function hasInfo($record) + { + return $this->hasRecord($record, Logger::INFO); + } + + public function hasDebug($record) + { + return $this->hasRecord($record, Logger::DEBUG); + } + + public function hasEmergencyRecords() + { + return isset($this->recordsByLevel[Logger::EMERGENCY]); + } + + public function hasAlertRecords() + { + return isset($this->recordsByLevel[Logger::ALERT]); + } + + public function hasCriticalRecords() + { + return isset($this->recordsByLevel[Logger::CRITICAL]); + } + + public function hasErrorRecords() + { + return isset($this->recordsByLevel[Logger::ERROR]); + } + + public function hasWarningRecords() + { + return isset($this->recordsByLevel[Logger::WARNING]); + } + + public function hasNoticeRecords() + { + return isset($this->recordsByLevel[Logger::NOTICE]); + } + + public function hasInfoRecords() + { + return isset($this->recordsByLevel[Logger::INFO]); + } + + public function hasDebugRecords() + { + return isset($this->recordsByLevel[Logger::DEBUG]); + } + + protected function hasRecord($record, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + if (is_array($record)) { + $record = $record['message']; + } + + foreach ($this->recordsByLevel[$level] as $rec) { + if ($rec['message'] === $record) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000000..f22cf21874 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000000..65f9257505 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,574 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + protected static $levels = array( + 100 => 'DEBUG', + 200 => 'INFO', + 250 => 'NOTICE', + 300 => 'WARNING', + 400 => 'ERROR', + 500 => 'CRITICAL', + 550 => 'ALERT', + 600 => 'EMERGENCY', + ); + + /** + * @var DateTimeZone + */ + protected static $timezone; + + protected $name; + + /** + * The handler stack + * + * @var array of Monolog\Handler\HandlerInterface + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var array of callables + */ + protected $processors; + + /** + * @param string $name The logging channel + * @param array $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param array $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Adds a log record. + * + * @param integer $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => static::getLevelName($level), + 'channel' => $this->name, + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$timezone), + 'extra' => array(), + ); + // check if any handler will handle this message + $handlerKey = null; + foreach ($this->handlers as $key => $handler) { + if ($handler->isHandling($record)) { + $handlerKey = $key; + break; + } + } + // none found + if (null === $handlerKey) { + return false; + } + + // found at least one, process message and dispatch it + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + while (isset($this->handlers[$handlerKey]) && + false === $this->handlers[$handlerKey]->handle($record)) { + $handlerKey++; + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param integer $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param integer $level + * @return Boolean + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + $level = constant(__CLASS__.'::'.strtoupper($level)); + } + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000000..b126218ee4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $trace = debug_backtrace(); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + while (isset($trace[$i]['class']) && false !== strpos($trace[$i]['class'], 'Monolog\\')) { + $i++; + } + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i-1]['file']) ? $trace[$i-1]['file'] : null, + 'line' => isset($trace[$i-1]['line']) ? $trace[$i-1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000000..e48672bf2e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_peak_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000000..7551043e12 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + protected $realUsage; + + /** + * @param boolean $realUsage + */ + public function __construct($realUsage = true) + { + $this->realUsage = (boolean) $realUsage; + } + + /** + * Formats bytes into a human readable string + * + * @param int $bytes + * @return string + */ + protected static function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if ($bytes > 1024*1024) { + return round($bytes/1024/1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes/1024, 2).' KB'; + } + + return $bytes . ' B'; + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000000..2c4a8079f0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000000..9bd5e5eaa1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor +{ + private static $pid; + + public function __construct() + { + if (null === self::$pid) { + self::$pid = getmypid(); + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = self::$pid; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000000..b63fcccce3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + $replacements['{'.$key.'}'] = $val; + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000000..80270d08ad --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000000..9916cc087f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + protected $serverData; + + /** + * @param mixed $serverData array or object w/ ArrayAccess that provides access to the $_SERVER data + */ + public function __construct($serverData = null) + { + if (null === $serverData) { + $this->serverData =& $_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = array_merge( + $record['extra'], + array( + 'url' => $this->serverData['REQUEST_URI'], + 'ip' => isset($this->serverData['REMOTE_ADDR']) ? $this->serverData['REMOTE_ADDR'] : null, + 'http_method' => isset($this->serverData['REQUEST_METHOD']) ? $this->serverData['REQUEST_METHOD'] : null, + 'server' => isset($this->serverData['SERVER_NAME']) ? $this->serverData['SERVER_NAME'] : null, + 'referrer' => isset($this->serverData['HTTP_REFERER']) ? $this->serverData['HTTP_REFERER'] : null, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 0000000000..a9a3f301f8 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 0000000000..e7f7334e2c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info' + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn' + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 0000000000..a1db166af5 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Gelf\Message")) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals(3, $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000000..ba6152c943 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 0000000000..fa3fa59d03 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux"} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).', LogicException: Wut? at '.substr($path, 1, -1).':'.(__LINE__-12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 0000000000..6b012deaa7 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Formatter\LogstashFormatter; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 0000000000..7477871284 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: {})', + 'baz' => array(), + 'res' => '[resource]', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ) + ), $formatted); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 0000000000..0b07e330fd --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000000..65b530970e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +spl_autoload_register(function($class) { + $file = __DIR__.'/../../../../src/'.strtr($class, '\\', '/').'.php'; + if (file_exists($file)) { + require $file; + + return true; + } +}); + +use Monolog\Logger; +use Monolog\Handler\FirePHPHandler; +use Monolog\Handler\ChromePHPHandler; + +$logger = new Logger('firephp'); +$logger->pushHandler(new FirePHPHandler); +$logger->pushHandler(new ChromePHPHandler()); + +$logger->addDebug('Debug'); +$logger->addInfo('Info'); +$logger->addWarning('Warning'); +$logger->addError('Error'); diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000000..01d522fa41 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 0000000000..3485bdf34a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(5, count($handledRecord['extra'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php new file mode 100644 index 0000000000..3415c82c4f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +class AmqpExchangeMock extends \AMQPExchange +{ + protected $messages = array(); + + public function __construct() + { + } + + public function publish($message, $routing_key, $params = 0, $attributes = array()) + { + $this->messages[] = array($message, $routing_key, $params, $attributes); + + return true; + } + + public function getMessages() + { + return $this->messages; + } + + public function setName($name) + { + return true; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 0000000000..7369091696 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + } + + public function testHandle() + { + $exchange = $this->getExchange(); + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'Content-type' => 'application/json' + ) + ); + + $handler->handle($record); + + $messages = $exchange->getMessages(); + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + protected function getExchange() + { + return new AmqpExchangeMock(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 0000000000..beb08cf8a0 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testDestructPropagatesRecords() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->__destruct(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 0000000000..9fa4d50682 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::reset(); + } + + public function testHeaders() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150*1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100*1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150*1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$overflowed = false; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 0000000000..78a1d15cdc --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 0000000000..d67da90aec --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 0000000000..7713e6a93b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleRestartBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000000..2b7b76d984 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::reset(); + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 0000000000..8e9b9f8ad4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists("Gelf\MessagePublisher") || !class_exists("Gelf\Message")) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMocks.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new MockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php new file mode 100644 index 0000000000..dda8711406 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class MockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 0000000000..c6298a6e6e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 0000000000..c393cf42ab --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/from=Monolog&room_id=room1¬ify=0&message=test1&message_format=text&color=red$/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false) + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 0000000000..6754f3d623 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 0000000000..fbaab9bc3d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 0000000000..ce3433e17b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection')); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() {} + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 0000000000..50ceace0f0 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class NativeMailerHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 0000000000..bc7bcab8ad --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class NewRelicHandlerTest extends TestCase +{ + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_add_custom_parameter() +{ + return true; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 0000000000..292df78c7f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 0000000000..a7d24d2a14 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1', Logger::EMERGENCY); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 0000000000..9c13c3f4ad --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\RavenHandler; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists("Raven_Client")) { + $this->markTestSkipped("raven/raven not installed"); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format'); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 0000000000..3629f8a247 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 0000000000..f4cefda178 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writeable to test the RotatingFileHandler.'); + } + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 2).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 3).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 4).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true), + 'Rotation is not triggered when the file is already present' + => array(false), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 0000000000..c642bea8b9 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 0000000000..63d4fef6d4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('/foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 0000000000..98219ac12d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 0000000000..801d80a9a2 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->{'has'.$method}($record)); + $this->assertFalse($handler->{'has'.$method.'Records'}()); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar')); + $this->assertTrue($handler->{'has'.$method}($record)); + $this->assertTrue($handler->{'has'.$method}('test'.$method)); + $this->assertTrue($handler->{'has'.$method.'Records'}()); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 0000000000..416039e65d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'] + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 0000000000..8bcbbf90a1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 0000000000..9adbe1742b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(58, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(64, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } +} + +namespace Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 0000000000..4bdf22c363 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 0000000000..a30d6de630 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 0000000000..458d2a33a6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 0000000000..7ced62ca0e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 0000000000..04a5422136 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 0000000000..ab89944962 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/TestCase.php b/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 0000000000..1067b91974 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/TestCase.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error') + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/tests/bootstrap.php b/vendor/monolog/monolog/tests/bootstrap.php new file mode 100644 index 0000000000..189f4a68f7 --- /dev/null +++ b/vendor/monolog/monolog/tests/bootstrap.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$loader = require_once __DIR__ . "/../vendor/autoload.php"; +$loader->add('Monolog\\', __DIR__); diff --git a/vendor/php-ffmpeg/php-ffmpeg b/vendor/php-ffmpeg/php-ffmpeg new file mode 160000 index 0000000000..9fcb485d49 --- /dev/null +++ b/vendor/php-ffmpeg/php-ffmpeg @@ -0,0 +1 @@ +Subproject commit 9fcb485d497872e674cb14eb3df1386dbda9169b diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000000..474c952b4b --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +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/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000000..00f9034521 --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,120 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000000..67f852d1db --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000000..476bb962af --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,114 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000000..553a3c593a --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,27 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 0000000000..a932815111 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,116 @@ + " + * + * Example ->error('Foo') would yield "error Foo" + * + * @return string[] + */ + abstract function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + } + + public function testContextCanContainAnything() + { + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $this->getLogger()->warning('Random message', array('exception' => 'oops')); + $this->getLogger()->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + } +} + +class DummyTest +{ +} \ No newline at end of file diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000000..574bc1cb2a --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,45 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000000..6bdcc2194b --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,17 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md b/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md new file mode 100644 index 0000000000..3bad982fcb --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md @@ -0,0 +1,26 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php b/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..75c1c9e5d8 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..926ee2118b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php new file mode 100644 index 0000000000..be3d490dde --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php new file mode 100644 index 0000000000..890935933b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + parent::__construct( + sprintf( + 'The command "%s" failed.'."\nExit Code: %s(%s)\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getOutput(), + $process->getErrorOutput() + ) + ); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php new file mode 100644 index 0000000000..adead2536b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php b/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php new file mode 100644 index 0000000000..5cc99c7692 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = array('.exe', '.bat', '.cmd', '.com'); + + /** + * Replaces default suffixes of executable. + * + * @param array $suffixes + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = array()) + { + if (ini_get('open_basedir')) { + $searchPath = explode(PATH_SEPARATOR, getenv('open_basedir')); + $dirs = array(); + foreach ($searchPath as $path) { + if (is_dir($path)) { + $dirs[] = $path; + } else { + $file = str_replace(dirname($path), '', $path); + if ($file == $name && is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = array(''); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $pathExt = getenv('PATHEXT'); + $suffixes = $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes; + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && (defined('PHP_WINDOWS_VERSION_BUILD') || is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/LICENSE b/vendor/symfony/process/Symfony/Component/Process/LICENSE new file mode 100644 index 0000000000..88a57f8d8d --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +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/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php b/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php new file mode 100644 index 0000000000..6c9b8a1149 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find() + { + // PHP_BINARY return the current sapi executable + if (defined('PHP_BINARY') && PHP_BINARY && ('cli' === PHP_SAPI) && is_file(PHP_BINARY)) { + return PHP_BINARY; + } + + if ($php = getenv('PHP_PATH')) { + if (!is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (is_executable($php)) { + return $php; + } + } + + $dirs = array(PHP_BINDIR); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php b/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php new file mode 100644 index 0000000000..d146057e08 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + * + * @api + */ +class PhpProcess extends Process +{ + private $executableFinder; + + /** + * Constructor. + * + * @param string $script The PHP script to run (as a string) + * @param string $cwd The working directory + * @param array $env The environment variables + * @param integer $timeout The timeout in seconds + * @param array $options An array of options for proc_open + * + * @api + */ + public function __construct($script, $cwd = null, array $env = array(), $timeout = 60, array $options = array()) + { + parent::__construct(null, $cwd, $env, $script, $timeout, $options); + + $this->executableFinder = new PhpExecutableFinder(); + } + + /** + * Sets the path to the PHP binary to use. + * + * @api + */ + public function setPhpBinary($php) + { + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start($callback = null) + { + if (null === $this->getCommandLine()) { + if (false === $php = $this->executableFinder->find()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + $this->setCommandLine($php); + } + + parent::start($callback); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Process.php b/vendor/symfony/process/Symfony/Component/Process/Process.php new file mode 100644 index 0000000000..6d0b3c537b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Process.php @@ -0,0 +1,1291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Process is a thin wrapper around proc_* functions to ease + * start independent PHP processes. + * + * @author Fabien Potencier + * + * @api + */ +class Process +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $stdin; + private $starttime; + private $timeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility; + private $enhanceSigchildCompatibility; + private $pipes; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset; + private $incrementalErrorOutputOffset; + private $tty; + + private $fileHandles; + private $readBytes; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + * + * @var array + */ + public static $exitCodes = array( + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ); + + /** + * Constructor. + * + * @param string $commandline The command line to run + * @param string $cwd The working directory + * @param array $env The environment variables or null to inherit + * @param string $stdin The STDIN content + * @param integer $timeout The timeout in seconds + * @param array $options An array of options for proc_open + * + * @throws RuntimeException When proc_open is not installed + * + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + if (!function_exists('proc_open')) { + throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + // on windows, if the cwd changed via chdir(), proc_open defaults to the dir where php was started + // on gnu/linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/bug.php?id=51800 + // @see : https://bugs.php.net/bug.php?id=50524 + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || defined('PHP_WINDOWS_VERSION_BUILD'))) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } else { + $this->env = null; + } + $this->stdin = $stdin; + $this->setTimeout($timeout); + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled(); + $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); + } + + public function __destruct() + { + // stop() will check if we have a process running. + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callback|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return integer The exit status code + * + * @throws RuntimeException When process can't be launch or is stopped + * + * @api + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * Starts the process and returns after sending the STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * If there is no callback passed, the wait() method can be called + * with true as a second parameter then the callback will get all data occurred + * in (and since) the start call. + * + * @param callback|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launch or is stopped + * @throws RuntimeException When process is already running + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + + $this->resetProcessData(); + $this->starttime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"'; + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + + $this->writePipes(); + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The new process + * + * @throws \RuntimeException When process can't be launch or is stopped + * @throws \RuntimeException When process is already running + * + * @see start() + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callback|null $callback A valid PHP callback + * + * @return integer The exitcode of the process + * + * @throws \RuntimeException When process timed out + * @throws \RuntimeException When process stopped after receiving signal + */ + public function wait($callback = null) + { + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) { + $this->checkTimeout(); + $this->readPipes(true); + } + $this->updateStatus(false); + if ($this->processInformation['signaled']) { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('The process has been signaled.'); + } + + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + $time = 0; + while ($this->isRunning() && $time < 1000000) { + $time += 1000; + usleep(1000); + } + + if ($this->processInformation['signaled']) { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('The process has been signaled.'); + } + + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return integer|null The process id if running, null otherwise + * + * @throws RuntimeException In case --enable-sigchild is activated + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a posix signal to the process. + * + * @param integer $signal A valid posix signal (see http://www.php.net/manual/en/pcntl.constants.php) + * @return Process + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + if (!$this->isRunning()) { + throw new LogicException('Can not send signal on a non running process.'); + } + + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + if (true !== @proc_terminate($this->process, $signal)) { + throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal)); + } + + return $this; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @api + */ + public function getOutput() + { + $this->readPipes(false); + + return $this->stdout; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + */ + public function getIncrementalOutput() + { + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @api + */ + public function getErrorOutput() + { + $this->readPipes(false); + + return $this->stderr; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + */ + public function getIncrementalErrorOutput() + { + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * Returns the exit code returned by the process. + * + * @return integer The exit status code + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + * + * @api + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string A string representation for the exit status code + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + $exitcode = $this->getExitCode(); + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return Boolean true if the process ended successfully, false otherwise + * + * @api + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return Boolean + * + * @throws RuntimeException In case --enable-sigchild is activated + * + * @api + */ + public function hasBeenSignaled() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return integer + * + * @throws RuntimeException In case --enable-sigchild is activated + * + * @api + */ + public function getTermSignal() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return Boolean + * + * @api + */ + public function hasBeenStopped() + { + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return integer + * + * @api + */ + public function getStopSignal() + { + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return Boolean true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return Boolean true if status is ready, false otherwise + */ + public function isStarted() + { + return $this->status != self::STATUS_READY; + } + + /** + * Checks if the process is terminated. + * + * @return Boolean true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return $this->status == self::STATUS_TERMINATED; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param integer|float $timeout The timeout in seconds + * @param integer $signal A posix signal to send in case the process has not stop at timeout, default is SIGKILL + * + * @return integer The exit-code of the process + * + * @throws RuntimeException if the process got signaled + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + proc_terminate($this->process); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning() && !$this->isSigchildEnabled()) { + if (null !== $signal || defined('SIGKILL')) { + $this->signal($signal ?: SIGKILL); + } + } + + $this->updateStatus(false); + } + $this->status = self::STATUS_TERMINATED; + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @param string $line The line to append + */ + public function addOutput($line) + { + $this->stdout .= $line; + } + + /** + * Adds a line to the STDERR stream. + * + * @param string $line The line to append + */ + public function addErrorOutput($line) + { + $this->stderr .= $line; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string $commandline The command to execute + * + * @return self The current Process instance + */ + public function setCommandLine($commandline) + { + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout. + * + * @return integer|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param boolean $tty True to enabled and false to disable + * + * @return self The current Process instance + */ + public function setTty($tty) + { + $this->tty = (Boolean) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return Boolean true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Gets the working directory. + * + * @return string The current working directory + */ + public function getWorkingDirectory() + { + // This is for BC only + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return self The current Process instance + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * An environment variable value should be a string. + * If it is an array, the variable is ignored. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return self The current Process instance + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { if (!is_array($value)) { return true; } }); + + $this->env = array(); + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * Gets the contents of STDIN. + * + * @return string The current contents + */ + public function getStdin() + { + return $this->stdin; + } + + /** + * Sets the contents of STDIN. + * + * @param string $stdin The new contents + * + * @return self The current Process instance + */ + public function setStdin($stdin) + { + $this->stdin = $stdin; + + return $this; + } + + /** + * Gets the options for proc_open. + * + * @return array The current options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options for proc_open. + * + * @param array $options The new options + * + * @return self The current Process instance + */ + public function setOptions(array $options) + { + $this->options = $options; + + return $this; + } + + /** + * Gets whether or not Windows compatibility is enabled. + * + * This is true by default. + * + * @return Boolean + */ + public function getEnhanceWindowsCompatibility() + { + return $this->enhanceWindowsCompatibility; + } + + /** + * Sets whether or not Windows compatibility is enabled. + * + * @param Boolean $enhance + * + * @return self The current Process instance + */ + public function setEnhanceWindowsCompatibility($enhance) + { + $this->enhanceWindowsCompatibility = (Boolean) $enhance; + + return $this; + } + + /** + * Returns whether sigchild compatibility mode is activated or not. + * + * @return Boolean + */ + public function getEnhanceSigchildCompatibility() + { + return $this->enhanceSigchildCompatibility; + } + + /** + * Activates sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option + * + * @param Boolean $enhance + * + * @return self The current Process instance + */ + public function setEnhanceSigchildCompatibility($enhance) + { + $this->enhanceSigchildCompatibility = (Boolean) $enhance; + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws RuntimeException In case the timeout was reached + */ + public function checkTimeout() + { + if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new RuntimeException('The process timed-out.'); + } + } + + /** + * Creates the descriptors needed by the proc_open. + * + * @return array + */ + private function getDescriptors() + { + //Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + //Workaround for this problem is to use temporary files instead of pipes on Windows platform. + //@see https://bugs.php.net/bug.php?id=51800 + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->fileHandles = array( + self::STDOUT => tmpfile(), + ); + if (false === $this->fileHandles[self::STDOUT]) { + throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + $this->readBytes = array( + self::STDOUT => 0, + ); + + return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w')); + } + + if ($this->tty) { + $descriptors = array( + array('file', '/dev/tty', 'r'), + array('file', '/dev/tty', 'w'), + array('file', '/dev/tty', 'w'), + ); + } else { + $descriptors = array( + array('pipe', 'r'), // stdin + array('pipe', 'w'), // stdout + array('pipe', 'w'), // stderr + ); + } + + if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors = array_merge($descriptors, array(array('pipe', 'w'))); + + $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callback|null $callback The user defined PHP callback + * + * @return callback A PHP callable + */ + protected function buildCallback($callback) + { + $that = $this; + $out = self::OUT; + $err = self::ERR; + $callback = function ($type, $data) use ($that, $callback, $out, $err) { + if ($out == $type) { + $that->addOutput($data); + } else { + $that->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param Boolean $blocking Whether to use a clocking read call. + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->readPipes($blocking); + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + if (!$this->processInformation['running']) { + $this->close(); + $this->status = self::STATUS_TERMINATED; + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return Boolean + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Handles the windows file handles fallbacks. + * + * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed + */ + private function processFileHandles($closeEmptyHandles = false) + { + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + fseek($fileHandle, $this->readBytes[$type]); + $data = fread($fileHandle, 8192); + if (strlen($data) > 0) { + $this->readBytes[$type] += strlen($data); + call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data); + } + if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) { + fclose($fileHandle); + unset($this->fileHandles[$type]); + } + } + } + + /** + * Returns true if a system call has been interrupted. + * + * @return Boolean + */ + private function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + /** + * Reads pipes, executes callback. + * + * @param Boolean $blocking Whether to use blocking calls or not. + */ + private function readPipes($blocking) + { + if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) { + $this->processFileHandles(!$this->pipes); + } + + if ($this->pipes) { + $r = $this->pipes; + $w = null; + $e = null; + + // let's have a look if something changed in streams + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(self::TIMEOUT_PRECISION * 1E6) : 0)) { + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occured, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = array(); + } + + return; + } + + // nothing has changed + if (0 === $n) { + return; + } + + $this->processReadPipes($r); + } + } + + /** + * Writes data to pipes. + * + * @param Boolean $blocking Whether to use blocking calls or not. + */ + private function writePipes() + { + if ($this->tty) { + $this->status = self::STATUS_TERMINATED; + + return; + } + + if (null === $this->stdin) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + + return; + } + + $writePipes = array($this->pipes[0]); + unset($this->pipes[0]); + $stdinLen = strlen($this->stdin); + $stdinOffset = 0; + + while ($writePipes) { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->processFileHandles(); + } + + $r = $this->pipes; + $w = $writePipes; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::TIMEOUT_PRECISION * 1E6) : 0)) { + // if a system call has been interrupted, forget about it, let's try again + if ($this->hasSystemCallBeenInterrupted()) { + continue; + } + break; + } + + // nothing has changed, let's wait until the process is ready + if (0 === $n) { + continue; + } + + if ($w) { + $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192); + if (false !== $written) { + $stdinOffset += $written; + } + if ($stdinOffset >= $stdinLen) { + fclose($writePipes[0]); + $writePipes = null; + } + } + + $this->processReadPipes($r); + } + } + + /** + * Processes read pipes, executes callback on it. + * + * @param array $pipes + */ + private function processReadPipes(array $pipes) + { + foreach ($pipes as $pipe) { + $type = array_search($pipe, $this->pipes); + $data = fread($pipe, 8192); + + if (strlen($data) > 0) { + // last exit code is output and caught to work around --enable-sigchild + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data); + } + } + if (false === $data || feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + } + + /** + * Captures the exitcode if mentioned in the process informations. + */ + private function captureExitCode() + { + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return Integer The exitcode + */ + private function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + + $this->pipes = null; + $exitcode = -1; + + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } + + $this->exitcode = $this->exitcode !== null ? $this->exitcode : -1; + $this->exitcode = -1 != $exitcode ? $exitcode : $this->exitcode; + + if (-1 == $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + foreach ($this->fileHandles as $fileHandle) { + fclose($fileHandle); + } + $this->fileHandles = array(); + } + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->pipes = null; + $this->process = null; + $this->status = self::STATUS_READY; + $this->fileHandles = null; + $this->readBytes = null; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php b/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php new file mode 100644 index 0000000000..ddd064a2b8 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; + +/** + * Process builder. + * + * @author Kris Wallsmith + */ +class ProcessBuilder +{ + private $arguments; + private $cwd; + private $env; + private $stdin; + private $timeout; + private $options; + private $inheritEnv; + private $prefix; + + public function __construct(array $arguments = array()) + { + $this->arguments = $arguments; + + $this->timeout = 60; + $this->options = array(); + $this->env = array(); + $this->inheritEnv = true; + } + + public static function create(array $arguments = array()) + { + return new static($arguments); + } + + /** + * Adds an unescaped argument to the command string. + * + * @param string $argument A command argument + * + * @return ProcessBuilder + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Adds an unescaped prefix to the command string. + * + * The prefix is preserved when reseting arguments. + * + * @param string $prefix A command prefix + * + * @return ProcessBuilder + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + + return $this; + } + + /** + * @param array $arguments + * + * @return ProcessBuilder + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + public function setInput($stdin) + { + $this->stdin = $stdin; + + return $this; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null + * + * @return ProcessBuilder + * + * @throws InvalidArgumentException + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + public function getProcess() + { + if (!$this->prefix && !count($this->arguments)) { + throw new LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments; + $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments)); + + if ($this->inheritEnv) { + $env = $this->env ? $this->env + $_ENV : null; + } else { + $env = $this->env; + } + + return new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $options); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php b/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php new file mode 100644 index 0000000000..4a5b7d6073 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() + { + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escapeArgument($argument) + { + //Fix for PHP bug #43784 escapeshellarg removes % from given string + //Fix for PHP bug #49446 escapeshellarg dosn`t work on windows + //@see https://bugs.php.net/bug.php?id=43784 + //@see https://bugs.php.net/bug.php?id=49446 + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if ('' === $argument) { + return escapeshellarg($argument); + } + + $escapedArgument = ''; + foreach (preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif ('%' === $part) { + $escapedArgument .= '^%'; + } else { + $escapedArgument .= escapeshellarg($part); + } + } + + return $escapedArgument; + } + + return escapeshellarg($argument); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/README.md b/vendor/symfony/process/Symfony/Component/Process/README.md new file mode 100644 index 0000000000..7b9f30757b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/README.md @@ -0,0 +1,47 @@ +Process Component +================= + +Process executes commands in sub-processes. + +In this example, we run a simple directory listing and get the result back: + + use Symfony\Component\Process\Process; + + $process = new Process('ls -lsa'); + $process->setTimeout(3600); + $process->run(); + if (!$process->isSuccessful()) { + throw new RuntimeException($process->getErrorOutput()); + } + + print $process->getOutput(); + +You can think that this is easy to achieve with plain PHP but it's not especially +if you want to take care of the subtle differences between the different platforms. + +And if you want to be able to get some feedback in real-time, just pass an +anonymous function to the ``run()`` method and you will get the output buffer +as it becomes available: + + use Symfony\Component\Process\Process; + + $process = new Process('ls -lsa'); + $process->run(function ($type, $buffer) { + if ('err' === $type) { + echo 'ERR > '.$buffer; + } else { + echo 'OUT > '.$buffer; + } + }); + +That's great if you want to execute a long running command (like rsync-ing files to a +remote server) and give feedback to the user in real-time. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/XXX/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php new file mode 100644 index 0000000000..d0228f0e55 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -0,0 +1,641 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * @author Robert Schönthal + */ +abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromConstructor() + { + $this->getProcess('', null, null, null, -1); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $p = $this->getProcess(''); + $p->setTimeout(-1); + } + + public function testNullTimeout() + { + $p = $this->getProcess(''); + $p->setTimeout(10); + $p->setTimeout(null); + + $this->assertNull($p->getTimeout()); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->verifyPosixIsEnabled(); + + // exec is mandatory here since we send a signal to the process + // see https://github.com/symfony/symfony/issues/5030 about prepending + // command with exec + $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3'); + $p->start(); + usleep(100000); + $start = microtime(true); + $p->stop(1.1, SIGKILL); + while ($p->isRunning()) { + usleep(1000); + } + $duration = microtime(true) - $start; + + $this->assertLessThan(1.8, $duration); + } + + public function testCallbacksAreExecutedWithStart() + { + $data = ''; + + $process = $this->getProcess('echo "foo";sleep 1;echo "foo"'); + $process->start(function ($type, $buffer) use (&$data) { + $data .= $buffer; + }); + + $start = microtime(true); + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertEquals("foo\nfoo\n", $data); + } + + /** + * tests results from sub processes + * + * @dataProvider responsesCodeProvider + */ + public function testProcessResponses($expected, $getter, $code) + { + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); + $p->run(); + + $this->assertSame($expected, $p->$getter()); + } + + /** + * tests results from sub processes + * + * @dataProvider pipesCodeProvider + */ + public function testProcessPipes($code, $size) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Test hangs on Windows & PHP due to https://bugs.php.net/bug.php?id=60120 and https://bugs.php.net/bug.php?id=51800'); + } + + $expected = str_repeat(str_repeat('*', 1024), $size) . '!'; + $expectedLength = (1024 * $size) + 1; + + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); + $p->setStdin($expected); + $p->run(); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + public function chainedCommandsOutputProvider() + { + return array( + array("1\n1\n", ';', '1'), + array("2\n2\n", '&&', '2'), + ); + } + + /** + * + * @dataProvider chainedCommandsOutputProvider + */ + public function testChainedCommandsOutput($expected, $operator, $input) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Does it work on windows ?'); + } + + $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input)); + $process->run(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCallbackIsExecutedForOutput() + { + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';'))); + + $called = false; + $p->run(function ($type, $buffer) use (&$called) { + $called = $buffer === 'foo'; + }); + + $this->assertTrue($called, 'The callback should be executed with the output'); + } + + public function testGetErrorOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches)); + } + + public function testGetIncrementalErrorOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(50000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->start(); + while ($p->isRunning()) { + $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches)); + usleep(20000); + } + } + + public function testGetOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); + } + + public function testGetIncrementalOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) { echo \' foo \'; usleep(50000); $n++; }'))); + + $p->start(); + while ($p->isRunning()) { + $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches)); + usleep(20000); + } + } + + public function testExitCodeCommandFailed() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX exit code'); + } + + // such command run in bash return an exitcode 127 + $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); + $process->run(); + + $this->assertGreaterThan(0, $process->getExitCode()); + } + + public function testTTYCommand() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does have /dev/tty support'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTTY(true); + $process->run(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testExitCodeText() + { + $process = $this->getProcess(''); + $r = new \ReflectionObject($process); + $p = $r->getProperty('exitcode'); + $p->setAccessible(true); + + $p->setValue($process, 2); + $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText()); + } + + public function testStartIsNonBlocking() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $start = microtime(true); + $process->start(); + $end = microtime(true); + $this->assertLessThan(1 , $end-$start); + } + + public function testUpdateStatus() + { + $process = $this->getProcess('php -h'); + $process->run(); + $this->assertTrue(strlen($process->getOutput()) > 0); + } + + public function testGetExitCodeIsNullOnStart() + { + $process = $this->getProcess('php -r "usleep(200000);"'); + $this->assertNull($process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + $process = $this->getProcess('php -r "usleep(200000);"'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCode() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testStatus() + { + $process = $this->getProcess('php -r "usleep(500000);"'); + $this->assertFalse($process->isRunning()); + $this->assertFalse($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_READY, $process->getStatus()); + $process->start(); + $this->assertTrue($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_STARTED, $process->getStatus()); + $process->wait(); + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertTrue($process->isTerminated()); + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testStop() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isRunning()); + } + + public function testIsSuccessful() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertTrue($process->isSuccessful()); + } + + public function testIsSuccessfulOnlyAfterTerminated() + { + $process = $this->getProcess('sleep 1'); + $process->start(); + while ($process->isRunning()) { + $this->assertFalse($process->isSuccessful()); + usleep(300000); + } + + $this->assertTrue($process->isSuccessful()); + } + + public function testIsNotSuccessful() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isSuccessful()); + } + + public function testProcessIsNotSignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignalIsNotSignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertEquals(0, $process->getTermSignal()); + } + + public function testProcessIsSignaledIfStopped() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $process->stop(); + $this->assertTrue($process->hasBeenSignaled()); + } + + public function testProcessWithTermSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + // SIGTERM is only defined if pcntl extension is present + $termSignal = defined('SIGTERM') ? SIGTERM : 15; + + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $process->stop(); + + $this->assertEquals($termSignal, $process->getTermSignal()); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + if (!function_exists('posix_kill')) { + $this->markTestSkipped('posix_kill is required for this test'); + } + + $termSignal = defined('SIGKILL') ? SIGKILL : 9; + + $process = $this->getProcess('exec php -r "while (true) {}"'); + $process->start(); + posix_kill($process->getPid(), $termSignal); + + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".'); + $process->wait(); + } + + public function testRestart() + { + $process1 = $this->getProcess('php -r "echo getmypid();"'); + $process1->run(); + $process2 = $process1->restart(); + + usleep(300000); // wait for output + + // Ensure that both processed finished and the output is numeric + $this->assertFalse($process1->isRunning()); + $this->assertFalse($process2->isRunning()); + $this->assertTrue(is_numeric($process1->getOutput())); + $this->assertTrue(is_numeric($process2->getOutput())); + + // Ensure that restart returned a new process by check that the output is different + $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); + } + + public function testPhpDeadlock() + { + $this->markTestSkipped('Can course php to hang'); + + // Sleep doesn't work as it will allow the process to handle signals and close + // file handles from the other end. + $process = $this->getProcess('php -r "while (true) {}"'); + $process->start(); + + // PHP will deadlock when it tries to cleanup $process + } + + public function testRunProcessWithTimeout() + { + $timeout = 0.5; + $process = $this->getProcess('sleep 3'); + $process->setTimeout($timeout); + $start = microtime(true); + try { + $process->run(); + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + + } + $duration = microtime(true) - $start; + + $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration); + } + + public function testCheckTimeoutOnStartedProcess() + { + $timeout = 0.5; + $precision = 100000; + $process = $this->getProcess('sleep 3'); + $process->setTimeout($timeout); + $start = microtime(true); + + $process->start(); + + try { + while ($process->isRunning()) { + $process->checkTimeout(); + usleep($precision); + } + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + + } + $duration = microtime(true) - $start; + + $this->assertLessThan($timeout + $precision, $duration); + $this->assertFalse($process->isSuccessful()); + } + + public function testGetPid() + { + $process = $this->getProcess('php -r "sleep(1);"'); + $process->start(); + $this->assertGreaterThan(0, $process->getPid()); + $process->stop(); + } + + public function testGetPidIsNullBeforeStart() + { + $process = $this->getProcess('php -r "sleep(1);"'); + $this->assertNull($process->getPid()); + } + + public function testGetPidIsNullAfterRun() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertNull($process->getPid()); + } + + public function testSignal() + { + $this->verifyPosixIsEnabled(); + + $process = $this->getProcess('exec php -f ' . __DIR__ . '/SignalListener.php'); + $process->start(); + usleep(500000); + $process->signal(SIGUSR1); + + while ($process->isRunning() && false === strpos($process->getoutput(), 'Caught SIGUSR1')) { + usleep(10000); + } + + $this->assertEquals('Caught SIGUSR1', $process->getOutput()); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->verifyPosixIsEnabled(); + + $process = $this->getProcess('sleep 4'); + $process->start(); + $process->signal(SIGKILL); + + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->hasBeenSignaled()); + $this->assertFalse($process->isSuccessful()); + $this->assertEquals(137, $process->getExitCode()); + } + + /** + * @expectedException Symfony\Component\Process\Exception\LogicException + */ + public function testSignalProcessNotRunning() + { + $this->verifyPosixIsEnabled(); + $process = $this->getProcess('php -m'); + $process->signal(SIGHUP); + } + + private function verifyPosixIsEnabled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + if (!defined('SIGUSR1')) { + $this->markTestSkipped('The pcntl extension is not enabled'); + } + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongIntSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + + $process = $this->getProcess('php -r "sleep(3);"'); + $process->start(); + $process->signal(-4); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongNonIntSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + + $process = $this->getProcess('php -r "sleep(3);"'); + $process->start(); + $process->signal('Céphalopodes'); + } + + public function responsesCodeProvider() + { + return array( + //expected output / getter / code to execute + //array(1,'getExitCode','exit(1);'), + //array(true,'isSuccessful','exit();'), + array('output', 'getOutput', 'echo \'output\';'), + ); + } + + public function pipesCodeProvider() + { + $variations = array( + 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);', + 'include \''.__DIR__.'/ProcessTestHelper.php\';', + ); + + $codes = array(); + foreach (array(1, 16, 64, 1024, 4096) as $size) { + foreach ($variations as $code) { + $codes[] = array($code, $size); + } + } + + return $codes; + } + + /** + * provides default method names for simple getter/setter + */ + public function methodProvider() + { + $defaults = array( + array('CommandLine'), + array('Timeout'), + array('WorkingDirectory'), + array('Env'), + array('Stdin'), + array('Options') + ); + + return $defaults; + } + + /** + * @param string $commandline + * @param null $cwd + * @param array $env + * @param null $stdin + * @param integer $timeout + * @param array $options + * + * @return Process + */ + abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()); +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php b/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php new file mode 100644 index 0000000000..a4db838256 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php @@ -0,0 +1,37 @@ + (microtime(true) - $start)) { + usleep(1000); +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php new file mode 100644 index 0000000000..99c4a1e7c9 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Robert Schönthal + */ +class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests find() with the env var PHP_PATH + */ + public function testFindWithPhpPath() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //not executable PHP_PATH + putenv('PHP_PATH=/not/executable/php'); + $this->assertFalse($f->find(), '::find() returns false for not executable php'); + + //executable PHP_PATH + putenv('PHP_PATH='.$current); + $this->assertEquals($f->find(), $current, '::find() returns the executable php'); + } + + /** + * tests find() with default executable + */ + public function testFindWithSuffix() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + putenv('PHP_PATH='); + putenv('PHP_PEAR_PHP_BIN='); + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //TODO maybe php executable is custom or even windows + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertTrue(is_executable($current)); + $this->assertTrue((bool) preg_match('/'.addSlashes(DIRECTORY_SEPARATOR).'php\.(exe|bat|cmd|com)$/i', $current), '::find() returns the executable php with suffixes'); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php new file mode 100644 index 0000000000..df66ad624b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\PhpProcess; + +class PhpProcessTest extends \PHPUnit_Framework_TestCase +{ + public function testNonBlockingWorks() + { + $expected = 'hello world!'; + $process = new PhpProcess(<<start(); + $process->wait(); + $this->assertEquals($expected, $process->getOutput()); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php new file mode 100644 index 0000000000..4c88b55ce1 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessBuilder; + +class ProcessBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testInheritEnvironmentVars() + { + $snapshot = $_ENV; + $_ENV = $expected = array('foo' => 'bar'); + + $pb = new ProcessBuilder(); + $pb->add('foo')->inheritEnvironmentVariables(); + $proc = $pb->getProcess(); + + $this->assertNull($proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + public function testProcessShouldInheritAndOverrideEnvironmentVars() + { + $snapshot = $_ENV; + $_ENV = array('foo' => 'bar', 'bar' => 'baz'); + $expected = array('foo' => 'foo', 'bar' => 'baz'); + + $pb = new ProcessBuilder(); + $pb->add('foo')->inheritEnvironmentVariables() + ->setEnv('foo', 'foo'); + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + public function testProcessBuilderShouldNotPassEnvArrays() + { + $snapshot = $_ENV; + $_ENV = array('a' => array('b', 'c'), 'd' => 'e', 'f' => 'g'); + $expected = array('d' => 'e', 'f' => 'g'); + + $pb = new ProcessBuilder(); + $pb->add('a')->inheritEnvironmentVariables() + ->setEnv('d', 'e'); + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() removes array values from $_ENV'); + + $_ENV = $snapshot; + } + + public function testInheritEnvironmentVarsByDefault() + { + $pb = new ProcessBuilder(); + $proc = $pb->add('foo')->getProcess(); + + $this->assertNull($proc->getEnv()); + } + + public function testNotReplaceExplicitlySetVars() + { + $snapshot = $_ENV; + $_ENV = array('foo' => 'bar'); + $expected = array('foo' => 'baz'); + + $pb = new ProcessBuilder(); + $pb + ->setEnv('foo', 'baz') + ->inheritEnvironmentVariables() + ->add('foo') + ; + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(-1); + } + + public function testNullTimeout() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(10); + $pb->setTimeout(null); + + $r = new \ReflectionObject($pb); + $p = $r->getProperty('timeout'); + $p->setAccessible(true); + + $this->assertNull($p->getValue($pb)); + } + + public function testShouldSetArguments() + { + $pb = new ProcessBuilder(array('initial')); + $pb->setArguments(array('second')); + + $proc = $pb->getProcess(); + + $this->assertContains("second", $proc->getCommandLine()); + } + + public function testPrefixIsPrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix('/usr/bin/php'); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArguments() + { + $pb = new ProcessBuilder(array('%path%', 'foo " bar')); + $proc = $pb->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine()); + } else { + $this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArgumentsAndPrefix() + { + $pb = new ProcessBuilder(array('arg')); + $pb->setPrefix('%prefix%'); + $proc = $pb->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine()); + } else { + $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine()); + } + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + */ + public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument() + { + ProcessBuilder::create()->getProcess(); + } + + public function testShouldNotThrowALogicExceptionIfNoArgument() + { + $process = ProcessBuilder::create() + ->setPrefix('/usr/bin/php') + ->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldNotThrowALogicExceptionIfNoPrefix() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php new file mode 100644 index 0000000000..5da55e75ec --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Exception\ProcessFailedException; + +/** + * @author Sebastian Marek + */ +class ProcessFailedExceptionTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests ProcessFailedException throws exception if the process was successful + */ + public function testProcessFailedExceptionThrowsException() + { + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful'), + array('php') + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(true)); + + $this->setExpectedException( + '\InvalidArgumentException', + 'Expected a failed process, but the given process was successful.' + ); + + new ProcessFailedException($process); + } + + /** + * tests ProcessFailedException uses information from process output + * to generate exception message + */ + public function testProcessFailedExceptionPopulatesInformationFromProcessOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $output = "Command output"; + $errorOutput = "FATAL: Unexpected error"; + + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText'), + array($cmd) + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + $process->expects($this->once()) + ->method('getOutput') + ->will($this->returnValue($output)); + $process->expects($this->once()) + ->method('getErrorOutput') + ->will($this->returnValue($errorOutput)); + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\nExit Code: $exitCode($exitText)\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}", + $exception->getMessage() + ); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php new file mode 100644 index 0000000000..3977bcdcf1 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class ProcessInSigchildEnvironment extends Process +{ + protected function isSigchildEnabled() + { + return true; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php new file mode 100644 index 0000000000..25cfb41f93 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php @@ -0,0 +1,63 @@ + 0) { + $written = fwrite(STDOUT, (binary) $out, 1024); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $out = (binary) substr($out, $written); + } + if (null === $read && strlen($out) < 1) { + $write = array_diff($write, array(STDOUT)); + } + + if (in_array(STDERR, $w) && strlen($err) > 0) { + $written = fwrite(STDERR, (binary) $err, 1024); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $err = (binary) substr($err, $written); + } + if (null === $read && strlen($err) < 1) { + $write = array_diff($write, array(STDERR)); + } + + if ($r) { + $str = fread(STDIN, 1024); + if (false !== $str) { + $out .= $str; + $err .= $str; + } + if (false === $str || feof(STDIN)) { + $read = null; + if (!feof(STDIN)) { + die(ERR_READ_FAILED); + } + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php new file mode 100644 index 0000000000..603fac53e7 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessUtils; + +class ProcessUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider dataArguments + */ + public function testEscapeArgument($result, $argument) + { + $this->assertSame($result, ProcessUtils::escapeArgument($argument)); + } + + public function dataArguments() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return array( + array('"foo bar"', 'foo bar'), + array('^%"path"^%', '%path%'), + array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'), + array('""', ''), + ); + } + + return array( + array("'foo bar'", 'foo bar'), + array("'%path%'", '%path%'), + array("'<|>\" \"'\\''f'", '<|>" "\'f'), + array("''", ''), + ); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php new file mode 100644 index 0000000000..29f3cd9a04 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +class SigchildDisabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCode() + { + parent::testGetExitCode(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCodeIsNullOnStart() + { + parent::testGetExitCodeIsNullOnStart(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + parent::testGetExitCodeIsNullOnWhenStartingAgain(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testExitCodeCommandFailed() + { + parent::testExitCodeCommandFailed(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testCheckTimeoutOnStartedProcess() + { + parent::testCheckTimeoutOnStartedProcess(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $process->getExitCodeText(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsSuccessful() + { + parent::testIsSuccessful(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsSuccessfulOnlyAfterTerminated() + { + parent::testIsSuccessfulOnlyAfterTerminated(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsNotSuccessful() + { + parent::testIsNotSuccessful(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); + $process->setEnhanceSigchildCompatibility(false); + + return $process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php new file mode 100644 index 0000000000..296b00dfcb --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +class SigchildEnabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $this->assertInternalType('string', $process->getExitCodeText()); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); + $process->setEnhanceSigchildCompatibility(true); + + return $process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php new file mode 100644 index 0000000000..0bf191e371 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class SimpleProcessTest extends AbstractProcessTest +{ + private $enabledSigchild = false; + + public function setUp() + { + ob_start(); + phpinfo(INFO_GENERAL); + + $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + public function testGetExitCode() + { + $this->skipIfPHPSigchild(); + parent::testGetExitCode(); + } + + public function testExitCodeCommandFailed() + { + $this->skipIfPHPSigchild(); + parent::testExitCodeCommandFailed(); + } + + public function testProcessIsSignaledIfStopped() + { + $this->skipIfPHPSigchild(); + parent::testProcessIsSignaledIfStopped(); + } + + public function testProcessWithTermSignal() + { + $this->skipIfPHPSigchild(); + parent::testProcessWithTermSignal(); + } + + public function testProcessIsNotSignaled() + { + $this->skipIfPHPSigchild(); + parent::testProcessIsNotSignaled(); + } + + public function testProcessWithoutTermSignal() + { + $this->skipIfPHPSigchild(); + parent::testProcessWithoutTermSignal(); + } + + public function testExitCodeText() + { + $this->skipIfPHPSigchild(); + parent::testExitCodeText(); + } + + public function testIsSuccessful() + { + $this->skipIfPHPSigchild(); + parent::testIsSuccessful(); + } + + public function testIsNotSuccessful() + { + $this->skipIfPHPSigchild(); + parent::testIsNotSuccessful(); + } + + public function testGetPid() + { + $this->skipIfPHPSigchild(); + parent::testGetPid(); + } + + public function testGetPidIsNullBeforeStart() + { + $this->skipIfPHPSigchild(); + parent::testGetPidIsNullBeforeStart(); + } + + public function testGetPidIsNullAfterRun() + { + $this->skipIfPHPSigchild(); + parent::testGetPidIsNullAfterRun(); + } + + public function testSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\LogicException + */ + public function testSignalProcessNotRunning() + { + $this->skipIfPHPSigchild(); + parent::testSignalProcessNotRunning(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongIntSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignalWithWrongIntSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongNonIntSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignalWithWrongNonIntSignal(); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + return new Process($commandline, $cwd, $env, $stdin, $timeout, $options); + } + + private function skipIfPHPSigchild() + { + if ($this->enabledSigchild) { + $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/composer.json b/vendor/symfony/process/Symfony/Component/Process/composer.json new file mode 100644 index 0000000000..427e63b87f --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Symfony Process Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Process\\": "" } + }, + "target-dir": "Symfony/Component/Process", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist b/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist new file mode 100644 index 0000000000..9d5830f9e2 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + + + + From 97e90eede2b24f0a434520e05f1ab13fb6ce975a Mon Sep 17 00:00:00 2001 From: Hubert Borderiou Date: Fri, 30 Aug 2013 16:34:08 +0200 Subject: [PATCH 14/35] Detect missing date.timezone at install time - ref #6514 --- main/install/install.lib.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/main/install/install.lib.php b/main/install/install.lib.php index 14083b26be..0d4e1f150f 100755 --- a/main/install/install.lib.php +++ b/main/install/install.lib.php @@ -93,6 +93,18 @@ function check_php_setting($php_setting, $recommended_value, $return_success = f } } + +/** + * This function return the value of a php.ini setting if not "" or if exists, otherwise return false + */ +function check_php_setting_exists($php_setting) { + if (ini_get($php_setting) != "") { + return ini_get($php_setting); + } + return false; +} + + /** * Returns a textual value ('ON' or 'OFF') based on a requester 2-state ini- configuration setting. * @@ -1056,6 +1068,12 @@ function display_requirements($installType, $badUpdatePath, $updatePath = '', $u // SERVER REQUIREMENTS echo '

'.get_lang('ServerRequirements').'

'; + + $timezone = check_php_setting_exists("date.timezone"); + if (!$timezone) { + echo "
".Display::return_icon('warning.png',get_lang('Warning'),'',ICON_SIZE_MEDIUM).get_lang("DateTimezoneSettingNotSet")."
"; + } + echo '
'.get_lang('ServerRequirementsInfo').'
'; echo '
'; echo ' From 8f4f5473c7a398a6b8e140716fc4c6212bd72c8b Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 30 Aug 2013 16:58:41 +0200 Subject: [PATCH 15/35] Adding mediaelement js library in order to reproduce audio and video tags see BT#6613 --- main/inc/ajax/lp.ajax.php | 2 +- .../javascript/mediaelement/background.png | Bin 0 -> 166 bytes .../lib/javascript/mediaelement/bigplay.png | Bin 0 -> 3001 bytes .../lib/javascript/mediaelement/bigplay.svg | 1 + .../javascript/mediaelement/controls-ted.png | Bin 0 -> 1559 bytes .../mediaelement/controls-wmp-bg.png | Bin 0 -> 1960 bytes .../javascript/mediaelement/controls-wmp.png | Bin 0 -> 5511 bytes .../lib/javascript/mediaelement/controls.png | Bin 0 -> 1892 bytes .../lib/javascript/mediaelement/controls.svg | 1 + .../mediaelement/flashmediaelement-cdn.swf | Bin 0 -> 29149 bytes .../mediaelement/flashmediaelement.swf | Bin 0 -> 29142 bytes .../inc/lib/javascript/mediaelement/jquery.js | 9597 +++++++++++++++++ .../lib/javascript/mediaelement/loading.gif | Bin 0 -> 6224 bytes .../mediaelement/mediaelement-and-player.js | 5067 +++++++++ .../mediaelement-and-player.min.js | 173 + .../javascript/mediaelement/mediaelement.js | 1902 ++++ .../mediaelement/mediaelement.min.js | 68 + .../mediaelement/mediaelementplayer.css | 870 ++ .../mediaelement/mediaelementplayer.js | 3163 ++++++ .../mediaelement/mediaelementplayer.min.css | 1 + .../mediaelement/mediaelementplayer.min.js | 103 + .../javascript/mediaelement/mejs-skins.css | 289 + .../mediaelement/silverlightmediaelement.xap | Bin 0 -> 12461 bytes main/inc/lib/template.lib.php | 3 + main/newscorm/learnpath.class.php | 2 +- main/newscorm/lp_view.php | 34 +- main/template/default/layout/footer.tpl | 74 +- .../default/learnpath/record_voice.tpl | 10 +- 28 files changed, 21306 insertions(+), 54 deletions(-) create mode 100644 main/inc/lib/javascript/mediaelement/background.png create mode 100644 main/inc/lib/javascript/mediaelement/bigplay.png create mode 100644 main/inc/lib/javascript/mediaelement/bigplay.svg create mode 100644 main/inc/lib/javascript/mediaelement/controls-ted.png create mode 100644 main/inc/lib/javascript/mediaelement/controls-wmp-bg.png create mode 100644 main/inc/lib/javascript/mediaelement/controls-wmp.png create mode 100644 main/inc/lib/javascript/mediaelement/controls.png create mode 100644 main/inc/lib/javascript/mediaelement/controls.svg create mode 100644 main/inc/lib/javascript/mediaelement/flashmediaelement-cdn.swf create mode 100644 main/inc/lib/javascript/mediaelement/flashmediaelement.swf create mode 100644 main/inc/lib/javascript/mediaelement/jquery.js create mode 100644 main/inc/lib/javascript/mediaelement/loading.gif create mode 100644 main/inc/lib/javascript/mediaelement/mediaelement-and-player.js create mode 100644 main/inc/lib/javascript/mediaelement/mediaelement-and-player.min.js create mode 100644 main/inc/lib/javascript/mediaelement/mediaelement.js create mode 100644 main/inc/lib/javascript/mediaelement/mediaelement.min.js create mode 100644 main/inc/lib/javascript/mediaelement/mediaelementplayer.css create mode 100644 main/inc/lib/javascript/mediaelement/mediaelementplayer.js create mode 100644 main/inc/lib/javascript/mediaelement/mediaelementplayer.min.css create mode 100644 main/inc/lib/javascript/mediaelement/mediaelementplayer.min.js create mode 100644 main/inc/lib/javascript/mediaelement/mejs-skins.css create mode 100644 main/inc/lib/javascript/mediaelement/silverlightmediaelement.xap diff --git a/main/inc/ajax/lp.ajax.php b/main/inc/ajax/lp.ajax.php index 5d7df12f17..3056b6fa74 100644 --- a/main/inc/ajax/lp.ajax.php +++ b/main/inc/ajax/lp.ajax.php @@ -81,7 +81,7 @@ switch ($action) { $fileInfo = pathinfo($fileName); - $file['name'] = 'rec_'.date('Y-m-d H:i:s').'_'.uniqid().'.'.$fileInfo['extension']; + $file['name'] = 'rec_'.date('Y-m-d_His').'_'.uniqid().'.'.$fileInfo['extension']; $file['file'] = $file; $lpPathInfo['dir'] = api_remove_trailing_slash($lpPathInfo['dir']); diff --git a/main/inc/lib/javascript/mediaelement/background.png b/main/inc/lib/javascript/mediaelement/background.png new file mode 100644 index 0000000000000000000000000000000000000000..fd428412ae26af13dab448ec833b1cb603e37ee9 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G9-c0aAr-gYoHrC?P~c(M$o@YsOF*1w?-55oQ{94SCl?~X dlYaFB3@*Kl;yd0~_5jUf@O1TaS?83{1OV-(FBAX( literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/bigplay.png b/main/inc/lib/javascript/mediaelement/bigplay.png new file mode 100644 index 0000000000000000000000000000000000000000..694553e31c387188b6bde397a5200c212aff2dc5 GIT binary patch literal 3001 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 J;~KO(@jp;uhBg2I literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/bigplay.svg b/main/inc/lib/javascript/mediaelement/bigplay.svg new file mode 100644 index 0000000000..c2f62bbc0d --- /dev/null +++ b/main/inc/lib/javascript/mediaelement/bigplay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/main/inc/lib/javascript/mediaelement/controls-ted.png b/main/inc/lib/javascript/mediaelement/controls-ted.png new file mode 100644 index 0000000000000000000000000000000000000000..3aac05aa83cb7fed54831a19d85a8c267e939720 GIT binary patch literal 1559 zcmV+y2I%>TP)b($m${ z*ulZUq@<+Ga(0rEl9ZH`pP!%1jF6a^n9k17S65e|p`o6hp2^9{eSLkbtgPMK;L9^M z+S%N$uCCP7)cpMX_4W2?X=#LngolTRmX?;bwzgkiUyO{5%M1^>xw(;%k+-+E)6&+J zm6fuxvf|?6#l^+Q$jId6%EZLP(b3VXtEe}8|OnVHSZ(SU$}$;r#*<>hvEcKiGL`uh5i zkdUgXsxvb)^78WY^YifV@a*jD@$vER@9*vH?d$97=H}-4`T6JP=k@jV_V)IQii+y$ z>iGEh=;-KXW@hg0?)3EZ%K!uE>FM|P_y7O@%*@Qh?k|P_0004WQchCKUO+1^xvT^Vf+4-v z?sc7Z-MiLHLK3gJBmxDokhc7(Po9K~ylSxWt2dNo@;Sl?lo7!uO(A{9KKhcXlIi|* z)y1Qa5h#7)A$5HCA#2rYb!%&D^wFbHwOX})PFczr-dbY>A5ReB;9pG+3Mf@97FAW< z`^_$@#bPp{9Hl1lR*DgxJ|!m+1Vd;O67twE40X&%lVL0z8dpaq6uf@iEEu|V$;8rlYmO1Fx9fWQbs~s{ba@$F zUPy;++u3Y(V|{%iXWP49#CDZ$@Ky&SP&x!de!Sv^TxlU8J&w%E1a)EdAq-cS`Z;F>v|iz|9xRCPd0Ub675YM~q&DLV{d z7{(zX9XP}RNIKR_5iBPO;7B-77M5R&vkX}Y2U>&LnK(6AlW?FxuwRK|!=Qu%0UU<` zK*E77)i3}^ICz%7F?0%r%)R)pkRl7YIh_mue8*g-7=Rz5v5=e7$p8@Kh<~DS@k0z2 zVxf}(fbZ~PzA`xYAte@KppyZB@9`$se=vLUO}IZaubxa3 zHs_?lA)@Ti%^if~n@~=e6=#!aA{BDtfhTuUxQTp7)I=iW-Tv*8e@-s{iI;zOUB+AZ z^8cR5Bi=-ws7cf$Y7#YxnnX>aCQ*~9Nz^225;YN_?$H>b_y?Un1O5gTSFQj6002ov JPDHLkV1l~`FgySN literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/controls-wmp-bg.png b/main/inc/lib/javascript/mediaelement/controls-wmp-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..89bb9b95602ecfca6290f6006f817da365da7b90 GIT binary patch literal 1960 zcmYLJ3se$l7KXGe6VuYMbnUV2H0EK-rZuw~cg#9Qf|=4d)s!)##yeTU#4AaGIBw2M!o?G&wajHQ+#IeSQ6{TekqJwY3!@CMa-vdV1if zv$GR?IK6i5T5)ml{}KQPh(S5r;>wjP&CShl)Y8&o;&$W44Jc76l~4{wrNH(1uz_GnvVUcBQ4I5YcEfmoHxiz^hlU0s$y2Dk=g^fGP+G zoGU9UK{TKQ$w05XyxhbMgn~j-JIIturJ%fAE(gVOb8{gXw$;_uVzD?iHI+)Ga=Bcg zPzYO?2&{2&acnkQCX*Eu6hJbpz!ut>CI$Bu2m~f=p}Ps0$KwH8=nF@{%A^U*2n-N1 zGc&WYvXYaN6$(XSVj_)31Mr-joQQ}BkQ$}~Wa8uFRVo#1K?KtSfCSjW#Nl4~`T2kY z1SAp(lgXsh>EYqwAZ%G#SyWUM(BN=585tST(b3>1BqV_B3UV!>TfQc^%e00E%@4Iu<8s08mk zEG%rTm1rEih4?`Kpx_v`q?C>Iw6UbA1yNHXB0U?P%RLoy1_#Pc6Bjn4GBN{^9`~*3Y-$uL9F5rr6?+VGm zyG|p@Hc1geS-FPDzwo)}_n(iN5d|Q?;S?T3)&poALSKB2gQgbb;^G|g24*I0dc~VFJw&h{e z%oaEA&>dqUL&qu4CBH}Ot(@2tp9^&>ZPmfaUFUQA&)RQk#;Ddy)fat>n>=k^=h0$c z4kJd3q@jGma&GnTgOtwguluea`gB{HeS;mre@Hj)b3eWbqAvl%mm(I@>Vq*WZfQ%Dfu z>&6W;-+6dT1`ghQL_jjsM#s1yy&sbTk z#{^QXzoYS0jf&kA8hOB-^aGLe!Haq(vFqtZKhlOf5k+HuBwg7*M*XQN4}3UxoQ>D! z@$cS-QuEFq<1EsvRWnP+7xsSPdSiK`|HCw;QztB#D1sAQdstr-~oiDZrX zexfB#!?X)Lp>KSQ#s2$U1=INOuh!uKyf^m7srS)l7?3o*oE$X1Uaz>-Mh+wyB1Wq6 z6_b9x;sDYep{JGd!|M&2FumqK_euAsdOaRg7`0vPLXLI^T`hiaTu86#U;9p#WWlZ zR`3<_7fYS)vx=OUvZu^L1rf~)Q`S^z%=qB;nZadq)v?Jly1@}_*7CkE+UHlPpRN}i z#~&ik%1TG!)#9C7es^yZ>7VMIQ%myj);|1-xp?A7m)7xp&w?giMXluyGYlc^Rc0@3 zrPNWv>?tq#$gc~h27U4DTJ!##3r}pUoR(KK(f(|6TmM)I|3?_YQU4>2hcB@I3!4Eg A)c^nh literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/controls-wmp.png b/main/inc/lib/javascript/mediaelement/controls-wmp.png new file mode 100644 index 0000000000000000000000000000000000000000..4775ef5b02faf2b826d35dfc72511b6b27fea87b GIT binary patch literal 5511 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 JPDHLkV1kZU;|2f# literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/controls.png b/main/inc/lib/javascript/mediaelement/controls.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a857d800b64264443af4609e0ebf7175593d8f GIT binary patch literal 1892 zcmdUv_d6R37snIh7SSuz2t|V4+9fes_1q$f5VaGdb}NdiM3cByUnLZ^M^$TvYH6+3 zZtmo)edTp*6v8gC|(^b&SOo{wj536 z1dNZHlfT#k*&N}fAhCEpKEA0rt62a5cx-KsH)gZhr>CdK$Hy!d>*(m{=g*&qhld9T z2mAZ`dwY93J3HIk+gn>(o12?VCUawBV|{&nZEbCJb@j)OA1f;>%gf7)i;D{j3-j~y zb8~aEv$HcZGt<-4-@kvKnwpxNoSc}L7#|-W8yg!L85tfP9vT{AFc<>^1O5H|eSLl1 z-Q8VXU7eks9UUE?K7DF$Z*OaBYi(_9X=!O}Y;0&~sIRZDt*x!DuCA)8s;sQ6sHi9} zFE1-AD=lp(DJgmN>Q!-ZaZynboleisFE1!4$ji&i&CSip$;r;ne(~Z(R#sMKX6Dl~ z3Ntb?($mvZQc{wWlM@pY6A}{Q}azH=;kx2CS5AyY;`1<;KdwUZI1W!*-4-XG_cXu~8H&<6z7Z(>NCnrZo zM|*pFJ3BiZ4rgm?Yhz<$Wo2b)X=!0$VQy}I^X5%6Gczm}Yhq%8!C=s6Gzx`6B9VrM zh6V-(H*Va}*Vot6)4O{0s;;iC&KY`IT3QGM0uG03YHF&fsi~@}s+@5}Sy@?0Nl8&r zQBF<{c7{9@3YC?Wm64H=mX?;1l9H5^gg_u-ViICvVj?0U!otE}Fjz=P2m}HF0DxcW zN>Glxq1syFIA`#G0pOSvCe9g}Kfv@B1pokvoCR?BpYUItra0Buk%}UF-KP4Ih%mgb zzZU=w*MMtjz!Bwb*F!iC=`#n8be9@*o9qQJB9QKQ!7#pLuOJfHj{-yCbXS_I69E7o zAQo+ecYOTSp5OU`gXsCo*>52)lh6vn8Kw*N@0iYb)3|u0uPUYjPIZ@+Fb|Aw;YTAb z5*xWH@ll7~EM`~`)BMw-YTd3_=zldfr+26G^c6|9ubO%Xt;J`06(dSSt}I(|Gi z-t*F1?mb=zt7(_@kXPIV9`QVZ_h_=)jyh07AvR3;X^zG5ym@b#-hWSSIn=C%#4L8k zRk&*G8xRFvgjcG~)1kFGlmk}OLyo{W4j`6Fzxc*HS|(D|o~1m@SscZF+L z#~vvMstrBA_Ut3ef#^Ibr;l=euG~%w2lKfw#MAo4XLW1*>dM6xyEb>7=|IK#WHB8` zjjHs6@{iuC^4}_Hl6MiQbsB1S)$V7}MEUE=$bbJUu(f>*Q_KVT`;GYz_5$C1(R%6r zb#HJh?!lJ*kOI<%MLcD~mNhR`P@4Y99ItYSw=Vh1M^^5ka9jwFIa@j(%2fcPT#Oa; z$P5qkI;b^~7@g$4e8@-<40LSip>WT}^x%Zd7+l@7|JWO% z&1A^bwBo;5TXcQa-8&$H=Tb!$vr3zbzQ8pu%r5jL`%DPuszpuvua(L60t1TbgZCko z$rV?gHL%rBURyr;Y=dm7eHvM=&>StyOr>#g`B7x!UbcIrVl{)GdZ$T&(wg@_sC2dr z`|qxGJHtOJe;$JFDyUfJ1H_VOq`r4y*%!^gAc|g&r7g!IlJsjXZlAOaE|G^qD{dtaisv}19gi`!-Cwlo2@;UsMHwBU z>>3{ZsS_za)C)>Ab3+%Ak?h9s5*Wfjlxi?aW~MA$@F`^apbn~d)8K{l cc \ No newline at end of file diff --git a/main/inc/lib/javascript/mediaelement/flashmediaelement-cdn.swf b/main/inc/lib/javascript/mediaelement/flashmediaelement-cdn.swf new file mode 100644 index 0000000000000000000000000000000000000000..f9db16032c14989a968681991edbb63075a8cfce GIT binary patch literal 29149 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࿱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 literal 0 HcmV?d00001 diff --git a/main/inc/lib/javascript/mediaelement/flashmediaelement.swf b/main/inc/lib/javascript/mediaelement/flashmediaelement.swf new file mode 100644 index 0000000000000000000000000000000000000000..c5d205a0ad73921b77ac6f484a1d71775ed2b23e GIT binary patch literal 29142 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 type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( (elem = this[i++]) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("'; + + /* + container.innerHTML = + '' + + '' + + '' + + '' + + '' + + '' + + ''; + */ + + break; + } + // hide original element + htmlMediaElement.style.display = 'none'; + // prevent browser from autoplaying when using a plugin + htmlMediaElement.removeAttribute('autoplay'); + + // FYI: options.success will be fired by the MediaPluginBridge + + return pluginMediaElement; + }, + + updateNative: function(playback, options, autoplay, preload) { + + var htmlMediaElement = playback.htmlMediaElement, + m; + + + // add methods to video object to bring it into parity with Flash Object + for (m in mejs.HtmlMediaElement) { + htmlMediaElement[m] = mejs.HtmlMediaElement[m]; + } + + /* + Chrome now supports preload="none" + if (mejs.MediaFeatures.isChrome) { + + // special case to enforce preload attribute (Chrome doesn't respect this) + if (preload === 'none' && !autoplay) { + + // forces the browser to stop loading (note: fails in IE9) + htmlMediaElement.src = ''; + htmlMediaElement.load(); + htmlMediaElement.canceledPreload = true; + + htmlMediaElement.addEventListener('play',function() { + if (htmlMediaElement.canceledPreload) { + htmlMediaElement.src = playback.url; + htmlMediaElement.load(); + htmlMediaElement.play(); + htmlMediaElement.canceledPreload = false; + } + }, false); + // for some reason Chrome forgets how to autoplay sometimes. + } else if (autoplay) { + htmlMediaElement.load(); + htmlMediaElement.play(); + } + } + */ + + // fire success code + options.success(htmlMediaElement, htmlMediaElement); + + return htmlMediaElement; + } +}; + +/* + - test on IE (object vs. embed) + - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) + - fullscreen? +*/ + +// YouTube Flash and Iframe API +mejs.YouTubeApi = { + isIframeStarted: false, + isIframeLoaded: false, + loadIframeApi: function() { + if (!this.isIframeStarted) { + var tag = document.createElement('script'); + tag.src = "//www.youtube.com/player_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + this.isIframeStarted = true; + } + }, + iframeQueue: [], + enqueueIframe: function(yt) { + + if (this.isLoaded) { + this.createIframe(yt); + } else { + this.loadIframeApi(); + this.iframeQueue.push(yt); + } + }, + createIframe: function(settings) { + + var + pluginMediaElement = settings.pluginMediaElement, + player = new YT.Player(settings.containerId, { + height: settings.height, + width: settings.width, + videoId: settings.videoId, + playerVars: {controls:0}, + events: { + 'onReady': function() { + + // hook up iframe object to MEjs + settings.pluginMediaElement.pluginApi = player; + + // init mejs + mejs.MediaPluginBridge.initPlugin(settings.pluginId); + + // create timer + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + }, + 'onStateChange': function(e) { + + mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); + + } + } + }); + }, + + createEvent: function (player, pluginMediaElement, eventName) { + var obj = { + type: eventName, + target: pluginMediaElement + }; + + if (player && player.getDuration) { + + // time + pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); + pluginMediaElement.duration = obj.duration = player.getDuration(); + + // state + obj.paused = pluginMediaElement.paused; + obj.ended = pluginMediaElement.ended; + + // sound + obj.muted = player.isMuted(); + obj.volume = player.getVolume() / 100; + + // progress + obj.bytesTotal = player.getVideoBytesTotal(); + obj.bufferedBytes = player.getVideoBytesLoaded(); + + // fake the W3C buffered TimeRange + var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; + + obj.target.buffered = obj.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + } + + // send event up the chain + pluginMediaElement.dispatchEvent(obj.type, obj); + }, + + iFrameReady: function() { + + this.isLoaded = true; + this.isIframeLoaded = true; + + while (this.iframeQueue.length > 0) { + var settings = this.iframeQueue.pop(); + this.createIframe(settings); + } + }, + + // FLASH! + flashPlayers: {}, + createFlash: function(settings) { + + this.flashPlayers[settings.pluginId] = settings; + + /* + settings.container.innerHTML = + '' + + '' + + '' + + ''; + */ + + var specialIEContainer, + youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; + + if (mejs.MediaFeatures.isIE) { + + specialIEContainer = document.createElement('div'); + settings.container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = '' + + '' + + '' + + '' + + '' + +''; + } else { + settings.container.innerHTML = + '' + + '' + + '' + + ''; + } + + }, + + flashReady: function(id) { + var + settings = this.flashPlayers[id], + player = document.getElementById(id), + pluginMediaElement = settings.pluginMediaElement; + + // hook up and return to MediaELementPlayer.success + pluginMediaElement.pluginApi = + pluginMediaElement.pluginElement = player; + mejs.MediaPluginBridge.initPlugin(id); + + // load the youtube video + player.cueVideoById(settings.videoId); + + var callbackName = settings.containerId + '_callback'; + + window[callbackName] = function(e) { + mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); + } + + player.addEventListener('onStateChange', callbackName); + + setInterval(function() { + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); + }, 250); + }, + + handleStateChange: function(youTubeState, player, pluginMediaElement) { + switch (youTubeState) { + case -1: // not started + pluginMediaElement.paused = true; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); + //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); + break; + case 0: + pluginMediaElement.paused = false; + pluginMediaElement.ended = true; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); + break; + case 1: + pluginMediaElement.paused = false; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); + break; + case 2: + pluginMediaElement.paused = true; + pluginMediaElement.ended = false; + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); + break; + case 3: // buffering + mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); + break; + case 5: + // cued? + break; + + } + + } +} +// IFRAME +function onYouTubePlayerAPIReady() { + mejs.YouTubeApi.iFrameReady(); +} +// FLASH +function onYouTubePlayerReady(id) { + mejs.YouTubeApi.flashReady(id); +} + +window.mejs = mejs; +window.MediaElement = mejs.MediaElement; + +/*! + * Adds Internationalization and localization to objects. + * + * What is the concept beyond i18n? + * http://en.wikipedia.org/wiki/Internationalization_and_localization + * + * + * This file both i18n methods and locale which is used to translate + * strings into other languages. + * + * Default translations are not available, you have to add them + * through locale objects which are named exactly as the langcode + * they stand for. The default language is always english (en). + * + * + * Wrapper built to be able to attach the i18n object to + * other objects without changing more than one line. + * + * + * LICENSE: + * + * The i18n file uses methods from the Drupal project (drupal.js): + * - i18n.methods.t() (modified) + * - i18n.methods.checkPlain() (full copy) + * - i18n.methods.formatString() (full copy) + * + * The Drupal project is (like mediaelementjs) licensed under GPLv2. + * - http://drupal.org/licensing/faq/#q1 + * - https://github.com/johndyer/mediaelement + * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n-locale.js + * + * @params + * - context - document, iframe .. + * - exports - CommonJS, window .. + * + */ +;(function(context, exports, undefined) { + "use strict"; + var i18n = { + "locale": { + "language" : '', + "strings" : {} + }, + "methods" : {} + }; +// start i18n + + + /** + * Get the current browser's language + * + * @see: i18n.methods.t() + */ + i18n.locale.getLanguage = function () { + return i18n.locale.language || navigator.language; + }; + + if ( typeof mejsL10n != 'undefined' ) { + i18n.locale.language = mejsL10n.language; + } + + /** + * Store the language the locale object was initialized with + */ + i18n.locale.INIT_LANGUAGE = i18n.locale.getLanguage(); + + + /** + * Encode special characters in a plain-text string for display as HTML. + */ + i18n.methods.checkPlain = function (str) { + var character, regex, + replace = { + '&': '&', + '"': '"', + '<': '<', + '>': '>' + }; + str = String(str); + for (character in replace) { + if (replace.hasOwnProperty(character)) { + regex = new RegExp(character, 'g'); + str = str.replace(regex, replace[character]); + } + } + return str; + }; + + /** + * Replace placeholders with sanitized values in a string. + * + * @param str + * A string with placeholders. + * @param args + * An object of replacements pairs to make. Incidences of any key in this + * array are replaced with the corresponding value. Based on the first + * character of the key, the value is escaped and/or themed: + * - !variable: inserted as is + * - @variable: escape plain text to HTML (i18n.methods.checkPlain) + * - %variable: escape text and theme as a placeholder for user-submitted + * content (checkPlain + ) + * + * @see i18n.methods.t() + */ + i18n.methods.formatString = function(str, args) { + // Transform arguments before inserting them. + for (var key in args) { + switch (key.charAt(0)) { + // Escaped only. + case '@': + args[key] = i18n.methods.checkPlain(args[key]); + break; + // Pass-through. + case '!': + break; + // Escaped and placeholder. + case '%': + default: + args[key] = '' + i18n.methods.checkPlain(args[key]) + ''; + break; + } + str = str.replace(key, args[key]); + } + return str; + }; + + /** + * Translate strings to the page language or a given language. + * + * See the documentation of the server-side t() function for further details. + * + * @param str + * A string containing the English string to translate. + * @param args + * An object of replacements pairs to make after translation. Incidences + * of any key in this array are replaced with the corresponding value. + * See i18n.methods.formatString(). + * + * @param options + * - 'context' (defaults to the default context): The context the source string + * belongs to. + * + * @return + * The translated string. + */ + i18n.methods.t = function (str, args, options) { + + // Fetch the localized version of the string. + if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { + str = i18n.locale.strings[options.context][str]; + } + + if (args) { + str = i18n.methods.formatString(str, args); + } + return str; + }; + + + /** + * Wrapper for i18n.methods.t() + * + * @see i18n.methods.t() + * @throws InvalidArgumentException + */ + i18n.t = function(str, args, options) { + + if (typeof str === 'string' && str.length > 0) { + + // check every time due language can change for + // different reasons (translation, lang switcher ..) + var language = i18n.locale.getLanguage(); + + options = options || { + "context" : language + }; + + return i18n.methods.t(str, args, options); + } + else { + throw { + "name" : 'InvalidArgumentException', + "message" : 'First argument is either not a string or empty.' + } + } + }; + +// end i18n + exports.i18n = i18n; +}(document, mejs)); + +;(function(exports, undefined) { + + "use strict"; + + if ( typeof mejsL10n != 'undefined' ) { + exports[mejsL10n.language] = mejsL10n.strings; + }; + +}(mejs.i18n.locale.strings)); + +/*! + * This is a i18n.locale language object. + * + * German translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + exports.de = { + "Fullscreen" : "Vollbild", + "Go Fullscreen" : "Vollbild an", + "Turn off Fullscreen" : "Vollbild aus", + "Close" : "Schließen" + }; + +}(mejs.i18n.locale.strings)); +/*! + * This is a i18n.locale language object. + * + * Traditional chinese translation by Tim Latz, latz.tim@gmail.com + * + * @author + * Tim Latz (latz.tim@gmail.com) + * + * @see + * me-i18n.js + * + * @params + * - exports - CommonJS, window .. + */ +;(function(exports, undefined) { + + "use strict"; + + exports.zh = { + "Fullscreen" : "全螢幕", + "Go Fullscreen" : "全屏模式", + "Turn off Fullscreen" : "退出全屏模式", + "Close" : "關閉" + }; + +}(mejs.i18n.locale.strings)); + + +/*! + * MediaElementPlayer + * http://mediaelementjs.com/ + * + * Creates a controller bar for HTML5
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + pixelMargin: true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if ( window.getComputedStyle ) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; + } + + if ( typeof container.style.zoom !== "undefined" ) { + container.style.zoom = 1; + } + + body.removeChild( container ); + marginDiv = div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise( object ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: selector && quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process events on disabled elements (#6911, #8165) + if ( cur.disabled !== true ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} +// Expose origPOS +// "global" as in regardless of relation to brackets/parens +Expr.match.globalPOS = origPOS; + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and diff --git a/main/newscorm/scorm_api.php b/main/newscorm/scorm_api.php index 1172ea5ca3..f7792abb25 100644 --- a/main/newscorm/scorm_api.php +++ b/main/newscorm/scorm_api.php @@ -1463,17 +1463,29 @@ function switch_item(current_item, next_item){ xajax_start_timer(); } + //(4) refresh the audio player if needed $.ajax({ type: "POST", url: "lp_nav.php", data: "", + beforeSend: function() { + $.each($('audio'), function () { + var player = new MediaElementPlayer($(this)); + player.pause(); + }); + }, success: function(tmp_data) { if ($("#lp_media_file").length != 0) { $("#lp_media_file").html(tmp_data); } } }); + + + + + return true; } diff --git a/main/template/default/layout/footer.tpl b/main/template/default/layout/footer.tpl index 103f590032..09b8b7206c 100644 --- a/main/template/default/layout/footer.tpl +++ b/main/template/default/layout/footer.tpl @@ -69,7 +69,7 @@ $(document).ready( function() { }); // Mediaelement - $('video:not(.skip), audio:not(.skip)').mediaelementplayer(/* Options */); + jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer(/* Options */); // Table highlight. $("form .data_table input:checkbox").click(function() { From 9c89452f1f3e0a7314f761ee01b164ddfeb39a02 Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Tue, 3 Sep 2013 13:06:05 +0200 Subject: [PATCH 33/35] Fixing ie8 issues when loading LP audio. --- main/newscorm/lp_view.php | 42 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/main/newscorm/lp_view.php b/main/newscorm/lp_view.php index c9a75e756a..ccbbaee91e 100644 --- a/main/newscorm/lp_view.php +++ b/main/newscorm/lp_view.php @@ -70,24 +70,12 @@ $platform_theme = api_get_setting('stylesheets'); // Plataform's css. $my_style = $platform_theme; $htmlHeadXtra[] = ''; -/* - * already added in lp_controller.php -if (api_get_setting('show_glossary_in_documents') == 'ismanual' || api_get_setting('show_glossary_in_documents') == 'isautomatic' ) { - $htmlHeadXtra[] = ''; - $htmlHeadXtra[] = ''; - $htmlHeadXtra[] = ''; -}*/ - $htmlHeadXtra[] = ''; @@ -226,7 +214,6 @@ if ($type_quiz && !empty($_REQUEST['exeId']) && isset($lp_id) && isset($_GET['lp if ($safe_id == strval(intval($safe_id)) && $safe_item_id == strval(intval($safe_item_id))) { $sql = 'SELECT start_date, exe_date, exe_result, exe_weighting FROM ' . $TBL_TRACK_EXERCICES . ' WHERE exe_id = '.$safe_exe_id; - if ($debug) error_log($sql); $res = Database::query($sql); $row_dates = Database::fetch_array($res); @@ -238,12 +225,10 @@ if ($type_quiz && !empty($_REQUEST['exeId']) && isset($lp_id) && isset($_GET['lp $max_score = (float)$row_dates['exe_weighting']; $sql_upd_max_score = "UPDATE $TBL_LP_ITEM SET max_score = '$max_score' WHERE c_id = $course_id AND id = '".$safe_item_id."'"; - if ($debug) error_log($sql_upd_max_score); Database::query($sql_upd_max_score); $sql_last_attempt = "SELECT id FROM $TBL_LP_ITEM_VIEW WHERE c_id = $course_id AND lp_item_id = '$safe_item_id' AND lp_view_id = '".$_SESSION['oLP']->lp_view_id."' order by id desc limit 1"; $res_last_attempt = Database::query($sql_last_attempt); - if ($debug) error_log($sql_last_attempt); if (Database::num_rows($res_last_attempt)) { $row_last_attempt = Database::fetch_row($res_last_attempt); @@ -329,18 +314,18 @@ if (Database::num_rows($res_media) > 0) { } } echo '
'; - $is_allowed_to_edit = api_is_allowed_to_edit(null, true, false, false); - if ($is_allowed_to_edit) { - echo '
'; - global $interbreadcrumb; - $interbreadcrumb[] = array('url' => 'lp_controller.php?action=list&isStudentView=false', 'name' => get_lang('LearningPaths')); - $interbreadcrumb[] = array('url' => api_get_self()."?action=add_item&type=step&lp_id=".$_SESSION['oLP']->lp_id."&isStudentView=false", 'name' => $_SESSION['oLP']->get_name()); - $interbreadcrumb[] = array('url' => '#', 'name' => get_lang('Preview')); - //$interbreadcrumb[] = array('type' => 'right', 'url' => api_get_self()."?action=add_item&type=step&lp_id=".$_SESSION['oLP']->lp_id."&isStudentView=false", 'name' => get_lang('Edit'), 'class' => 'btn btn-mini btn-warning'); - - echo return_breadcrumb($interbreadcrumb, null, null); - echo '
'; - } +$is_allowed_to_edit = api_is_allowed_to_edit(null, true, false, false); +if ($is_allowed_to_edit) { + echo '
'; + global $interbreadcrumb; + $interbreadcrumb[] = array('url' => 'lp_controller.php?action=list&isStudentView=false', 'name' => get_lang('LearningPaths')); + $interbreadcrumb[] = array('url' => api_get_self()."?action=add_item&type=step&lp_id=".$_SESSION['oLP']->lp_id."&isStudentView=false", 'name' => $_SESSION['oLP']->get_name()); + $interbreadcrumb[] = array('url' => '#', 'name' => get_lang('Preview')); + //$interbreadcrumb[] = array('type' => 'right', 'url' => api_get_self()."?action=add_item&type=step&lp_id=".$_SESSION['oLP']->lp_id."&isStudentView=false", 'name' => get_lang('Edit'), 'class' => 'btn btn-mini btn-warning'); + + echo return_breadcrumb($interbreadcrumb, null, null); + echo '
'; +} echo '
'; echo '