diff --git a/composer.json b/composer.json index 84c72bc99a..8100d68423 100755 --- a/composer.json +++ b/composer.json @@ -86,7 +86,8 @@ "opauth/google": "0.2.2", "media-alchemyst/media-alchemyst": "0.3.6", "php-ffmpeg/php-ffmpeg": "0.4.4", - "php-unoconv/php-unoconv": "0.3.0" + "php-unoconv/php-unoconv": "0.3.0", + "sunra/php-simple-html-dom-parser": "1.5.0" }, "require-dev": { "knplabs/gaufrette": "0.2.*@dev", diff --git a/main/inc/global.inc.php b/main/inc/global.inc.php index 55565a24a4..ae226194e1 100644 --- a/main/inc/global.inc.php +++ b/main/inc/global.inc.php @@ -584,6 +584,7 @@ $app->before( } else { $app['course'] = null; } + $app['session']->set('course_session', $app['course']); $studentView = $request->get('isStudentView'); if (!empty($studentView)) { diff --git a/main/inc/services.php b/main/inc/services.php index bc8e4364b9..32f7039fb7 100644 --- a/main/inc/services.php +++ b/main/inc/services.php @@ -637,7 +637,12 @@ class ChamiloServiceProvider implements ServiceProviderInterface // Chamilo data filesystem. $app['chamilo.filesystem'] = $app->share(function () use ($app) { - $filesystem = new ChamiloLMS\Component\DataFilesystem\DataFilesystem($app['paths'], $app['filesystem']); + $filesystem = new ChamiloLMS\Component\DataFilesystem\DataFilesystem( + $app['paths'], + $app['filesystem'], + $app['editor_connector'], + $app['unoconv'] + ); return $filesystem; }); @@ -857,6 +862,7 @@ $app['html_editor'] = $app->share(function($app) { $app['editor_connector'] = $app->share(function ($app) { $token = $app['security']->getToken(); $user = $token->getUser(); + $course = $app['session']->get('course_session'); return new Connector( $app['orm.em'], @@ -865,15 +871,26 @@ $app['editor_connector'] = $app->share(function ($app) { $app['translator'], $app['security'], $user, - $app['course'] + $course ); }); +$app->register(new Unoconv\UnoconvServiceProvider(), array( + 'unoconv.configuration' => array( + 'unoconv.binaries' => $app['configuration']['unoconv.binaries'], + 'timeout' => 42, + ), + 'unoconv.logger' => $app->share(function () use ($app) { + return $app['monolog']; // use Monolog service provider + }), +)); + + /* $app->register( new ChamiloLMS\Provider\BootstrapSilexProvider(), array( ) -);*/ \ No newline at end of file +);*/ diff --git a/main/wiki/index.php b/main/wiki/index.php index a2622e59fd..1dda074b4d 100644 --- a/main/wiki/index.php +++ b/main/wiki/index.php @@ -9,20 +9,15 @@ /** * Code */ +use \ChamiloSession as Session; + // name of the language file that needs to be included $language_file = 'wiki'; -$newtitle = null; // including the global initialization file require_once '../inc/global.inc.php'; require_once 'wiki.inc.php'; -// Database table definition -$tbl_wiki = Database::get_course_table(TABLE_WIKI); -$tbl_wiki_discuss = Database::get_course_table(TABLE_WIKI_DISCUSS); -$tbl_wiki_mailcue = Database::get_course_table(TABLE_WIKI_MAILCUE); -$tbl_wiki_conf = Database::get_course_table(TABLE_WIKI_CONF); - global $charset; $wiki = new Wiki(); @@ -34,6 +29,11 @@ $current_course_tool = TOOL_WIKI; //require_once api_get_path(LIBRARY_PATH).'mail.lib.inc.php'; $course_id = api_get_course_int_id(); +$session_id = api_get_session_id(); +$condition_session = api_get_session_condition($session_id); +$course_id = api_get_course_int_id(); +$groupId = api_get_group_id(); + // additional style information $htmlHeadXtra[] =''; @@ -42,43 +42,31 @@ $htmlHeadXtra[] = ''; /* Constants and variables */ $tool_name = get_lang('ToolWiki'); -$MonthsLong = array( - get_lang("JanuaryLong"), - get_lang("FebruaryLong"), - get_lang("MarchLong"), - get_lang("AprilLong"), - get_lang("MayLong"), - get_lang("JuneLong"), - get_lang("JulyLong"), - get_lang("AugustLong"), - get_lang("SeptemberLong"), - get_lang("OctoberLong"), - get_lang("NovemberLong"), - get_lang("DecemberLong") -); - -//condition for the session -$session_id = api_get_session_id(); -$condition_session = api_get_session_condition($session_id); -$course_id = api_get_course_int_id(); /* ACCESS */ api_protect_course_script(); @@ -87,1531 +75,58 @@ api_block_anonymous_users(); /* TRACKING */ event_access_tool(TOOL_WIKI); -/* HEADER & TITLE */ -// If it is a group wiki then the breadcrumbs will be different. - -// Setting variable -$groupId = api_get_group_id(); - if ($groupId) { $group_properties = GroupManager::get_group_properties($groupId); - $interbreadcrumb[] = array("url" => "../group/group.php", "name" => get_lang('Groups')); - $interbreadcrumb[] = array("url"=>"../group/group_space.php?gidReq=".$groupId, "name"=> get_lang('GroupSpace').' '.$group_properties['name']); - - $add_group_to_title = ' '.$group_properties['name']; - $groupfilter='group_id="'.$groupId.'"'; - + $interbreadcrumb[] = array("url" => api_get_path(WEB_CODE_PATH)."group/group.php", "name" => get_lang('Groups')); + $interbreadcrumb[] = array( + "url" => api_get_path(WEB_CODE_PATH)."group/group_space.php?gidReq=".$groupId, + "name" => get_lang('GroupSpace').' '.$group_properties['name'] + ); //ensure this tool in groups whe it's private or deactivated if ($group_properties['wiki_state'] == 0) { api_not_allowed(); } elseif ($group_properties['wiki_state']==2) { - if (!api_is_allowed_to_edit(false,true) and !GroupManager :: is_user_in_group($_user['user_id'], $_SESSION['_gid'])) { + if (!api_is_allowed_to_edit(false,true) and !GroupManager :: is_user_in_group(api_get_user_id(), api_get_group_id())) { api_not_allowed(); } } -} else { - $groupfilter='group_id=0'; -} - -if (isset($_POST['action']) && $_POST['action']=='export_to_pdf' && isset($_POST['wiki_id']) && api_get_setting('students_export2pdf') == 'true') { - $wiki->export_to_pdf($_POST['wiki_id'], api_get_course_id()); - exit; } -$action = isset($_GET['action']) ? $_GET['action'] : null; -Display::display_header($tool_name, 'Wiki'); $is_allowed_to_edit = api_is_allowed_to_edit(false, true); -/* INITIALISATION */ -//the page we are dealing with -if (!isset($_GET['title'])) { - $page = 'index'; -} else { - $page = $_GET['title']; -} +// The page we are dealing with +$page = isset($_GET['title']) ? $_GET['title']: 'index'; +$action = isset($_GET['action']) ? $_GET['action'] : 'showpage'; +$view = isset($_GET['view']) ? $_GET['view'] : null; $wiki->page = $page; +$wiki->action = $action; /* MAIN CODE */ -// Tool introduction -Display::display_introduction_section(TOOL_WIKI); - /* ACTIONS */ $wiki->blockConcurrentEditions(api_get_user_id(), $action); -// Saving a change - -if (isset($_POST['SaveWikiChange']) AND $_POST['title']<>'') { - if (empty($_POST['title'])) { - Display::display_error_message(get_lang("NoWikiPageTitle")); - } elseif (!$wiki->double_post($_POST['wpost_id'])) { - //double post - } elseif ($_POST['version']!='' && $_SESSION['_version']!=0 && $_POST['version']!=$_SESSION['_version']) { - //prevent concurrent users and double version - Display::display_error_message(get_lang("EditedByAnotherUser")); - } else { - $return_message = $wiki->save_wiki(); - Display::display_confirmation_message($return_message, false); - } -} - -// Saving a new wiki entry -if (isset($_POST['SaveWikiNew'])) { - if (empty($_POST['title'])) { - Display::display_error_message(get_lang("NoWikiPageTitle")); - } elseif (strtotime($wiki->get_date_from_select('startdate_assig')) > strtotime($wiki->get_date_from_select('enddate_assig'))) { - Display::display_error_message(get_lang("EndDateCannotBeBeforeTheStartDate")); - } elseif (!$wiki->double_post($_POST['wpost_id'])) { - //double post - } else { - $_clean['assignment'] = null; - if (isset($_POST['assignment'])) { - // for mode assignment - $_clean['assignment'] = Database::escape_string($_POST['assignment']); - } - - if ($_clean['assignment'] == 1) { - $wiki->auto_add_page_users($_clean['assignment']); - } - $return_message = $wiki->save_new_wiki(); - if ($return_message == false) { - Display::display_error_message(get_lang('NoWikiPageTitle'), false); - } else { - Display::display_confirmation_message($return_message, false); - } - } -} - -// check last version -if (isset($_GET['view']) && $_GET['view']) { - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE - c_id = '.$course_id.' AND - id="'.Database::escape_string($_GET['view']).'"'; //current view - $result=Database::query($sql); - $current_row=Database::fetch_array($result); - - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND reflink="'.Database::escape_string($page).'" AND '.$groupfilter.$condition_session.' ORDER BY id DESC'; //last version - $result=Database::query($sql); - $last_row=Database::fetch_array($result); - - if ($_GET['view'] < $last_row['id']) { - $message = '
'.get_lang('NoAreSeeingTheLastVersion').'
'.get_lang("Version").' ('.$current_row['version'].' / '.$last_row['version'].')
'.get_lang("ConvertToLastVersion").': '.get_lang("Restore").'
'; - Display::display_warning_message($message,false); - } - - // Restore page. - - if ($action == 'restorepage') { - //Only teachers and platform admin can edit the index page. Only teachers and platform admin can edit an assignment teacher - if (( - $current_row['reflink']=='index' || - $current_row['reflink']=='' || - $current_row['assignment'] == 1 - ) && - (!api_is_allowed_to_edit(false,true) && intval($_GET['group_id'])==0) - ) { - Display::display_normal_message(get_lang('OnlyEditPagesCourseManager')); - } else { - $PassEdit=false; - - //check if is a wiki group - if ($current_row['group_id'] != 0) { - //Only teacher, platform admin and group members can edit a wiki group - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin() || GroupManager :: is_user_in_group($_user['user_id'],intval($_GET['group_id']))) { - $PassEdit = true; - } else { - Display::display_normal_message(get_lang('OnlyEditPagesGroupMembers')); - } - } else { - $PassEdit=true; - } - - // check if is an assignment - $icon_assignment = null; - if ($current_row['assignment']==1) { - Display::display_normal_message(get_lang('EditAssignmentWarning')); - $icon_assignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDescExtra'),'',ICON_SIZE_SMALL); - } elseif($current_row['assignment']==2) { - $icon_assignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWorkExtra'),'',ICON_SIZE_SMALL); - if ((api_get_user_id()==$current_row['user_id'])==false) { - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - $PassEdit=true; - } else { - Display::display_warning_message(get_lang('LockByTeacher')); - $PassEdit=false; - } - } else { - $PassEdit=true; - } - } - - //show editor if edit is allowed - if ($PassEdit) { - if ($row['editlock']==1 && (api_is_allowed_to_edit(false,true)==false || api_is_platform_admin()==false)) { - Display::display_normal_message(get_lang('PageLockedExtra')); - } else { - if ($last_row['is_editing']!=0 && $last_row['is_editing'] != $_user['user_id']) { - //checking for concurrent users - $timestamp_edit = strtotime($last_row['time_edit']); - $time_editing = time()-$timestamp_edit; - $max_edit_time = 1200; // 20 minutes - $rest_time = $max_edit_time - $time_editing; - $userinfo = api_get_user_info($last_row['is_editing']); - $username = api_htmlentities(sprintf(get_lang('LoginX'), $userinfo['username']), ENT_QUOTES); - $is_being_edited = get_lang('ThisPageisBeginEditedBy').' '. - Display::tag('span', api_get_person_name($userinfo['firstname'], $userinfo['lastname'], array('title'=>$username))). - get_lang('ThisPageisBeginEditedTryLater').' '.date( "i",$rest_time).' '.get_lang('MinMinutes'); - Display::display_normal_message($is_being_edited, false); - } else { - Display::display_confirmation_message( - $wiki->restore_wikipage( - $current_row['page_id'], - $current_row['reflink'], - api_htmlentities($current_row['title']), - api_htmlentities($current_row['content']), - $current_row['group_id'], - $current_row['assignment'], - $current_row['progress'], - $current_row['version'], - $last_row['version'], - $current_row['linksto'] - ).': '.api_htmlentities($last_row['title']).'', - false - ); - } - } - } - } - } -} - -if ($action == 'deletewiki') { - if (api_is_allowed_to_edit(false, true) || api_is_platform_admin()) { - if ($_GET['delete'] == 'yes') { - $return_message = $wiki->delete_wiki(); - Display::display_confirmation_message($return_message); - } - } -} -if ($action =='discuss' && isset($_POST['Submit']) && $_POST['Submit']) { - Display::display_confirmation_message(get_lang('CommentAdded')); -} - /* MAIN WIKI AREA */ -/** menuwiki (= actions of the page, not of the wiki tool) **/ - -echo '
'; -/* echo ' is_active_navigation_tab('show').'>'. - Display::return_icon('wiki.png',get_lang('HomeWiki'),'',ICON_SIZE_MEDIUM).' ';*/ -echo ''; -echo '
'; // End actions - - -//In new pages go to new page -if (isset($_POST['SaveWikiNew'])) { - if (isset($_POST['reflink'])) { - $wiki->display_wiki_entry($_POST['reflink']); - } -} - -//More for export to course document area. See display_wiki_entry -if (isset($_POST['export2DOC']) && $_POST['export2DOC']) { - $doc_id = $_POST['doc_id']; - $export2doc = $wiki->export2doc($doc_id); - if ($export2doc) { - Display::display_confirmation_message(get_lang('ThePageHasBeenExportedToDocArea')); - } -} - -if (isset($action) && $action =='more') { - echo '
'.get_lang('More').'
'; - echo ''; - echo ' '; - echo ' '; - echo ' '; - echo ''; - echo ' '; - echo '
'; - echo ' '; - echo ' '; - echo ' '; - echo ''; - echo '
    '; - // Submenu Statistics - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - echo '
  • '.get_lang('Statistics').'
  • '; - } - echo '
'; - echo'
'; -} - -// Statistics Juan Carlos Raña Trabado - -if ($action =='statistics' && (api_is_allowed_to_edit(false,true) || api_is_platform_admin())) { - $wiki->getStats(); -} - -// Most active users Juan Carlos Raña Trabado - -if ($action =='mactiveusers') { - $wiki->getActiveUsers($action); -} - -// User contributions Juan Carlos Raña Trabado - -if ($action =='usercontrib') { - $userinfo = api_get_user_info($_GET['user_id']); - $username = api_htmlentities(sprintf(get_lang('LoginX'), $userinfo['username']), ENT_QUOTES); - - echo '
'.get_lang('UserContributions').': '. - Display::tag('span', api_htmlentities(api_get_person_name($userinfo['firstname'], $userinfo['lastname'])), array('title'=>$username)). - '
'; - - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - //only by professors if page is hidden - $sql='SELECT * FROM '.$tbl_wiki.' WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' AND user_id="'.Database::escape_string($_GET['user_id']).'"'; - } else { - $sql='SELECT * FROM '.$tbl_wiki.' WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' AND user_id="'.Database::escape_string($_GET['user_id']).'" AND visibility=1'; - } - - $allpages = Database::query($sql); - - //show table - if (Database::num_rows($allpages) > 0) { - $row = array (); - while ($obj = Database::fetch_object($allpages)) { - //get author - $userinfo = api_get_user_info($obj->user_id); - - //get time - $year = substr($obj->dtime, 0, 4); - $month = substr($obj->dtime, 5, 2); - $day = substr($obj->dtime, 8, 2); - $hours = substr($obj->dtime, 11,2); - $minutes = substr($obj->dtime, 14,2); - $seconds = substr($obj->dtime, 17,2); - - //get type assignment icon - if ($obj->assignment==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDescExtra'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==2) { - $ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==0) { - $ShowAssignment=''; - } - - $row = array (); - $row[] = $year.'-'.$month.'-'.$day.' '.$hours.":".$minutes.":".$seconds; - $row[] =$ShowAssignment; - $row[] = ''.api_htmlentities($obj->title).''; - $row[] =Security::remove_XSS($obj->version); - $row[] =Security::remove_XSS($obj->comment); - $row[] =Security::remove_XSS($obj->progress).' %'; - $row[] =Security::remove_XSS($obj->score); - $rows[] = $row; - - } - - $table = new SortableTableFromArrayConfig($rows,2,10,'UsersContributions_table','','','ASC'); - $table->set_additional_parameters( - array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'user_id'=>Security::remove_XSS($_GET['user_id']),'session_id'=>Security::remove_XSS($_GET['session_id']),'group_id'=>Security::remove_XSS($_GET['group_id'])) - ); - $table->set_header(0,get_lang('Date'), true, array ('style' => 'width:200px;')); - $table->set_header(1,get_lang('Type'), true, array ('style' => 'width:30px;')); - $table->set_header(2,get_lang('Title'), true, array ('style' => 'width:200px;')); - $table->set_header(3,get_lang('Version'), true, array ('style' => 'width:30px;')); - $table->set_header(4,get_lang('Comment'), true, array ('style' => 'width:200px;')); - $table->set_header(5,get_lang('Progress'), true, array ('style' => 'width:30px;')); - $table->set_header(6,get_lang('Rating'), true, array ('style' => 'width:30px;')); - $table->display(); - } -} - -/* Most changed pages */ - -if ($action =='mostchanged') { - echo '
'.get_lang('MostChangedPages').'
'; - - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { //only by professors if page is hidden - $sql='SELECT *, MAX(version) AS MAX FROM '.$tbl_wiki.' WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' GROUP BY reflink';//TODO:check MAX and group by return last version - } else { - $sql='SELECT *, MAX(version) AS MAX FROM '.$tbl_wiki.' WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' AND visibility=1 GROUP BY reflink'; //TODO:check MAX and group by return last version - } - - $allpages=Database::query($sql); - - //show table - if (Database::num_rows($allpages) > 0) { - $row = array (); - while ($obj = Database::fetch_object($allpages)) { - //get type assignment icon - if ($obj->assignment==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDesc'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==2) { - $ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==0) { - $ShowAssignment=''; - } - - $row = array (); - $row[] = $ShowAssignment; - $row[] = ''.api_htmlentities($obj->title).''; - $row[] = $obj->MAX; - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,2,10,'MostChangedPages_table','','','DESC'); - $table->set_additional_parameters(array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'session_id'=>Security::remove_XSS($_GET['session_id']),'group_id'=>Security::remove_XSS($_GET['group_id']))); - $table->set_header(0,get_lang('Type'), true, array ('style' => 'width:30px;')); - $table->set_header(1,get_lang('Title'), true); - $table->set_header(2,get_lang('Changes'), true); - $table->display(); - } -} - -/* Most visited pages */ - -if ($action =='mvisited') { - echo '
'.get_lang('MostVisitedPages').'
'; - - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { //only by professors if page is hidden - $sql = 'SELECT *, SUM(hits) AS tsum FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' - GROUP BY reflink'; - } else { - $sql = 'SELECT *, SUM(hits) AS tsum FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' AND visibility=1 - GROUP BY reflink'; - } - - $allpages=Database::query($sql); - - //show table - if (Database::num_rows($allpages) > 0) { - $row = array (); - while ($obj = Database::fetch_object($allpages)) { - //get type assignment icon - if ($obj->assignment==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDesc'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==2) { - $ShowAssignment=$ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==0) { - $ShowAssignment=''; - } - - $row = array (); - $row[] =$ShowAssignment; - $row[] = ''.api_htmlentities($obj->title).''; - $row[] = $obj->tsum; - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,2,10,'MostVisitedPages_table','','','DESC'); - $table->set_additional_parameters(array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'session_id'=>Security::remove_XSS($_GET['session_id']),'group_id'=>Security::remove_XSS($_GET['group_id']))); - $table->set_header(0,get_lang('Type'), true, array ('style' => 'width:30px;')); - $table->set_header(1,get_lang('Title'), true); - $table->set_header(2,get_lang('Visits'), true); - $table->display(); - } -} - -/* Wanted pages */ - -if ($action =='wanted') { - echo '
'.get_lang('WantedPages').'
'; - $pages = array(); - $refs = array(); - $wanted = array(); - //get name pages - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' - GROUP BY reflink ORDER BY reflink ASC'; - $allpages=Database::query($sql); - - while ($row=Database::fetch_array($allpages)) { - if ($row['reflink']=='index'){ - $row['reflink']=str_replace(' ','_',get_lang('DefaultTitle')); - } - $pages[] = $row['reflink']; - } - - //get name refs in last pages - $sql = 'SELECT * FROM '.$tbl_wiki.' s1 - WHERE s1.c_id = '.$course_id.' AND id=( - SELECT MAX(s2.id) FROM '.$tbl_wiki.' s2 - WHERE s2.c_id = '.$course_id.' AND s1.reflink = s2.reflink AND '.$groupfilter.$condition_session.' - )'; - - $allpages = Database::query($sql); - - while ($row=Database::fetch_array($allpages)) { - $refs = explode(" ", trim($row["linksto"])); - // Find linksto into reflink. If not found ->page is wanted - foreach ($refs as $v) { - - if (!in_array($v, $pages)) { - if (trim($v)!="") { - $wanted[]=$v; - } - } - } - } - - $wanted = array_unique($wanted);//make a unique list - - //show table - $rows = array(); - foreach ($wanted as $wanted_show) { - $row = array(); - $wanted_show=Security::remove_XSS($wanted_show); - $row[] = ''.str_replace('_',' ',$wanted_show).'';//meter un remove xss en lugar de htmlentities - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,0,10,'WantedPages_table','','','DESC'); - $table->set_additional_parameters(array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'session_id'=>Security::remove_XSS($_GET['session_id']),'group_id'=>Security::remove_XSS($_GET['group_id']))); - $table->set_header(0,get_lang('Title'), true); - $table->display(); -} - -/* Orphaned pages */ - -if ($action =='orphaned') { - echo '
'.get_lang('OrphanedPages').'
'; - - $pages = array(); - $refs = array(); - $list_refs = array(); - $orphaned = array(); - - //get name pages - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' - GROUP BY reflink - ORDER BY reflink ASC'; - $allpages=Database::query($sql); - while ($row=Database::fetch_array($allpages)) { - $pages[] = $row['reflink']; - } - - //get name refs in last pages and make a unique list - $sql = 'SELECT * FROM '.$tbl_wiki.' s1 - WHERE s1.c_id = '.$course_id.' AND id=( - SELECT MAX(s2.id) FROM '.$tbl_wiki.' s2 WHERE s2.c_id = '.$course_id.' AND s1.reflink = s2.reflink AND '.$groupfilter.$condition_session.')'; - - $allpages=Database::query($sql); - while ($row=Database::fetch_array($allpages)) { - $row['linksto']= str_replace($row["reflink"], " ", trim($row["linksto"])); //remove self reference - $refs = explode(" ", trim($row["linksto"])); - foreach ($refs as $ref_linked){ - if ($ref_linked==str_replace(' ','_',get_lang('DefaultTitle'))) { - $ref_linked='index'; - } - $array_refs_linked[]= $ref_linked; - } - } - - $array_refs_linked = array_unique($array_refs_linked); - - //search each name of list linksto into list reflink - foreach ($pages as $v) { - if (!in_array($v, $array_refs_linked)) { - $orphaned[] = $v; - } - } - - foreach ($orphaned as $orphaned_show) { - // get visibility status and title - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' AND reflink="'.Database::escape_string($orphaned_show).'" GROUP BY reflink'; - $allpages=Database::query($sql); - while ($row=Database::fetch_array($allpages)) { - $orphaned_title=$row['title']; - $orphaned_visibility=$row['visibility']; - if ($row['assignment']==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png','','',ICON_SIZE_SMALL); - } elseif ($row['assignment']==2) { - $ShowAssignment=Display::return_icon('wiki_work.png','','',ICON_SIZE_SMALL); - } elseif ($row['assignment']==0) { - $ShowAssignment=''; - } - } - if (!api_is_allowed_to_edit(false,true) || !api_is_platform_admin() AND $orphaned_visibility==0){ - continue; - } - - //show table - $row = array(); - $row[] = $ShowAssignment; - $row[] = ''.api_htmlentities($orphaned_title).''; - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,1,10,'OrphanedPages_table','','','DESC'); - $table->set_additional_parameters(array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'session_id'=>Security::remove_XSS($_GET['session_id']),'group_id'=>Security::remove_XSS($_GET['group_id']))); - $table->set_header(0,get_lang('Type'), true, array ('style' => 'width:30px;')); - $table->set_header(1,get_lang('Title'), true); - $table->display(); -} - -/* Most linked pages */ - -if ($action =='mostlinked') { - echo '
'.get_lang('MostLinkedPages').'
'; - $pages = array(); - $refs = array(); - $linked = array(); - - //get name pages - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND '.$groupfilter.$condition_session.' - GROUP BY reflink ORDER BY reflink ASC'; - $allpages=Database::query($sql); +ob_start(); +$wiki->handleAction($action); +$content = ob_get_contents(); +ob_end_clean(); - while ($row=Database::fetch_array($allpages)) { - if ($row['reflink']=='index') { - $row['reflink']=str_replace(' ','_',get_lang('DefaultTitle')); - } - $pages[] = $row['reflink']; - } - - //get name refs in last pages - $sql = 'SELECT * FROM '.$tbl_wiki.' s1 WHERE s1.c_id = '.$course_id.' AND id=( - SELECT MAX(s2.id) FROM '.$tbl_wiki.' s2 WHERE s2.c_id = '.$course_id.' AND s1.reflink = s2.reflink AND '.$groupfilter.$condition_session.')'; - - $allpages=Database::query($sql); - - while ($row=Database::fetch_array($allpages)) { - $row['linksto']= str_replace($row["reflink"], " ", trim($row["linksto"])); //remove self reference - $refs = explode(" ", trim($row["linksto"])); - - // Find linksto into reflink. If found ->page is linked - foreach ($refs as $v) { - if (in_array($v, $pages)) { - if (trim($v)!="") { - $linked[]=$v; - } - } - } - } - - $linked = array_unique($linked); - //make a unique list. TODO:delete this line and count how many for each page - //show table - $rows = array(); - foreach ($linked as $linked_show) { - $row = array(); - $row[] = ''.str_replace('_',' ',$linked_show).''; - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,0,10,'LinkedPages_table','','','DESC'); - $table->set_additional_parameters( - array( - 'cidReq' =>Security::remove_XSS($_GET['cidReq']), - 'action'=>Security::remove_XSS($action ), - 'session_id'=>Security::remove_XSS($_GET['session_id']), - 'group_id'=>Security::remove_XSS($_GET['group_id']) - ) - ); - $table->set_header(0,get_lang('Title'), true); - $table->display(); -} - -/* Delete current page */ - -if ($action =='delete') { - if (!$_GET['title']) { - Display::display_error_message(get_lang('MustSelectPage')); - exit; - } - - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - echo '
'.get_lang('DeletePageHistory').'
'; - - if ($page == "index") { - Display::display_warning_message(get_lang('WarningDeleteMainPage'),false); - } - - $message = get_lang('ConfirmDeletePage')."

"."

"."".get_lang("No").""."  |  "."".get_lang("Yes").""."

"; - - if (!isset ($_GET['delete'])) { - Display::display_warning_message($message,false); - } - - if (isset($_GET['delete']) && $_GET['delete'] == 'yes') { - $result = $wiki->deletePage($page, $course_id, $groupfilter, $condition_session); - if ($result) { - Display::display_confirmation_message(get_lang('WikiPageDeleted')); - } - } - } else { - Display::display_normal_message(get_lang("OnlyAdminDeletePageWiki")); - } -} - -/* Delete all wiki */ - -if ($action =='deletewiki') { - echo '
'.get_lang('DeleteWiki').'
'; - echo '
'; - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - $message = get_lang('ConfirmDeleteWiki'); - $message .= '

- '.get_lang('No').' -   |   - '.get_lang('Yes').' -

'; - - if (!isset($_GET['delete'])) { - Display::display_warning_message($message,false); - } - } else { - Display::display_normal_message(get_lang("OnlyAdminDeleteWiki")); - } - echo '
'; -} - -/* Search wiki pages */ - -if ($action =='searchpages') { - echo '
'.get_lang('SearchPages').'
'; - if (isset($_GET['mode_table'])) { - if (!isset($_GET['SearchPages_table_page_nr'])) { - $_GET['search_term'] = $_POST['search_term']; - $_GET['search_content'] = $_POST['search_content']; - $_GET['all_vers'] = $_POST['all_vers']; - } - $wiki->display_wiki_search_results( - api_htmlentities($_GET['search_term']), - api_htmlentities($_GET['search_content']), - api_htmlentities($_GET['all_vers']) - ); - } else { - - // initiate the object - $form = new FormValidator('wiki_search', - 'post', - api_get_self().'?cidReq='.api_htmlentities($_GET['cidReq']).'&action='.api_htmlentities($action).'&session_id='.api_htmlentities($_GET['session_id']).'&group_id='.api_htmlentities($_GET['group_id']).'&mode_table=yes1&search_term='.api_htmlentities($_GET['search_term']).'&search_content='.api_htmlentities($_GET['search_content']).'&all_vers='.api_htmlentities($_GET['all_vers']) - ); - - // Setting the form elements - - $form->addElement('text', 'search_term', get_lang('SearchTerm'),'class="input_titles" id="search_title"'); - $form->addElement('checkbox', 'search_content', null, get_lang('AlsoSearchContent')); - $form->addElement('checkbox', 'all_vers', null, get_lang('IncludeAllVersions')); - $form->addElement('style_submit_button', 'SubmitWikiSearch', get_lang('Search'), 'class="search"'); - - // setting the rules - $form->addRule('search_term', get_lang('ThisFieldIsRequired'), 'required'); - $form->addRule('search_term', get_lang('TooShort'),'minlength',3); //TODO: before fixing the pagination rules worked, not now - if ($form->validate()) { - $form->display(); - $values = $form->exportValues(); - $wiki->display_wiki_search_results( - $values['search_term'], - $values['search_content'], - $values['all_vers'] - ); - } else { - $form->display(); - } - } -} - -/* What links here. Show pages that have linked this page */ - -if ($action =='links') { - if (!$_GET['title']) { - Display::display_error_message(get_lang("MustSelectPage")); - } else { - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE - c_id = '.$course_id.' AND - reflink="'.Database::escape_string($page).'" AND - '.$groupfilter.$condition_session.''; - $result = Database::query($sql); - $row = Database::fetch_array($result); - - //get type assignment icon - - if ($row['assignment']==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDesc'),'',ICON_SIZE_SMALL); - } elseif ($row['assignment']==2) { - $ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); - } elseif ($row['assignment']==0) { - $ShowAssignment=''; - } - - //fix Title to reflink (link Main Page) - - if ($page==get_lang('DefaultTitle')) { - $page='index'; - } - - echo '
'; - echo get_lang('LinksPagesFrom').': '.$ShowAssignment.' '.api_htmlentities($row['title']).''; - echo '
'; - - //fix index to title Main page into linksto - - if ($page=='index') { - $page=str_replace(' ','_',get_lang('DefaultTitle')); - } - - //table - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { //only by professors if page is hidden - $sql="SELECT * FROM ".$tbl_wiki." s1 WHERE s1.c_id = $course_id AND linksto LIKE '%".Database::escape_string($page)." %' AND id=(SELECT MAX(s2.id) FROM ".$tbl_wiki." s2 WHERE s2.c_id = $course_id AND s1.reflink = s2.reflink AND ".$groupfilter.$condition_session.")";//add blank space after like '%" " %' to identify each word - } else { - $sql="SELECT * FROM ".$tbl_wiki." s1 WHERE s1.c_id = $course_id AND visibility=1 AND linksto LIKE '%".Database::escape_string($page)." %' AND id=(SELECT MAX(s2.id) FROM ".$tbl_wiki." s2 WHERE s2.c_id = $course_id AND s1.reflink = s2.reflink AND ".$groupfilter.$condition_session.")";//add blank space after like '%" " %' to identify each word - } - - $allpages=Database::query($sql); - - //show table - if (Database::num_rows($allpages) > 0) { - $row = array (); - while ($obj = Database::fetch_object($allpages)) { - //get author - $userinfo = api_get_user_info($obj->user_id); - $username = api_htmlentities(sprintf(get_lang('LoginX'), $userinfo['username']), ENT_QUOTES); - - //get time - $year = substr($obj->dtime, 0, 4); - $month = substr($obj->dtime, 5, 2); - $day = substr($obj->dtime, 8, 2); - $hours = substr($obj->dtime, 11,2); - $minutes = substr($obj->dtime, 14,2); - $seconds = substr($obj->dtime, 17,2); - - //get type assignment icon - if ($obj->assignment==1) { - $ShowAssignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDesc'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==2) { - $ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); - } elseif ($obj->assignment==0) { - $ShowAssignment=''; - } - - $row = array (); - $row[] =$ShowAssignment; - $row[] = ''.api_htmlentities($obj->title).''; - if ($obj->user_id <>0) { - $row[] = ''. - Display::tag('span', api_htmlentities(api_get_person_name($userinfo['firstname'], $userinfo['lastname'])), array('title'=>$username)).''; - } - else { - $row[] = get_lang('Anonymous').' ('.$obj->user_ip.')'; - } - $row[] = $year.'-'.$month.'-'.$day.' '.$hours.":".$minutes.":".$seconds; - $rows[] = $row; - } - - $table = new SortableTableFromArrayConfig($rows,1,10,'AllPages_table','','','ASC'); - $table->set_additional_parameters(array('cidReq' =>Security::remove_XSS($_GET['cidReq']),'action'=>Security::remove_XSS($action ),'group_id'=>Security::remove_XSS($_GET['group_id']))); - $table->set_header(0,get_lang('Type'), true, array ('style' => 'width:30px;')); - $table->set_header(1,get_lang('Title'), true); - $table->set_header(2,get_lang('Author'), true); - $table->set_header(3,get_lang('Date'), true); - $table->display(); - } - } -} - -// Adding a new page -// Display the form for adding a new wiki page - -if ($action =='addnew') { - if (api_get_session_id()!=0 && api_is_allowed_to_session_edit(false,true)==false) { - api_not_allowed(); - } - - echo '
'.get_lang('AddNew').'
'; - echo '
'; - //first, check if page index was created. chektitle=false - if ($wiki->checktitle('index')) { - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin() || GroupManager :: is_user_in_group($_user['user_id'],$_SESSION['_gid'])) { - Display::display_normal_message(get_lang('GoAndEditMainPage')); - } else { - return Display::display_normal_message(get_lang('WikiStandBy')); - } - } elseif ($wiki->check_addnewpagelock()==0 && (api_is_allowed_to_edit(false,true)==false || api_is_platform_admin()==false)) { - Display::display_error_message(get_lang('AddPagesLocked')); - } else { - if(api_is_allowed_to_edit(false,true) || api_is_platform_admin() || GroupManager :: is_user_in_group($_user['user_id'],$_SESSION['_gid']) || Security::remove_XSS($_GET['group_id'])==0) { - $wiki->display_new_wiki_form(); - } else { - Display::display_normal_message(get_lang('OnlyAddPagesGroupMembers')); - } - } -} - -// Show home page -if (!$action OR $action =='show' AND !isset($_POST['SaveWikiNew'])) { - $wiki->display_wiki_entry($newtitle); -} - -// Show current page -if ($action =='showpage' AND !isset($_POST['SaveWikiNew'])) { - if ($_GET['title']) { - $wiki->display_wiki_entry($newtitle); - } else { - Display::display_error_message(get_lang('MustSelectPage')); - } -} - -// Edit current page - -if (isset($action) && $action =='edit') { - - if (api_get_session_id()!=0 && api_is_allowed_to_session_edit(false,true)==false) { - api_not_allowed(); - } - - $sql = 'SELECT * FROM '.$tbl_wiki.', '.$tbl_wiki_conf.' - WHERE - '.$tbl_wiki.'.c_id = '.$course_id.' AND - '.$tbl_wiki_conf.'.c_id = '.$course_id.' AND - '.$tbl_wiki_conf.'.page_id='.$tbl_wiki.'.page_id AND - '.$tbl_wiki.'.reflink="'.Database::escape_string($page).'" AND - '.$tbl_wiki.'.'.$groupfilter.$condition_session.' - ORDER BY id DESC'; - $result=Database::query($sql); - $row=Database::fetch_array($result); // we do not need a while loop since we are always displaying the last version - - - if ($row['content']=='' AND $row['title']=='' AND $page=='') { - Display::display_error_message(get_lang('MustSelectPage')); - exit; - } elseif ($row['content']=='' AND $row['title']=='' AND $page=='index') { - //Table structure for better export to pdf - $default_table_for_content_Start='
'; - $default_table_for_content_End='
'; - - $content=$default_table_for_content_Start.sprintf(get_lang('DefaultContent'),api_get_path(WEB_IMG_PATH)).$default_table_for_content_End; - $title=get_lang('DefaultTitle'); - $page_id=0; - } else { - $content = api_html_entity_decode($row['content']); - $title = api_html_entity_decode($row['title']); - $page_id = $row['page_id']; - } - - //Only teachers and platform admin can edit the index page. Only teachers and platform admin can edit an assignment teacher. And users in groups - if (($row['reflink']=='index' || $row['reflink']=='' || $row['assignment']==1) && (!api_is_allowed_to_edit(false,true) && intval($_GET['group_id'])==0)) { - Display::display_error_message(get_lang('OnlyEditPagesCourseManager')); - } else { - $PassEdit=false; - - //check if is a wiki group - if ($groupId!=0) { - //Only teacher, platform admin and group members can edit a wiki group - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin() || GroupManager :: is_user_in_group($_user['user_id'],intval($_GET['group_id']))) { - $PassEdit=true; - } else { - Display::display_normal_message(get_lang('OnlyEditPagesGroupMembers')); - } - } else { - $PassEdit=true; - } - $icon_assignment = null; - // check if is a assignment - if ($row['assignment']==1) { - Display::display_normal_message(get_lang('EditAssignmentWarning')); - $icon_assignment=Display::return_icon('wiki_assignment.png', get_lang('AssignmentDescExtra'),'',ICON_SIZE_SMALL); - } elseif ($row['assignment']==2) { - $icon_assignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWorkExtra'),'',ICON_SIZE_SMALL); - if ((api_get_user_id()==$row['user_id'])==false) { - if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - $PassEdit=true; - } else { - Display::display_warning_message(get_lang('LockByTeacher')); - $PassEdit=false; - } - } else { - $PassEdit=true; - } - } - - if ($PassEdit) { //show editor if edit is allowed - if ($row['editlock']==1 && (api_is_allowed_to_edit(false,true)==false || api_is_platform_admin()==false)) { - Display::display_normal_message(get_lang('PageLockedExtra')); - } else { - //check tasks - if (!empty($row['startdate_assig']) && $row['startdate_assig']!='0000-00-00 00:00:00' && time()strtotime($row['enddate_assig']) && $row['enddate_assig']!='0000-00-00 00:00:00' && $row['delayedsubmit']==0) { - $message=get_lang('TheDeadlineHasBeenCompleted').': '.api_get_local_time($row['enddate_assig'], null, date_default_timezone_get()); - Display::display_warning_message($message); - if (!api_is_allowed_to_edit(false,true)) { - exit; - } - } - - if (!empty($row['max_version']) && $row['version']>=$row['max_version']) { - $message=get_lang('HasReachedMaxiNumVersions'); - Display::display_warning_message($message); - if (!api_is_allowed_to_edit(false,true)) { - exit; - } - } - - if (!empty($row['max_text']) && $row['max_text']<=$wiki->word_count($row['content'])) { - $message=get_lang('HasReachedMaxNumWords'); - Display::display_warning_message($message); - if (!api_is_allowed_to_edit(false,true)) { - exit; - } - } - - if (!empty($row['task'])) { - //previous change 0 by text - if ($row['startdate_assig']=='0000-00-00 00:00:00') { - $message_task_startdate=get_lang('No'); - } else { - $message_task_startdate=api_get_local_time($row['startdate_assig'], null, date_default_timezone_get()); - } - - if ($row['enddate_assig']=='0000-00-00 00:00:00') { - $message_task_enddate=get_lang('No'); - } else { - $message_task_endate=api_get_local_time($row['enddate_assig'], null, date_default_timezone_get()); - } - - if ($row['delayedsubmit']==0) { - $message_task_delayedsubmit=get_lang('No'); - } else { - $message_task_delayedsubmit=get_lang('Yes'); - } - if ($row['max_version']==0) { - $message_task_max_version=get_lang('No'); - } else { - $message_task_max_version=$row['max_version']; - } - if ($row['max_text']==0) { - $message_task_max_text=get_lang('No'); - } else { - $message_task_max_text=$row['max_text']; - } - - //comp message - $message_task=''.get_lang('DescriptionOfTheTask').'

'.$row['task'].'


'; - $message_task.='

'.get_lang('StartDate').': '.$message_task_startdate.'

'; - $message_task.='

'.get_lang('EndDate').': '.$message_task_enddate; - $message_task.=' ('.get_lang('AllowLaterSends').') '.$message_task_delayedsubmit.'

'; - $message_task.='

'.get_lang('OtherSettings').': '.get_lang('NMaxVersion').': '.$message_task_max_version; - $message_task.=' '.get_lang('NMaxWords').': '.$message_task_max_text; - //display message - Display::display_normal_message($message_task,false); - } - - if ($row['progress']==$row['fprogress1'] && !empty($row['fprogress1'])) { - $feedback_message=''.get_lang('Feedback').'

'.api_htmlentities($row['feedback1']).'

'; - Display::display_normal_message($feedback_message, false); - } elseif ($row['progress']==$row['fprogress2'] && !empty($row['fprogress2'])) { - $feedback_message=''.get_lang('Feedback').'

'.api_htmlentities($row['feedback2']).'

'; - Display::display_normal_message($feedback_message, false); - } elseif ($row['progress']==$row['fprogress3'] && !empty($row['fprogress3'])) { - $feedback_message=''.get_lang('Feedback').'

'.api_htmlentities($row['feedback3']).'

'; - Display::display_normal_message($feedback_message, false); - } - - //previous checking for concurrent editions - if ($row['is_editing']==0) { - Display::display_normal_message(get_lang('WarningMaxEditingTime')); - - $time_edit = date("Y-m-d H:i:s"); - $sql='UPDATE '.$tbl_wiki.' SET is_editing="'.$_user['user_id'].'", time_edit="'.$time_edit.'" WHERE c_id = '.$course_id.' AND id="'.$row['id'].'"'; - Database::query($sql); - } elseif ($row['is_editing']!=$_user['user_id']) { - $timestamp_edit=strtotime($row['time_edit']); - $time_editing=time()-$timestamp_edit; - $max_edit_time=1200; // 20 minutes - $rest_time=$max_edit_time-$time_editing; - - $userinfo = api_get_user_info($row['is_editing']); - $username = api_htmlentities(sprintf(get_lang('LoginX'), $userinfo['username']), ENT_QUOTES); - - $is_being_edited= get_lang('ThisPageisBeginEditedBy'). - ' '. - Display::tag('span', api_htmlentities(api_get_person_name($userinfo['firstname'], $userinfo['lastname'])), array('title'=>$username)). - '. '.get_lang('ThisPageisBeginEditedTryLater').' '.date( "i",$rest_time).' '.get_lang('MinMinutes').''; - Display::display_normal_message($is_being_edited, false); - exit; - } - - // Form. - echo '
'.$icon_assignment.str_repeat(' ',3).api_htmlentities($title).'
'; - echo '
'; - - if ((api_is_allowed_to_edit(false,true) || api_is_platform_admin()) && $row['reflink'] != 'index') { - echo ' -
 '. - Display::return_icon( - 'div_show.gif', - get_lang('Show'), - array('style'=>'vertical-align:middle') - ).' '.get_lang('AdvancedParameters').'
'; - - echo ''; - } - - echo ''; - echo ''; - echo ''; - - api_disp_html_area('content', $content, '', '', null, api_is_allowed_to_edit(null,true) - ? array('ToolbarSet' => 'Wiki', 'Width' => '100%', 'Height' => '400') - : array('ToolbarSet' => 'WikiStudent', 'Width' => '100%', 'Height' => '400', 'UserStatus' => 'student') - ); - echo '
'; - echo '
'; - - echo get_lang('Comments').':     '; - echo ''; - echo ''; - - //hack date for edit - echo ''; - echo ''; - - echo get_lang('Progress').':   %'; - echo '

'; - echo '';//prevent double post - //for save button Don't change name (see fckeditor/editor/plugins/customizations/fckplugin_compressed.js and fckplugin.js - echo ''; - echo '
'; - } - } - } -} - -// Page history - -if ($action == 'history' or isset($_POST['HistoryDifferences'])) { - if (!$_GET['title']) { - Display::display_error_message(get_lang("MustSelectPage")); - exit; - } - - /* First, see the property visibility that is at the last register and - therefore we should select descending order. - But to give ownership to each record, - this is no longer necessary except for the title. TODO: check this*/ - - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE c_id = '.$course_id.' AND reflink="'.Database::escape_string($page).'" AND '.$groupfilter.$condition_session.' - ORDER BY id DESC'; - $result=Database::query($sql); - - $KeyVisibility = null; - $KeyAssignment = null; - $KeyTitle = null; - $KeyUserId = null; - while ($row=Database::fetch_array($result)) { - $KeyVisibility = $row['visibility']; - $KeyAssignment = $row['assignment']; - $KeyTitle = $row['title']; - $KeyUserId = $row['user_id']; - } - $icon_assignment = null; - if ($KeyAssignment == 1) { - $icon_assignment = Display::return_icon('wiki_assignment.png', get_lang('AssignmentDescExtra'), '', ICON_SIZE_SMALL); - } elseif($KeyAssignment == 2) { - $icon_assignment = Display::return_icon('wiki_work.png', get_lang('AssignmentWorkExtra'), '', ICON_SIZE_SMALL); - } - - // Second, show - - //if the page is hidden and is a job only sees its author and professor - if ($KeyVisibility == 1 || - api_is_allowed_to_edit(false,true) || - api_is_platform_admin() || - ( - $KeyAssignment==2 && $KeyVisibility==0 && - (api_get_user_id() == $KeyUserId) - ) - ) { - // We show the complete history - if (!isset($_POST['HistoryDifferences']) && !isset($_POST['HistoryDifferences2'])) { - $sql = 'SELECT * FROM '.$tbl_wiki.' - WHERE - c_id = '.$course_id.' AND - reflink="'.Database::escape_string($page).'" AND - '.$groupfilter.$condition_session.' - ORDER BY id DESC'; - $result = Database::query($sql); - $title = $_GET['title']; - $group_id = $_GET['group_id']; - - echo '
'; - echo $icon_assignment.'   '.api_htmlentities($KeyTitle); - echo '
'; - - echo '
'; - - echo '
'; - } else { // We show the differences between two versions - $version_old = array(); - if (isset($_POST['old'])) { - $sql_old= "SELECT * FROM $tbl_wiki - WHERE c_id = $course_id AND id='".Database::escape_string($_POST['old'])."'"; - $result_old=Database::query($sql_old); - $version_old=Database::fetch_array($result_old); - } - - $sql_new="SELECT * FROM $tbl_wiki WHERE c_id = $course_id AND id='".Database::escape_string($_POST['new'])."'"; - $result_new=Database::query($sql_new); - $version_new=Database::fetch_array($result_new); - $oldTime = isset($version_old['dtime']) ? $version_old['dtime'] : null; - $oldContent = isset($version_old['content']) ? $version_old['content'] : null; - - if (isset($_POST['HistoryDifferences'])) { - include 'diff.inc.php'; - //title - echo '
'.api_htmlentities($version_new['title']).' - ('.get_lang('DifferencesNew').' - '.$version_new['dtime'].' - '.get_lang('DifferencesOld').' - '.$oldTime.' - ) '.get_lang('Legend').': '.get_lang('WikiDiffAddedLine').' - '.get_lang('WikiDiffDeletedLine').' '.get_lang('WikiDiffMovedLine').' -
'; - } - if (isset($_POST['HistoryDifferences2'])) { - // including global PEAR diff libraries - require_once 'Text/Diff.php'; - require_once 'Text/Diff/Renderer/inline.php'; - //title - echo '
'.api_htmlentities($version_new['title']).' - ('.get_lang('DifferencesNew').' '.$version_new['dtime'].' - '.get_lang('DifferencesOld').' '.$version_old['dtime'].') - '.get_lang('Legend').': '.get_lang('WikiDiffAddedTex').' - '.get_lang('WikiDiffDeletedTex').'
'; - } - - - if (isset($_POST['HistoryDifferences'])) { - echo ''.diff($oldContent, $version_new['content'], true, 'format_table_line' ).'
'; // format_line mode is better for words - echo '
'; - echo ''.get_lang('Legend').'
' . "\n"; - echo ''; - echo ''; - echo '
'; - echo ''; - echo ''.get_lang('WikiDiffUnchangedLine').'
'; - echo ''.get_lang('WikiDiffAddedLine').'
'; - echo ''.get_lang('WikiDiffDeletedLine').'
'; - echo ''.get_lang('WikiDiffMovedLine').'
'; - echo '
'; - } - - if (isset($_POST['HistoryDifferences2'])) { - $lines1 = array(strip_tags($version_old['content'])); //without <> tags - $lines2 = array(strip_tags($version_new['content'])); //without <> tags - $diff = new Text_Diff($lines1, $lines2); - $renderer = new Text_Diff_Renderer_inline(); - echo ''.$renderer->render($diff); // Code inline - echo '
'; - echo ''.get_lang('Legend').'
' . "\n"; - echo ''; - echo ''; - echo '
'; - echo ''; - echo ''.get_lang('WikiDiffAddedTex').'
'; - echo ''.get_lang('WikiDiffDeletedTex').'
'; - echo '
'; - } - } - } -} +Display::display_header($tool_name, 'Wiki'); -// Recent changes -// @todo rss feed -if ($action =='recentchanges') { - $wiki->recentChanges($page, $action); +// check last version +if (!empty($view)) { + $wiki->setWikiData($view); + $wiki->checkLastVersion($view); } -// All pages -if ($action == 'allpages') { - $wiki->allPages($action); -} +// Tool introduction +Display::display_introduction_section(TOOL_WIKI); -// Discuss pages -if ($action == 'discuss') { - $wiki->getDiscuss($page); -} +$wiki->showActionBar(); +echo $wiki->getMessages(); +echo $content; Display::display_footer(); diff --git a/main/wiki/wiki.inc.php b/main/wiki/wiki.inc.php index 1a67ab4302..98228a2085 100644 --- a/main/wiki/wiki.inc.php +++ b/main/wiki/wiki.inc.php @@ -8,6 +8,8 @@ * @package chamilo.wiki */ +use \ChamiloSession as Session; + class Wiki { public $tbl_wiki; @@ -23,6 +25,9 @@ class Wiki public $courseInfo; public $charset; public $page; + public $action; + public $wikiData = array(); + public $url; public function __construct() { @@ -40,6 +45,7 @@ class Wiki $this->groupfilter = ' group_id="'.$this->group_id.'"'; } $this->courseInfo = api_get_course_info(); + $this->url = api_get_path(WEB_CODE_PATH).'wiki/index.php?'.api_get_cidreq(); } /** @@ -48,7 +54,7 @@ class Wiki * @return bool False if title is already taken * @author Patrick Cool , Ghent University **/ - function checktitle($paramwk) + public function checktitle($paramwk) { $tbl_wiki = $this->tbl_wiki; $condition_session = $this->condition_session; @@ -60,10 +66,10 @@ class Wiki c_id = '.$course_id.' AND reflink="'.Database::escape_string($paramwk).'" AND '.$groupfilter.$condition_session.''; - $result=Database::query($sql); - $numberofresults=Database::num_rows($result); + $result = Database::query($sql); + $numberofresults = Database::num_rows($result); // the value has not been found and is this available - if ($numberofresults==0) { + if ($numberofresults == 0) { return true; } else { // the value has been found @@ -74,8 +80,9 @@ class Wiki /** * check wikilinks that has a page * @author Juan Carlos Raña + * @param string $input **/ - function links_to($input) + public function links_to($input) { $input_array = preg_split("/(\[\[|\]\])/",$input,-1, PREG_SPLIT_DELIM_CAPTURE); $all_links = array(); @@ -107,7 +114,7 @@ class Wiki * detect and add style to external links * @author Juan Carlos Raña Trabado **/ - function detect_external_link($input) + public function detect_external_link($input) { $exlink='href='; $exlinkStyle='class="wiki_link_ext" href='; @@ -119,7 +126,7 @@ class Wiki * detect and add style to anchor links * @author Juan Carlos Raña Trabado **/ - function detect_anchor_link($input) + public function detect_anchor_link($input) { $anchorlink = 'href="#'; $anchorlinkStyle='class="wiki_anchor_link" href="#'; @@ -132,7 +139,7 @@ class Wiki * detect and add style to mail links * author Juan Carlos Raña Trabado **/ - function detect_mail_link($input) + public function detect_mail_link($input) { $maillink='href="mailto'; $maillinkStyle='class="wiki_mail_link" href="mailto'; @@ -144,7 +151,7 @@ class Wiki * detect and add style to ftp links * @author Juan Carlos Raña Trabado **/ - function detect_ftp_link($input) + public function detect_ftp_link($input) { $ftplink='href="ftp'; $ftplinkStyle='class="wiki_ftp_link" href="ftp'; @@ -156,7 +163,7 @@ class Wiki * detect and add style to news links * @author Juan Carlos Raña Trabado **/ - function detect_news_link($input) + public function detect_news_link($input) { $newslink='href="news'; $newslinkStyle='class="wiki_news_link" href="news'; @@ -168,7 +175,7 @@ class Wiki * detect and add style to irc links * @author Juan Carlos Raña Trabado **/ - function detect_irc_link($input) + public function detect_irc_link($input) { $irclink='href="irc'; $irclinkStyle='class="wiki_irc_link" href="irc'; @@ -183,7 +190,7 @@ class Wiki * Improvements [[]] and [[ | ]]by Juan Carlos Raña * Improvements internal wiki style and mark group by Juan Carlos Raña **/ - function make_wiki_link_clickable($input) + public function make_wiki_link_clickable($input) { $groupId = api_get_group_id(); //now doubles brackets @@ -232,11 +239,15 @@ class Wiki * @author Patrick Cool , Ghent University * @return language string saying that the changes are stored **/ - function save_wiki() + public function save_wiki($values) { $tbl_wiki = $this->tbl_wiki; $tbl_wiki_conf = $this->tbl_wiki_conf; $_course = $this->courseInfo; + $dtime = date( "Y-m-d H:i:s" ); + $session_id = api_get_session_id(); + $groupId = api_get_group_id(); + $_clean = array( 'task' => null, 'feedback1' => null, @@ -247,71 +258,68 @@ class Wiki 'fprogress3' => null, 'max_text' => null, 'max_version' => null, - 'delayedsubmit' => null + 'delayedsubmit' => null, + 'assignment' => null ); // NOTE: visibility, visibility_disc and ratinglock_disc changes are not made here, but through the interce buttons // cleaning the variables - $_clean['page_id'] = Database::escape_string($_POST['page_id']); - $_clean['reflink'] = Database::escape_string(trim($_POST['reflink'])); - $_clean['title'] = Database::escape_string(trim($_POST['title'])); - $_clean['content'] = Database::escape_string($_POST['content']); + $_clean['page_id'] = Database::escape_string($values['page_id']); + $_clean['reflink'] = Database::escape_string(trim($values['reflink'])); + $_clean['title'] = Database::escape_string(trim($values['title'])); + $_clean['content'] = Database::escape_string($values['content']); if (api_get_setting('htmlpurifier_wiki') == 'true'){ $purifier = new HTMLPurifier(); $_clean['content'] = $purifier->purify($_clean['content']); } $_clean['user_id'] = api_get_user_id(); - $_clean['assignment'] = Database::escape_string($_POST['assignment']); - $_clean['comment'] = Database::escape_string($_POST['comment']); - $_clean['progress'] = Database::escape_string($_POST['progress']); - $_clean['version'] = intval($_POST['version']) + 1 ; + $_clean['assignment'] = Database::escape_string($values['assignment']); + $_clean['comment'] = Database::escape_string($values['comment']); + $_clean['progress'] = Database::escape_string($values['progress']); + $_clean['version'] = intval($values['version']) + 1 ; $_clean['linksto'] = self::links_to($_clean['content']); //and check links content - $dtime = date( "Y-m-d H:i:s" ); - $session_id = api_get_session_id(); - $groupId = api_get_group_id(); - //cleaning config variables - if (!empty($_POST['task'])) { - $_clean['task'] = Database::escape_string($_POST['task']); + if (!empty($values['task'])) { + $_clean['task'] = Database::escape_string($values['task']); } - if (!empty($_POST['feedback1']) || !empty($_POST['feedback2']) || !empty($_POST['feedback3'])) { - $_clean['feedback1']=Database::escape_string($_POST['feedback1']); - $_clean['feedback2']=Database::escape_string($_POST['feedback2']); - $_clean['feedback3']=Database::escape_string($_POST['feedback3']); - $_clean['fprogress1']=Database::escape_string($_POST['fprogress1']); - $_clean['fprogress2']=Database::escape_string($_POST['fprogress2']); - $_clean['fprogress3']=Database::escape_string($_POST['fprogress3']); + if (!empty($values['feedback1']) || !empty($values['feedback2']) || !empty($values['feedback3'])) { + $_clean['feedback1']=Database::escape_string($values['feedback1']); + $_clean['feedback2']=Database::escape_string($values['feedback2']); + $_clean['feedback3']=Database::escape_string($values['feedback3']); + $_clean['fprogress1']=Database::escape_string($values['fprogress1']); + $_clean['fprogress2']=Database::escape_string($values['fprogress2']); + $_clean['fprogress3']=Database::escape_string($values['fprogress3']); } - if (isset($_POST['initstartdate']) && $_POST['initstartdate'] == 1) { - $_clean['startdate_assig']=Database::escape_string(self::get_date_from_select('startdate_assig')); + if (isset($values['initstartdate']) && $values['initstartdate'] == 1) { + $_clean['startdate_assig'] = Database::escape_string($values['startdate_assig']); } else { - $_clean['startdate_assig']=Database::escape_string($_POST['startdate_assig']); + $_clean['startdate_assig'] = '0000-00-00 00:00:00'; } - if (isset($_POST['initenddate']) && $_POST['initenddate']==1) { - $_clean['enddate_assig'] = Database::escape_string(self::get_date_from_select('enddate_assig')); + if (isset($values['initenddate']) && $values['initenddate']==1) { + $_clean['enddate_assig'] = Database::escape_string($values['enddate_assig']); } else { - $_clean['enddate_assig'] = Database::escape_string($_POST['enddate_assig']); + $_clean['enddate_assig'] = '0000-00-00 00:00:00'; } - if (isset($_POST['delayedsubmit'])) { - $_clean['delayedsubmit']=Database::escape_string($_POST['delayedsubmit']); + if (isset($values['delayedsubmit'])) { + $_clean['delayedsubmit']=Database::escape_string($values['delayedsubmit']); } - if (!empty($_POST['max_text']) || !empty($_POST['max_version'])) { - $_clean['max_text'] =Database::escape_string($_POST['max_text']); - $_clean['max_version']=Database::escape_string($_POST['max_version']); + if (!empty($values['max_text']) || !empty($values['max_version'])) { + $_clean['max_text'] =Database::escape_string($values['max_text']); + $_clean['max_version']=Database::escape_string($values['max_version']); } $course_id = api_get_course_int_id(); $sql = "INSERT INTO ".$tbl_wiki." (c_id, page_id, reflink, title, content, user_id, group_id, dtime, assignment, comment, progress, version, linksto, user_ip, session_id) VALUES ($course_id, '".$_clean['page_id']."','".$_clean['reflink']."','".$_clean['title']."','".$_clean['content']."','".$_clean['user_id']."','".$groupId."','".$dtime."','".$_clean['assignment']."','".$_clean['comment']."','".$_clean['progress']."','".$_clean['version']."','".$_clean['linksto']."','".Database::escape_string($_SERVER['REMOTE_ADDR'])."', '".Database::escape_string($session_id)."')"; Database::query($sql); - $Id = Database::insert_id(); + $Id = Database::insert_id(); if ($Id > 0) { //insert into item_property @@ -328,13 +336,29 @@ class Wiki $sql="INSERT INTO ".$tbl_wiki_conf." (c_id, page_id, task, feedback1, feedback2, feedback3, fprogress1, fprogress2, fprogress3, max_text, max_version, startdate_assig, enddate_assig, delayedsubmit) VALUES ($course_id, '".$Id."','".$_clean['task']."','".$_clean['feedback1']."','".$_clean['feedback2']."','".$_clean['feedback3']."','".$_clean['fprogress1']."','".$_clean['fprogress2']."','".$_clean['fprogress3']."','".$_clean['max_text']."','".$_clean['max_version']."','".$_clean['startdate_assig']."','".$_clean['enddate_assig']."','".$_clean['delayedsubmit']."')"; } else { - $sql='UPDATE'.$tbl_wiki_conf.' SET task="'.$_clean['task'].'", feedback1="'.$_clean['feedback1'].'", feedback2="'.$_clean['feedback2'].'", feedback3="'.$_clean['feedback3'].'", fprogress1="'.$_clean['fprogress1'].'", fprogress2="'.$_clean['fprogress2'].'", fprogress3="'.$_clean['fprogress3'].'", max_text="'.$_clean['max_text'].'", max_version="'.$_clean['max_version'].'", startdate_assig="'.$_clean['startdate_assig'].'", enddate_assig="'.$_clean['enddate_assig'].'", delayedsubmit="'.$_clean['delayedsubmit'].'" - WHERE page_id="'.$_clean['page_id'].'" AND c_id = '.$course_id; + $sql = 'UPDATE '.$tbl_wiki_conf.' SET + task="'.$_clean['task'].'", + feedback1="'.$_clean['feedback1'].'", + feedback2="'.$_clean['feedback2'].'", + feedback3="'.$_clean['feedback3'].'", + fprogress1="'.$_clean['fprogress1'].'", + fprogress2="'.$_clean['fprogress2'].'", + fprogress3="'.$_clean['fprogress3'].'", + max_text="'.$_clean['max_text'].'", + max_version="'.$_clean['max_version'].'", + startdate_assig="'.$_clean['startdate_assig'].'", + enddate_assig="'.$_clean['enddate_assig'].'", + delayedsubmit="'.$_clean['delayedsubmit'].'" + WHERE + page_id = "'.$_clean['page_id'].'" AND + c_id = '.$course_id; } Database::query($sql); api_item_property_update($_course, 'wiki', $Id, 'WikiAdded', api_get_user_id(), $groupId); self::check_emailcue($_clean['reflink'], 'P', $dtime, $_clean['user_id']); - return get_lang('ChangesStored'); + $this->setWikiData($Id); + + return get_lang('Saved'); } /** @@ -342,7 +366,7 @@ class Wiki * @author Juan Carlos Raña * @return string Message of success (to be printed on screen) **/ - function restore_wikipage($r_page_id, $r_reflink, $r_title, $r_content, $r_group_id, $r_assignment, $r_progress, $c_version, $r_version, $r_linksto) + public function restore_wikipage($r_page_id, $r_reflink, $r_title, $r_content, $r_group_id, $r_assignment, $r_progress, $c_version, $r_version, $r_linksto) { $tbl_wiki = $this->tbl_wiki; $_course = $this->courseInfo; @@ -370,7 +394,7 @@ class Wiki * @author Juan Carlos Raña * @return string Message of success (to be printed) **/ - function delete_wiki() + public function delete_wiki() { $tbl_wiki = $this->tbl_wiki; $tbl_wiki_discuss = $this->tbl_wiki_discuss; @@ -401,14 +425,13 @@ class Wiki return get_lang('WikiDeleted'); } - /** * This function saves a new wiki page. * @author Patrick Cool , Ghent University * @todo consider merging this with the function save_wiki into one single function. * @return string Message of success **/ - function save_new_wiki() + public function save_new_wiki($values) { $tbl_wiki = $this->tbl_wiki; $tbl_wiki_conf = $this->tbl_wiki_conf; @@ -419,21 +442,21 @@ class Wiki // cleaning the variables $_clean['assignment'] = null; - if (isset($_POST['assignment'])) { - $_clean['assignment'] = Database::escape_string($_POST['assignment']); + if (isset($values['assignment'])) { + $_clean['assignment'] = Database::escape_string($values['assignment']); } // session_id $session_id = api_get_session_id(); // Unlike ordinary pages of pages of assignments. Allow create a ordinary page although there is a assignment with the same name if ($_clean['assignment']==2 || $_clean['assignment']==1) { - $page = str_replace(' ','_',$_POST['title']."_uass".$assig_user_id); + $page = str_replace(' ','_',$values['title']."_uass".$assig_user_id); } else { - $page = str_replace(' ','_',$_POST['title']); + $page = str_replace(' ','_',$values['title']); } $_clean['reflink'] = Database::escape_string(strip_tags(api_htmlentities($page))); - $_clean['title'] = Database::escape_string(strip_tags(trim($_POST['title']))); - $_clean['content'] = Database::escape_string($_POST['content']); + $_clean['title'] = Database::escape_string(strip_tags(trim($values['title']))); + $_clean['content'] = Database::escape_string($values['content']); if (api_get_setting('htmlpurifier_wiki') == 'true'){ $purifier = new HTMLPurifier(); @@ -458,8 +481,8 @@ class Wiki $_clean['ratinglock_disc']=1; } - $_clean['comment']=Database::escape_string($_POST['comment']); - $_clean['progress']=Database::escape_string($_POST['progress']); + $_clean['comment']=Database::escape_string($values['comment']); + $_clean['progress']=Database::escape_string($values['progress']); $_clean['version']=1; $groupId = api_get_group_id(); @@ -467,64 +490,146 @@ class Wiki $_clean['linksto'] = self::links_to($_clean['content']); //check wikilinks //cleaning config variables - $_clean['task']= Database::escape_string($_POST['task']); - $_clean['feedback1']=Database::escape_string($_POST['feedback1']); - $_clean['feedback2']=Database::escape_string($_POST['feedback2']); - $_clean['feedback3']=Database::escape_string($_POST['feedback3']); - $_clean['fprogress1']=Database::escape_string($_POST['fprogress1']); - $_clean['fprogress2']=Database::escape_string($_POST['fprogress2']); - $_clean['fprogress3']=Database::escape_string($_POST['fprogress3']); - - if (isset($_POST['initstartdate']) && $_POST['initstartdate'] == 1) { - $_clean['startdate_assig'] = Database::escape_string(self::get_date_from_select('startdate_assig')); + $_clean['task']= Database::escape_string($values['task']); + $_clean['feedback1']=Database::escape_string($values['feedback1']); + $_clean['feedback2']=Database::escape_string($values['feedback2']); + $_clean['feedback3']=Database::escape_string($values['feedback3']); + $_clean['fprogress1']=Database::escape_string($values['fprogress1']); + $_clean['fprogress2']=Database::escape_string($values['fprogress2']); + $_clean['fprogress3']=Database::escape_string($values['fprogress3']); + + if (isset($values['initstartdate']) && $values['initstartdate'] == 1) { + $_clean['startdate_assig'] = Database::escape_string($values['startdate_assig']); } else { - $_clean['startdate_assig'] = Database::escape_string($_POST['startdate_assig']); + $_clean['startdate_assig'] = '0000-00-00 00:00:00'; } - if (isset($_POST['initenddate']) && $_POST['initenddate']==1) { - $_clean['enddate_assig']=Database::escape_string(self::get_date_from_select('enddate_assig')); + if (isset($values['initenddate']) && $values['initenddate']==1) { + $_clean['enddate_assig'] = Database::escape_string($values['enddate_assig']); } else { - $_clean['enddate_assig']=Database::escape_string($_POST['enddate_assig']); + $_clean['enddate_assig'] = '0000-00-00 00:00:00'; } - $_clean['delayedsubmit']=Database::escape_string($_POST['delayedsubmit']); - $_clean['max_text']=Database::escape_string($_POST['max_text']); - $_clean['max_version']=Database::escape_string($_POST['max_version']); + $_clean['delayedsubmit']=Database::escape_string($values['delayedsubmit']); + $_clean['max_text']=Database::escape_string($values['max_text']); + $_clean['max_version']=Database::escape_string($values['max_version']); $course_id = api_get_course_int_id(); - //filter no _uass - if (api_eregi('_uass', $_POST['title']) || (api_strtoupper(trim($_POST['title'])) == 'INDEX' || api_strtoupper(trim(api_htmlentities($_POST['title'], ENT_QUOTES, $charset))) == api_strtoupper(api_htmlentities(get_lang('DefaultTitle'), ENT_QUOTES, $charset)))) { - $message= get_lang('GoAndEditMainPage'); - Display::display_warning_message($message,false); + // Filter no _uass + if (api_eregi('_uass', $values['title']) || + (api_strtoupper(trim($values['title'])) == 'INDEX' || + api_strtoupper(trim(api_htmlentities($values['title'], ENT_QUOTES, $charset))) == api_strtoupper(api_htmlentities(get_lang('DefaultTitle'), ENT_QUOTES, $charset))) + ) { + self::setMessage(Display::display_warning_message(get_lang('GoAndEditMainPage'), false, true)); } else { - $var=$_clean['reflink']; - $group_id=Security::remove_XSS($_GET['group_id']); - if(!self::checktitle($var)) { - return get_lang('WikiPageTitleExist').''.$_POST['title'].''; + $var = $_clean['reflink']; + $group_id = Security::remove_XSS($_GET['group_id']); + if (!self::checktitle($var)) { + return get_lang('WikiPageTitleExist').''.$values['title'].''; } else { $dtime = date( "Y-m-d H:i:s" ); $sql = "INSERT INTO ".$tbl_wiki." (c_id, reflink, title, content, user_id, group_id, dtime, visibility, visibility_disc, ratinglock_disc, assignment, comment, progress, version, linksto, user_ip, session_id) VALUES ($course_id, '".$_clean['reflink']."','".$_clean['title']."','".$_clean['content']."','".$_clean['user_id']."','".$groupId."','".$dtime."','".$_clean['visibility']."','".$_clean['visibility_disc']."','".$_clean['ratinglock_disc']."','".$_clean['assignment']."','".$_clean['comment']."','".$_clean['progress']."','".$_clean['version']."','".$_clean['linksto']."','".Database::escape_string($_SERVER['REMOTE_ADDR'])."', '".Database::escape_string($session_id)."')"; - $result = Database::query($sql); + Database::query($sql); $Id = Database::insert_id(); if ($Id > 0) { //insert into item_property api_item_property_update(api_get_course_info(), TOOL_WIKI, $Id, 'WikiAdded', api_get_user_id(), $groupId); } - $sql='UPDATE '.$tbl_wiki.' SET page_id="'.$Id.'" WHERE c_id = '.$course_id.' AND id="'.$Id.'"'; - Database::query($sql); + $sql = 'UPDATE '.$tbl_wiki.' SET page_id="'.$Id.'" WHERE c_id = '.$course_id.' AND id="'.$Id.'"'; + Database::query($sql); //insert wiki config - $sql="INSERT INTO ".$tbl_wiki_conf." (c_id, page_id, task, feedback1, feedback2, feedback3, fprogress1, fprogress2, fprogress3, max_text, max_version, startdate_assig, enddate_assig, delayedsubmit) VALUES - ($course_id, '".$Id."','".$_clean['task']."','".$_clean['feedback1']."','".$_clean['feedback2']."','".$_clean['feedback3']."','".$_clean['fprogress1']."','".$_clean['fprogress2']."','".$_clean['fprogress3']."','".$_clean['max_text']."','".$_clean['max_version']."','".$_clean['startdate_assig']."','".$_clean['enddate_assig']."','".$_clean['delayedsubmit']."')"; - Database::query($sql); - self::check_emailcue(0, 'A'); + $sql = " INSERT INTO ".$tbl_wiki_conf." (c_id, page_id, task, feedback1, feedback2, feedback3, fprogress1, fprogress2, fprogress3, max_text, max_version, startdate_assig, enddate_assig, delayedsubmit) + VALUES ($course_id, '".$Id."','".$_clean['task']."','".$_clean['feedback1']."','".$_clean['feedback2']."','".$_clean['feedback3']."','".$_clean['fprogress1']."','".$_clean['fprogress2']."','".$_clean['fprogress3']."','".$_clean['max_text']."','".$_clean['max_version']."','".$_clean['startdate_assig']."','".$_clean['enddate_assig']."','".$_clean['delayedsubmit']."')"; + Database::query($sql); + $this->setWikiData($Id); + self::check_emailcue(0, 'A'); + return get_lang('NewWikiSaved'); + } + } + } + + /** + * @param FormValidator $form + * @param array $row + */ + public function setForm($form, $row = array()) + { + $toolBar = api_is_allowed_to_edit(null,true) + ? array('ToolbarSet' => 'Wiki', 'Width' => '100%', 'Height' => '400') + : array('ToolbarSet' => 'WikiStudent', 'Width' => '100%', 'Height' => '400', 'UserStatus' => 'student'); + + $form->add_html_editor('content', get_lang('Content'), false, false, $toolBar); + //$content + $form->addElement('text', 'comment', get_lang('Comments')); + $progress = array('', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100); + + $form->addElement('select', 'progress', get_lang('Progress'), $progress); + + if ((api_is_allowed_to_edit(false,true) || api_is_platform_admin()) && isset($row['reflink']) && $row['reflink'] != 'index') { + + /* $advanced = ' +
 '. + Display::return_icon( + 'div_show.gif', + get_lang('Show'), + array('style'=>'vertical-align:middle') + ).' '.get_lang('AdvancedParameters').'
'; +*/ + $form->addElement('advanced_settings', 'settings', get_lang('AdvancedParameters')); + + $form->addElement('html', ''); } + + $form->addElement('hidden', 'page_id'); + $form->addElement('hidden', 'reflink'); +// $form->addElement('hidden', 'assignment'); + $form->addElement('hidden', 'version'); + $form->addElement('hidden', 'wpost_id', api_get_unique_id()); } /** @@ -532,194 +637,53 @@ class Wiki * @author Patrick Cool , Ghent University * @return html code **/ - function display_new_wiki_form() - { - $page = $this->page; - ?> - - '; - echo '
'; - echo '
- * '.get_lang('Title').':
'; - - if(api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { - echo' '.Display::return_icon('div_show.gif',get_lang('Show'),array('style'=>'vertical-align:middle')).' '.get_lang('AdvancedParameters').''; - echo ''; + if ($return_message == false) { + self::setMessage(Display::display_error_message(get_lang('NoWikiPageTitle'), false, true)); + } else { + self::setMessage(Display::display_confirmation_message($return_message, false, true)); + } + $wikiData = self::getWikiData(); + $redirectUrl = $this->url.'&action=showpage&title='.$wikiData['reflink']; + header('Location: '.$redirectUrl); + exit; + } } - echo '
'; - echo '
'; - api_disp_html_area('content', '', '', '', null, api_is_allowed_to_edit(null,true) - ? array('ToolbarSet' => 'Wiki', 'Width' => '100%', 'Height' => '400') - : array('ToolbarSet' => 'WikiStudent', 'Width' => '100%', 'Height' => '400', 'UserStatus' => 'student') - ); - echo '
'; - echo '
'; - echo get_lang('Comments').':  

'; - echo get_lang('Progress').':   %'; - echo '

'; - echo '';//prevent double post - echo '';//for button icon. Don't change name (see fckeditor/editor/plugins/customizations/fckplugin_compressed.js and fckplugin.js - echo '
'; - echo ''; } /** * This function displays a wiki entry * @author Patrick Cool , Ghent University * @author Juan Carlos Raña Trabado - * @return html code + * @param string $newtitle + * @return string html code **/ - function display_wiki_entry($newtitle) + public function display_wiki_entry($newtitle) { $tbl_wiki = $this->tbl_wiki; $tbl_wiki_conf = $this->tbl_wiki_conf; @@ -750,7 +714,7 @@ class Wiki '.$groupfilter.$condition_session.' ORDER BY id ASC'; $result=Database::query($sql); - $row=Database::fetch_array($result); + $row = Database::fetch_array($result); $KeyVisibility=$row['visibility']; // second, show the last version @@ -787,7 +751,7 @@ class Wiki $content=$default_table_for_content_Start.sprintf(get_lang('DefaultContent'),api_get_path(WEB_IMG_PATH)).$default_table_for_content_End; $title=get_lang('DefaultTitle'); } else { - return Display::display_normal_message(get_lang('WikiStandBy')); + return self::setMessage(Display::display_normal_message(get_lang('WikiStandBy'), false, true)); } } else { $content = Security::remove_XSS(api_html_entity_decode($row['content']), COURSEMANAGERLOWSECURITY); @@ -872,24 +836,31 @@ class Wiki //ONly available if row['id'] is set if ($row['id']) { //page action: export to pdf - echo ''; - echo '
'; + echo ''; + echo ''; echo ''; echo ''; - echo ''; + echo ''; echo ''; echo '
'; - //page action: copy last version to doc area + // Page action: copy last version to doc area if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { echo ''; - echo '
'; - echo ''; + echo ''; + echo ''; echo ''; - echo ''; + echo ''; echo '
'; echo '
'; } + + if (api_is_unoconv_installed()) { + echo ''; + echo ''. + Display::return_icon('export_doc.png', get_lang('ExportToDoc'), array(), ICON_SIZE_SMALL).''; + echo ''; + } } //export to print @@ -932,7 +903,7 @@ class Wiki ) ) ).'
'; - echo '
'.get_lang('Progress').': '.$row['progress'].'%   '.get_lang('Rating').': '.$row['score'].'   '.get_lang('Words').': '.self::word_count($content).'
'; + echo '
'.get_lang('Progress').': '.($row['progress']*10).'%   '.get_lang('Rating').': '.$row['score'].'   '.get_lang('Words').': '.self::word_count($content).'
'; } //end filter visibility } @@ -941,7 +912,7 @@ class Wiki * @param string Document's text * @return int Number of words */ - function word_count($document) + public function word_count($document) { $search = array( '@]*?>.*?@si', @@ -979,7 +950,7 @@ class Wiki /** * This function checks if wiki title exist */ - function wiki_exist($title) + public function wiki_exist($title) { $tbl_wiki = $this->tbl_wiki; $groupfilter = $this->groupfilter; @@ -1007,7 +978,7 @@ class Wiki * @author Patrick Cool , Ghent University * @return html code */ - function is_active_navigation_tab($paramwk) + public function is_active_navigation_tab($paramwk) { if (isset($_GET['action']) && $_GET['action'] == $paramwk) { return ' class="active"'; @@ -1019,7 +990,7 @@ class Wiki * @author Juan Carlos Raña * return current database status of protect page and change it if get action */ - function check_addnewpagelock() + public function check_addnewpagelock() { $tbl_wiki = $this->tbl_wiki; $condition_session = $this->condition_session; @@ -1054,7 +1025,7 @@ class Wiki * @author Juan Carlos Raña * return current database status of protect page and change it if get action */ - function check_protect_page() + public function check_protect_page() { $tbl_wiki = $this->tbl_wiki; $condition_session = $this->condition_session; @@ -1102,7 +1073,7 @@ class Wiki * @author Juan Carlos Raña * return current database status of visibility and change it if get action */ - function check_visibility_page() + public function check_visibility_page() { $tbl_wiki = $this->tbl_wiki; $page = $this->page; @@ -1151,7 +1122,7 @@ class Wiki * @author Juan Carlos Raña * @return int current database status of discuss visibility and change it if get action page */ - function check_visibility_discuss() + public function check_visibility_discuss() { $tbl_wiki = $this->tbl_wiki; $page = $this->page; @@ -1199,7 +1170,7 @@ class Wiki * @author Juan Carlos Raña * @return int current database status of lock dicuss and change if get action */ - function check_addlock_discuss() + public function check_addlock_discuss() { $tbl_wiki = $this->tbl_wiki; $page = $this->page; @@ -1237,7 +1208,6 @@ class Wiki } return $row['addlock_disc']; - } /** @@ -1245,7 +1215,7 @@ class Wiki * @author Juan Carlos Raña * @return int current database status of rating discuss and change it if get action */ - function check_ratinglock_discuss() + public function check_ratinglock_discuss() { $tbl_wiki = $this->tbl_wiki; $page = $this->page; @@ -1288,7 +1258,7 @@ class Wiki * @author Juan Carlos Raña * @return int the current notification status */ - function check_notify_page($reflink) + public function check_notify_page($reflink) { $tbl_wiki = $this->tbl_wiki; $tbl_wiki_mailcue = $this->tbl_wiki_mailcue; @@ -1330,26 +1300,26 @@ class Wiki ($course_id, '".$id."','".api_get_user_id()."','P','".$groupId."','".$session_id."')"; Database::query($sql); } - $status_notify=1; } + if (isset($_GET['actionpage']) && $_GET['actionpage'] =='unlocknotify' && $status_notify==1) { $sql = 'DELETE FROM '.$tbl_wiki_mailcue.' WHERE id="'.$id.'" AND user_id="'.api_get_user_id().'" AND type="P" AND c_id = '.$course_id; Database::query($sql); - $status_notify=0; } - //show status + return $status_notify; } /** * Notify discussion changes * @author Juan Carlos Raña + * @param string $reflink * @return int current database status of rating discuss and change it if get action */ - function check_notify_discuss($reflink) + public function check_notify_discuss($reflink) { $tbl_wiki_mailcue = $this->tbl_wiki_mailcue; $tbl_wiki = $this->tbl_wiki; @@ -1400,7 +1370,7 @@ class Wiki * Notify all changes * @author Juan Carlos Raña */ - function check_notify_all() + public function check_notify_all() { $tbl_wiki_mailcue = $this->tbl_wiki_mailcue; $course_id = api_get_course_int_id(); @@ -1442,7 +1412,7 @@ class Wiki /** * Sends pending e-mails */ - function check_emailcue($id_or_ref, $type, $lastime='', $lastuser='') + public function check_emailcue($id_or_ref, $type, $lastime='', $lastuser='') { $tbl_wiki_mailcue = $this->tbl_wiki_mailcue; $tbl_wiki = $this->tbl_wiki; @@ -1610,11 +1580,11 @@ class Wiki * Function export last wiki page version to document area * @author Juan Carlos Raña */ - function export2doc($doc_id) + public function export2doc($doc_id) { $_course = $this->courseInfo; - $groupId = api_get_group_id(); - $data = self::get_wiki_data($doc_id); + $groupId = api_get_group_id(); + $data = self::get_wiki_data($doc_id); if (empty($data)) { return false; @@ -1692,6 +1662,7 @@ class Wiki $wikiFileName = $exportFile . '_' . $i . '.html'; $exportPath = $exportDir . '/' . $wikiFileName; file_put_contents( $exportPath, $wikiContents ); + require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; $doc_id = add_document($_course, $groupPath.'/'.$wikiFileName, 'file', filesize($exportPath), $wikiTitle); api_item_property_update($_course, TOOL_DOCUMENT, $doc_id, 'DocumentAdded', api_get_user_id(), $groupId); @@ -1702,8 +1673,14 @@ class Wiki /** * Exports the wiki page to PDF */ - function export_to_pdf($id, $course_code) + public function export_to_pdf($id, $course_code) { + if (!api_is_platform_admin()) { + if (api_get_setting('students_export2pdf') == 'true') { + return false; + } + } + require_once api_get_path(LIBRARY_PATH).'pdf.lib.php'; $data = self::get_wiki_data($id); $content_pdf = api_html_entity_decode($data['content'], ENT_QUOTES, api_get_system_encoding()); @@ -1712,8 +1689,8 @@ class Wiki $content_pdf=trim(preg_replace("/\[[\[]?([^\]|]*)[|]?([^|\]]*)\][\]]?/", "$1", $content_pdf)); //TODO: It should be better to display the link insted of the tile but it is hard for [[title]] links - $title_pdf = api_html_entity_decode($data['title'], ENT_QUOTES, api_get_system_encoding()); - $title_pdf = api_utf8_encode($title_pdf, api_get_system_encoding()); + $title_pdf = api_html_entity_decode($data['title'], ENT_QUOTES, api_get_system_encoding()); + $title_pdf = api_utf8_encode($title_pdf, api_get_system_encoding()); $content_pdf = api_utf8_encode($content_pdf, api_get_system_encoding()); $html=' @@ -1730,7 +1707,6 @@ class Wiki mpdf-->'.$content_pdf; - $css_file = api_get_path(TO_SYS, WEB_CSS_PATH).api_get_setting('stylesheets').'/print.css'; if (file_exists($css_file)) { $css = @file_get_contents($css_file); @@ -1747,7 +1723,7 @@ class Wiki * Function prevent double post (reload or F5) * */ - function double_post($wpost_id) + public function double_post($wpost_id) { if (isset($_SESSION['wpost_id'])) { if ($wpost_id == $_SESSION['wpost_id']) { @@ -1766,8 +1742,10 @@ class Wiki * Function wizard individual assignment * @author Juan Carlos Raña */ - function auto_add_page_users($assignment_type) + public function auto_add_page_users($values) { + $assignment_type = $values['assignment']; + //$assig_user_id is need to identify end reflinks $session_id = $this->session_id; $groupId = api_get_group_id(); @@ -1775,9 +1753,9 @@ class Wiki if ($groupId==0) { //extract course members if(!empty($session_id)) { - $a_users_to_add = CourseManager :: get_user_list_from_course_code($_SESSION['_course']['id'], $session_id); + $a_users_to_add = CourseManager::get_user_list_from_course_code(api_get_course_id(), $session_id); } else { - $a_users_to_add = CourseManager :: get_user_list_from_course_code($_SESSION['_course']['id'], 0); + $a_users_to_add = CourseManager::get_user_list_from_course_code(api_get_course_id(), 0); } } else { //extract group members @@ -1808,20 +1786,24 @@ class Wiki } //teacher assignment title - $title_orig=$_POST['title']; + $title_orig = $values['title']; //teacher assignment reflink - $link2teacher=$_POST['title']= $title_orig."_uass".api_get_user_id(); + $link2teacher = $values['title'] = $title_orig."_uass".api_get_user_id(); //first: teacher name, photo, and assignment description (original content) // $content_orig_A='
'.$photo.'
'.api_get_person_name($userinfo['firstname'], $userinfo['lastname']).'
('.get_lang('Teacher').')

'; - $content_orig_A='
'.get_lang('AssignmentDesc').'
'.$photo.'
'.Display::tag('span', api_get_person_name($userinfo['firstname'], $userinfo['lastname']), array('title'=>$username)).'
'; + $content_orig_A='
+ + + +
'.get_lang('AssignmentDesc').'
'.$photo.'
'.Display::tag('span', api_get_person_name($userinfo['firstname'], $userinfo['lastname']), array('title'=>$username)).'
'; $content_orig_B='
'.get_lang('AssignmentDescription').': '.$title_orig.'

'.$_POST['content']; //Second: student list (names, photo and links to their works). //Third: Create Students work pages. - foreach($a_users_to_add as $user_id=>$o_user_to_add) { - if($o_user_to_add['user_id'] != api_get_user_id()) { + foreach ($a_users_to_add as $o_user_to_add) { + if ($o_user_to_add['user_id'] != api_get_user_id()) { //except that puts the task $assig_user_id = $o_user_to_add['user_id']; //identifies each page as created by the student, not by teacher $image_path = UserManager::get_user_picture_path_by_id($assig_user_id,'web',false, true); @@ -1844,45 +1826,54 @@ class Wiki } } - if($assignment_type==1) { - $_POST['title']= $title_orig; - $_POST['comment']=get_lang('AssignmentFirstComToStudent'); - $_POST['content']='
'.get_lang('AssignmentWork').'
'.$photo.'
'.$name.'
[['.$link2teacher.' | '.get_lang('AssignmentLinktoTeacherPage').']] '; //If $content_orig_B is added here, the task written by the professor was copied to the page of each student. TODO: config options - - //AssignmentLinktoTeacherPage + if ($assignment_type==1) { + $values['title']= $title_orig; + //$values['comment'] = get_lang('AssignmentFirstComToStudent'); + $values['content'] = '
+ + +
'.get_lang('AssignmentWork').'
'.$photo.'
'.$name.'
+
[['.$link2teacher.' | '.get_lang('AssignmentLinktoTeacherPage').']] '; + //If $content_orig_B is added here, the task written by the professor was copied to the page of each student. TODO: config options + + // AssignmentLinktoTeacherPage $all_students_pages[] = '
  • '. - Display::tag('span', strtoupper($o_user_to_add['lastname']).', '.$o_user_to_add['firstname'], array('title'=>$username)). - ' [['.$_POST['title']."_uass".$assig_user_id.' | '.$photo.']] '.$status_in_group.'
  • '; //don't change this line without guaranteeing that users will be ordered by last names in the following format (surname, name) - $_POST['assignment']=2; + Display::tag( + 'span', + strtoupper($o_user_to_add['lastname']).', '.$o_user_to_add['firstname'], array('title'=>$username) + ). + ' [['.$_POST['title']."_uass".$assig_user_id.' | '.$photo.']] '.$status_in_group.''; + //don't change this line without guaranteeing that users will be ordered by last names in the following format (surname, name) + $values['assignment']=2; } $this->assig_user_id = $assig_user_id; - self::save_new_wiki(); + self::save_new_wiki($values); } - }//end foreach for each user + } - foreach ($a_users_to_add as $user_id=>$o_user_to_add) { - if($o_user_to_add['user_id'] == api_get_user_id()) { + foreach ($a_users_to_add as $o_user_to_add) { + if ($o_user_to_add['user_id'] == api_get_user_id()) { $assig_user_id=$o_user_to_add['user_id']; - if($assignment_type==1) { - $_POST['title']= $title_orig; - $_POST['comment']=get_lang('AssignmentDesc'); + if ($assignment_type == 1) { + $values['title']= $title_orig; + $values['comment']=get_lang('AssignmentDesc'); sort($all_students_pages); - $_POST['content']=$content_orig_A.$content_orig_B.'
    '.get_lang('AssignmentLinkstoStudentsPage').'

      '.implode($all_students_pages).'

    '; - $_POST['assignment']=1; + $values['content']=$content_orig_A.$content_orig_B.'
    '.get_lang('AssignmentLinkstoStudentsPage').'

      '.implode($all_students_pages).'

    '; + $values['assignment']=1; } $this->assig_user_id = $assig_user_id; - self::save_new_wiki(); + self::save_new_wiki($values); } - } //end foreach to teacher + } } /** * Displays the results of a wiki search * @param string Search term * @param int Whether to search the contents (1) or just the titles (0) + * @param int */ - - function display_wiki_search_results($search_term, $search_content=0, $all_vers=0) + public function display_wiki_search_results($search_term, $search_content=0, $all_vers=0) { $tbl_wiki = $this->tbl_wiki; $condition_session = $this->condition_session; @@ -1959,7 +1950,7 @@ class Wiki } elseif ($obj->assignment==2) { $ShowAssignment=Display::return_icon('wiki_work.png', get_lang('AssignmentWork'),'',ICON_SIZE_SMALL); } elseif ($obj->assignment==0) { - $ShowAssignment=''; + $ShowAssignment= Display::return_icon('px_transparent.gif'); } $row = array(); $row[] =$ShowAssignment; @@ -1970,7 +1961,8 @@ class Wiki $row[] = ''.$obj->title.''; } - $row[] = $obj->user_id <>0 ? ''.api_htmlentities($userinfo['complete_name']).'' : get_lang('Anonymous').' ('.$obj->user_ip.')'; + $row[] = $obj->user_id <>0 ? ''. + api_htmlentities($userinfo['complete_name']).'' : get_lang('Anonymous').' ('.$obj->user_ip.')'; $row[] = $year.'-'.$month.'-'.$day.' '.$hours.":".$minutes.":".$seconds; if ($all_vers=='1') { @@ -1979,7 +1971,13 @@ class Wiki if (api_is_allowed_to_edit(false,true)|| api_is_platform_admin()) { $showdelete=' '.Display::return_icon('delete.png', get_lang('Delete'),'',ICON_SIZE_SMALL); } - $row[] = ''.Display::return_icon('edit.png', get_lang('EditPage'),'',ICON_SIZE_SMALL).' '.Display::return_icon('discuss.png', get_lang('Discuss'),'',ICON_SIZE_SMALL).' '.Display::return_icon('history.png', get_lang('History'),'',ICON_SIZE_SMALL).' '.Display::return_icon('what_link_here.png', get_lang('LinksPages'),'',ICON_SIZE_SMALL).''.$showdelete; + $row[] = ''. + Display::return_icon('edit.png', get_lang('EditPage'),'',ICON_SIZE_SMALL).' + '. + Display::return_icon('discuss.png', get_lang('Discuss'),'',ICON_SIZE_SMALL).' + '. + Display::return_icon('history.png', get_lang('History'),'',ICON_SIZE_SMALL).' '. + Display::return_icon('what_link_here.png', get_lang('LinksPages'),'',ICON_SIZE_SMALL).''.$showdelete; } $rows[] = $row; } @@ -2008,7 +2006,7 @@ class Wiki * @todo replace this function with the formvalidator datepicker * */ - function draw_date_picker($prefix,$default='') + public function draw_date_picker($prefix,$default='') { if (empty($default)) { $default = date('Y-m-d H:i:s'); @@ -2047,7 +2045,7 @@ class Wiki * Draws an HTML form select with the given options * */ - function make_select($name,$values,$checked='') + public function make_select($name,$values,$checked='') { $output = ' '; + echo ' '; + echo ''; + echo ''; + echo api_get_local_time($row['dtime'], null, date_default_timezone_get()); + echo ''; + echo ' ('.get_lang('Version').' '.$row['version'].')'; + echo ' '.get_lang('By').' '; + if ($row['user_id']<>0) { + echo ''. + Display::tag('span', api_htmlentities(api_get_person_name($userinfo['firstname'], $userinfo['lastname'])), array('title'=>$username)). + ''; + } else { + echo get_lang('Anonymous').' ('.api_htmlentities($row['user_ip']).')'; + } + echo ' ( '.get_lang('Progress').': '.api_htmlentities($row['progress']).'%, '; + $comment = $row['comment']; + if (!empty($comment)) { + echo get_lang('Comments').': '.api_htmlentities(api_substr($row['comment'],0,100)); + if (api_strlen($row['comment'])>100) { + echo '... '; + } + } else { + echo get_lang('Comments').': ---'; + } + echo ' ) '; + $counter++; + } //end while + + echo '
    '; + echo ''; + echo ''; + echo ''; + } else { // We show the differences between two versions + $version_old = array(); + if (isset($_POST['old'])) { + $sql_old= "SELECT * FROM $tbl_wiki + WHERE c_id = $course_id AND id='".Database::escape_string($_POST['old'])."'"; + $result_old=Database::query($sql_old); + $version_old=Database::fetch_array($result_old); + } + + $sql_new="SELECT * FROM $tbl_wiki WHERE c_id = $course_id AND id='".Database::escape_string($_POST['new'])."'"; + $result_new=Database::query($sql_new); + $version_new=Database::fetch_array($result_new); + $oldTime = isset($version_old['dtime']) ? $version_old['dtime'] : null; + $oldContent = isset($version_old['content']) ? $version_old['content'] : null; + + if (isset($_POST['HistoryDifferences'])) { + include 'diff.inc.php'; + //title + echo '
    '.api_htmlentities($version_new['title']).' + ('.get_lang('DifferencesNew').' + '.$version_new['dtime'].' + '.get_lang('DifferencesOld').' + '.$oldTime.' + ) '.get_lang('Legend').': '.get_lang('WikiDiffAddedLine').' + '.get_lang('WikiDiffDeletedLine').' '.get_lang('WikiDiffMovedLine').' +
    '; + } + if (isset($_POST['HistoryDifferences2'])) { + // including global PEAR diff libraries + require_once 'Text/Diff.php'; + require_once 'Text/Diff/Renderer/inline.php'; + //title + echo '
    '.api_htmlentities($version_new['title']).' + ('.get_lang('DifferencesNew').' '.$version_new['dtime'].' + '.get_lang('DifferencesOld').' '.$oldTime.') + '.get_lang('Legend').': '.get_lang('WikiDiffAddedTex').' + '.get_lang('WikiDiffDeletedTex').'
    '; + } + + + if (isset($_POST['HistoryDifferences'])) { + echo ''.diff($oldContent, $version_new['content'], true, 'format_table_line' ).'
    '; // format_line mode is better for words + echo '
    '; + echo ''.get_lang('Legend').'
    ' . "\n"; + echo ''; + echo ''; + echo '
    '; + echo ''; + echo ''.get_lang('WikiDiffUnchangedLine').'
    '; + echo ''.get_lang('WikiDiffAddedLine').'
    '; + echo ''.get_lang('WikiDiffDeletedLine').'
    '; + echo ''.get_lang('WikiDiffMovedLine').'
    '; + echo '
    '; + } + + if (isset($_POST['HistoryDifferences2'])) { + $lines1 = array(strip_tags($oldContent)); //without <> tags + $lines2 = array(strip_tags($version_new['content'])); //without <> tags + $diff = new Text_Diff($lines1, $lines2); + $renderer = new Text_Diff_Renderer_inline(); + echo ''.$renderer->render($diff); // Code inline + echo '
    '; + echo ''.get_lang('Legend').'
    ' . "\n"; + echo ''; + echo ''; + echo '
    '; + echo ''; + echo ''.get_lang('WikiDiffAddedTex').'
    '; + echo ''.get_lang('WikiDiffDeletedTex').'
    '; + echo '
    '; + } + } + } + } + + /** + * Get stat tables + */ + public function getStatsTable() + { + $_course = $this->courseInfo; + $session_id = $this->session_id; + $groupId = $this->group_id; + + echo '
    '.get_lang('More').'
    '; + echo ''; + echo ' '; + echo ' '; + echo ' '; + echo ''; + echo ' '; + echo '
    '; + echo ' '; + echo ' '; + echo ' '; + echo ''; + echo '
      '; + // Submenu Statistics + if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { + echo '
    • '.get_lang('Statistics').'
    • '; + } + echo '
    '; + echo'
    '; + } + + /** + * Kind of controller + * @param string $action + */ + public function handleAction($action) + { + $page = $this->page; + + switch ($action) { + case 'export_to_pdf': + if (isset($_GET['wiki_id'])) { + self::export_to_pdf($_GET['wiki_id'], api_get_course_id()); + exit; + } + break; + case 'export2doc': + if (isset($_GET['doc_id'])) { + $export2doc = self::export2doc($_GET['doc_id']); + if ($export2doc) { + self::setMessage( + Display::display_confirmation_message( + get_lang('ThePageHasBeenExportedToDocArea'), + false, + true + ) + ); + } + } + break; + case 'restorepage': + self::restorePage(); + break; + case 'more': + self::getStatsTable(); + break; + case 'statistics': + self::getStats(); + break; + case 'mactiveusers': + self::getActiveUsers($action); + break; + case 'usercontrib': + self::getUserContributions($_GET['user_id'], $action); + break; + case 'mostchanged': + $this->getMostChangedPages($action); + break; + case 'mvisited': + self::getMostVisited(); + break; + case 'wanted': + $this->getWantedPages(); + break; + case 'orphaned': + self::getOrphaned(); + break; + case 'mostlinked': + self::getMostLinked(); + break; + case 'delete': + self::deletePageWarning($page); + break; + case 'deletewiki': + $title = '
    '.get_lang('DeleteWiki').'
    '; + if (api_is_allowed_to_edit(false,true) || api_is_platform_admin()) { + $message = get_lang('ConfirmDeleteWiki'); + $message .= '

    + '.get_lang('No').' +   |   + '.get_lang('Yes').' +

    '; + + if (!isset($_GET['delete'])) { + self::setMessage($title.Display::display_warning_message($message, false, true)); + } + } else { + self::setMessage(Display::display_normal_message(get_lang("OnlyAdminDeleteWiki"), false, true)); + } + + if (api_is_allowed_to_edit(false, true) || api_is_platform_admin()) { + if (isset($_GET['delete']) && $_GET['delete'] == 'yes') { + $return_message = self::delete_wiki(); + self::setMessage(Display::display_confirmation_message($return_message, false, true)); + $this->redirectHome(); + } + } + break; + case 'searchpages': + self::getSearchPages($action); + break; + case 'links': + self::getLinks($page); + break; + case 'addnew': + if (api_get_session_id()!=0 && api_is_allowed_to_session_edit(false,true)==false) { + api_not_allowed(); + } + echo '
    '.get_lang('AddNew').'
    '; + echo '
    '; + //first, check if page index was created. chektitle=false + if (self::checktitle('index')) { + if (api_is_allowed_to_edit(false,true) || + api_is_platform_admin() || + GroupManager::is_user_in_group(api_get_user_id(), api_get_group_id()) + ) { + self::setMessage(Display::display_normal_message(get_lang('GoAndEditMainPage'), false, true)); + } else { + self::setMessage(Display::display_normal_message(get_lang('WikiStandBy'), false, true)); + } + } elseif (self::check_addnewpagelock()==0 && (api_is_allowed_to_edit(false, true)==false || api_is_platform_admin()==false)) { + self::setMessage(Display::display_error_message(get_lang('AddPagesLocked'), false, true)); + } else { + if (api_is_allowed_to_edit(false,true) || + api_is_platform_admin() || + GroupManager::is_user_in_group(api_get_user_id(), api_get_group_id()) || + $_GET['group_id'] == 0 + ) { + self::display_new_wiki_form(); + } else { + self::setMessage(Display::display_normal_message(get_lang('OnlyAddPagesGroupMembers'), false, true)); + } + } + break; + case 'show': + self::display_wiki_entry($page); + break; + case 'showpage': + self::display_wiki_entry($page); + //self::setMessage(Display::display_error_message(get_lang('MustSelectPage')); + break; + case 'edit': + self::editPage(); + break; + case 'history': + self::getHistory(); + break; + case 'recentchanges': + self::recentChanges($page, $action); + break; + case 'allpages': + self::allPages($action); + break; + case 'discuss': + //self::setMessage(Display::display_confirmation_message(get_lang('CommentAdded')); + self::getDiscuss($page); + break; + case 'export_to_doc_file': + self::exportTo($_GET['id'], 'odt'); + exit; + break; + } + } + + /** + * @param string $message + */ + public function setMessage($message) + { + $messagesArray = Session::read('wiki_message'); + if (empty($messagesArray)) { + $messagesArray = array($message); + } else { + $messagesArray[] = $message; + } + + Session::write('wiki_message', $messagesArray); + } + + /** + * Get messages + * @return string + */ + public function getMessages() + { + $messagesArray = Session::read('wiki_message'); + $messageToString = null; + if (!empty($messagesArray)) { + foreach ($messagesArray as $message) { + $messageToString .= $message.'
    '; + } + } + Session::erase('wiki_message'); + + return $messageToString; + } + + /** + * Redirect to home + */ + public function redirectHome() + { + $redirectUrl = $this->url.'&action=showpage&title=index'; + header('Location: '.$redirectUrl); + exit; + } + + /** + * Export wiki content in a odf + * @param int $id + * @param string int + * @return bool + */ + public function exportTo($id, $format = 'doc') + { + $data = self::get_wiki_data($id); + if (!empty($data['content'])) { + global $app; + $content = $app['chamilo.filesystem']->convertRelativeToAbsoluteUrl($data['content']); + $filePath = $app['chamilo.filesystem']->putContentInTempFile($content, $data['reflink'], 'html'); + $convertedFile = $app['chamilo.filesystem']->transcode($filePath, $format); + + DocumentManager::file_send_for_download($convertedFile); + } + return false; + } +} + diff --git a/src/ChamiloLMS/Component/DataFilesystem/DataFilesystem.php b/src/ChamiloLMS/Component/DataFilesystem/DataFilesystem.php index c083170f47..bd48a34d0f 100644 --- a/src/ChamiloLMS/Component/DataFilesystem/DataFilesystem.php +++ b/src/ChamiloLMS/Component/DataFilesystem/DataFilesystem.php @@ -6,6 +6,10 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Console; +use Unoconv\Unoconv; +use Sunra\PhpSimple\HtmlDomParser; +use ChamiloLMS\Component\Editor\Connector; +use ChamiloLMS\Component\Editor\Driver\CourseDriver; /** * @todo use Gaufrette to manage course files (some day) @@ -24,11 +28,15 @@ class DataFilesystem /** * @param array $paths * @param Filesystem $filesystem + * @param Unoconv $unoconv */ - public function __construct($paths, Filesystem $filesystem) + public function __construct($paths, Filesystem $filesystem, Connector $editor, $unoconv = null) { $this->paths = $paths; $this->fs = $filesystem; + $this->unoconv = $unoconv; + $this->editor = $editor; + $this->editor->setDriver('CourseDriver'); } /** @@ -140,4 +148,92 @@ class DataFilesystem $styleSheetFolder = $this->paths['root_sys'].'main/css'; return $finder->directories()->depth('== 0')->in($styleSheetFolder); } + + /** + * Creates a empty file inside the temp folder + * @param string $fileName + * @param string $extension + * @return string + */ + public function createTempFile($fileName = null, $extension = null) + { + if (empty($fileName)) { + $fileName = mt_rand(); + } + if (!empty($extension)) { + $extension = ".$extension"; + } + $filePath = $this->paths['sys_temp_path'].$fileName.$extension; + $this->fs->touch($filePath); + if ($this->fs->exists($filePath)) { + return $filePath; + } + return null; + } + + /** + * Converts ../courses/ABC/document/file.jpg to + * http://chamilo/courses/ABC/document/file.jpg + * @param string $content + * @return string + */ + public function convertRelativeToAbsoluteUrl($content) + { + /** @var CourseDriver $courseDriver */ + $courseDriver = $this->editor->getDriver('CourseDriver'); + + $dom = HtmlDomParser::str_get_html($content); + //var_dump($this->editor->getDrivers()); + /** @var \simple_html_dom_node $image */ + foreach ($dom->find('img') as $image) { + $image->src = str_replace( + $courseDriver->getCourseDocumentRelativeWebPath(), + $courseDriver->getCourseDocumentWebPath(), + $image->src + ); + } + return $dom; + } + + /** + * Save string in a temp file + * @param string $content + * @param string $fileName + * @param string $extension + * + * @return string file path + */ + public function putContentInTempFile($content, $filename = null, $extension = null) + { + $file = $this->createTempFile($filename, $extension); + if (!empty($file)) { + $this->fs->dumpFile($file, $content); + return $file; + } + return null; + } + + /** + * @param string $filePath + * @param string $format + * @return string + */ + public function transcode($filePath, $format) + { + if ($this->fs->exists($filePath)) { + $fileInfo = pathinfo($filePath); + $fileName = $fileInfo['filename']; + $newFilePath = str_replace( + $fileInfo['basename'], + $fileName.'.'.$format, $filePath + ); + $this->unoconv->transcode($filePath, $format, $newFilePath); + if ($this->fs->exists($newFilePath)) { + return $newFilePath; + + } + } + return false; + } + } diff --git a/src/ChamiloLMS/Component/Editor/Connector.php b/src/ChamiloLMS/Component/Editor/Connector.php index a145f6e087..d79be21e5e 100644 --- a/src/ChamiloLMS/Component/Editor/Connector.php +++ b/src/ChamiloLMS/Component/Editor/Connector.php @@ -170,7 +170,6 @@ class Connector return $driverUpdated; } - /** * Get default driver settings. * @return array @@ -269,19 +268,35 @@ class Connector ) ); - foreach ($this->getDriverList() as $driverName) { - $driverClass = $this->getDriverClass($driverName); - /** @var Driver $driver */ - $driver = new $driverClass(); - $driver->setName($driverName); - $driver->setConnector($this); - $this->addDriver($driver); - } + $this->setDrivers(); $opts['roots'] = $this->getRoots(); return $opts; } + /** + * Set drivers from list + */ + public function setDrivers() + { + foreach ($this->getDriverList() as $driverName) { + $this->setDriver($driverName); + } + } + + /** + * Sets a driver. + * @param string $driverName + */ + public function setDriver($driverName) + { + $driverClass = $this->getDriverClass($driverName); + /** @var Driver $driver */ + $driver = new $driverClass(); + $driver->setName($driverName); + $driver->setConnector($this); + $this->addDriver($driver); + } /** * Simple function to demonstrate how to control file access using "accessControl" callback. diff --git a/src/ChamiloLMS/Component/Editor/Driver/CourseDriver.php b/src/ChamiloLMS/Component/Editor/Driver/CourseDriver.php index 428356f8bb..5e15552134 100644 --- a/src/ChamiloLMS/Component/Editor/Driver/CourseDriver.php +++ b/src/ChamiloLMS/Component/Editor/Driver/CourseDriver.php @@ -22,7 +22,7 @@ class CourseDriver extends Driver return array( 'driver' => 'CourseDriver', 'path' => $this->getCourseDocumentSysPath(), - 'URL' => $this->getCourseDocumentWebPath(), + 'URL' => $this->getCourseDocumentRelativeWebPath(), 'accessControl' => array($this, 'access'), 'alias' => $alias, 'attributes' => array( @@ -58,7 +58,7 @@ class CourseDriver extends Driver /** * @return string */ - public function getCourseDocumentWebPath() + public function getCourseDocumentRelativeWebPath() { $url = null; if (isset($this->connector->course)) { @@ -68,6 +68,20 @@ class CourseDriver extends Driver return $url; } + + /** + * @return string + */ + public function getCourseDocumentWebPath() + { + $url = null; + if (isset($this->connector->course)) { + $directory = $this->connector->course->getDirectory(); + $url = api_get_path(WEB_DATA_COURSE_PATH).$directory.'/document/'; + } + return $url; + } + /** * {@inheritdoc} */ diff --git a/src/ChamiloLMS/Component/Editor/Driver/CourseUserDriver.php b/src/ChamiloLMS/Component/Editor/Driver/CourseUserDriver.php index 34e68524eb..e17f577b34 100644 --- a/src/ChamiloLMS/Component/Editor/Driver/CourseUserDriver.php +++ b/src/ChamiloLMS/Component/Editor/Driver/CourseUserDriver.php @@ -28,7 +28,7 @@ class CourseUserDriver extends CourseDriver 'alias' => $alias, 'path' => $this->getCourseDocumentSysPath().$path, //'alias' => $courseInfo['code'].' personal documents', - 'URL' => $this->getCourseDocumentWebPath().$path, + 'URL' => $this->getCourseDocumentRelativeWebPath().$path, 'accessControl' => 'access' ); } diff --git a/src/ChamiloLMS/Component/Editor/Driver/DropBoxDriver.php b/src/ChamiloLMS/Component/Editor/Driver/DropBoxDriver.php index 88b8b0c4ae..987cdcb68e 100644 --- a/src/ChamiloLMS/Component/Editor/Driver/DropBoxDriver.php +++ b/src/ChamiloLMS/Component/Editor/Driver/DropBoxDriver.php @@ -81,7 +81,7 @@ class DropBoxDriver extends \elFinderVolumeMySQL implements InterfaceDriver 'alias' => 'dropbox', 'tmpPath' => $this->connector->paths['sys_temp_path'], //'alias' => $courseInfo['code'].' personal documents', - //'URL' => $this->getCourseDocumentWebPath().$path, + //'URL' => $this->getCourseDocumentRelativeWebPath().$path, 'accessControl' => 'access' ); } diff --git a/vendor/autoload.php b/vendor/autoload.php index 088c34b044..214d93da4f 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer' . '/autoload_real.php'; -return ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581::getLoader(); +return ComposerAutoloaderInitda8df92eadc454c2aaf367aac7d7b7ce::getLoader(); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 5a1ce5546d..928767b078 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -11,10 +11,6 @@ return array( 'Absolute_Positioner' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/absolute_positioner.cls.php', 'AbstractLink' => $baseDir . '/main/gradebook/lib/be/abstractlink.class.php', 'Abstract_Renderer' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/abstract_renderer.cls.php', - 'Access' => $baseDir . '/main/inc/lib/access.class.php', - 'AccessAll' => $baseDir . '/main/inc/lib/access.class.php', - 'AccessForbidden' => $baseDir . '/main/inc/lib/access.class.php', - 'AccessToken' => $baseDir . '/main/inc/lib/access_token.class.php', 'Accessurleditcoursestourl' => $baseDir . '/main/inc/lib/access_url_edit_courses_to_url_functions.lib.php', 'Accessurleditsessionstourl' => $baseDir . '/main/inc/lib/access_url_edit_sessions_to_url_functions.lib.php', 'Accessurledituserstourl' => $baseDir . '/main/inc/lib/access_url_edit_users_to_url_functions.lib.php', @@ -22,7 +18,6 @@ return array( 'AddManySessionToCategoryFunctions' => $baseDir . '/main/inc/lib/add_many_session_to_category_functions.lib.php', 'AdminPage' => $baseDir . '/main/admin/admin_page.class.php', 'Agenda' => $baseDir . '/main/inc/lib/agenda.lib.php', - 'AjaxController' => $baseDir . '/main/inc/lib/ajax_controller.class.php', 'Announcement' => $baseDir . '/main/coursecopy/classes/Announcement.class.php', 'AnnouncementEmail' => $baseDir . '/main/inc/lib/announcement_email.class.php', 'AnnouncementManager' => $baseDir . '/main/inc/lib/announcements.inc.php', @@ -59,14 +54,12 @@ return array( 'Cellmap' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/cellmap.cls.php', 'Certificate' => $baseDir . '/main/inc/lib/certificate.lib.php', 'Cezpdf' => $baseDir . '/main/inc/lib/ezpdf/class.ezpdf.php', - 'Chamilo' => $baseDir . '/main/inc/lib/chamilo.class.php', 'ChamiloIndexer' => $baseDir . '/main/inc/lib/search/ChamiloIndexer.class.php', 'ChamiloSession' => $baseDir . '/main/inc/lib/chamilo_session.class.php', 'Chat' => $baseDir . '/main/inc/lib/chat.lib.php', 'ClassManager' => $baseDir . '/main/inc/lib/classmanager.lib.php', 'Collator' => $vendorDir . '/symfony/intl/Symfony/Component/Intl/Resources/stubs/Collator.php', 'ConditionalLogin' => $baseDir . '/main/inc/lib/conditional_login.class.php', - 'Controller' => $baseDir . '/main/inc/lib/controller.class.php', 'Converter' => $baseDir . '/main/inc/lib/system/text/converter.class.php', 'Course' => $baseDir . '/main/coursecopy/classes/Course.class.php', 'CourseArchiver' => $baseDir . '/main/coursecopy/classes/CourseArchiver.class.php', @@ -74,8 +67,6 @@ return array( 'CourseCopyAttendance' => $baseDir . '/main/coursecopy/classes/Attendance.class.php', 'CourseCopyLearnpath' => $baseDir . '/main/coursecopy/classes/CourseCopyLearnpath.class.php', 'CourseDescription' => $baseDir . '/main/inc/lib/course_description.lib.php', - 'CourseEntity' => $baseDir . '/main/inc/lib/course_entity.class.php', - 'CourseEntityRepository' => $baseDir . '/main/inc/lib/course_entity_repository.class.php', 'CourseHome' => $baseDir . '/main/inc/lib/course_home.lib.php', 'CourseManager' => $baseDir . '/main/inc/lib/course.lib.php', 'CourseRecycler' => $baseDir . '/main/coursecopy/classes/CourseRecycler.class.php', @@ -189,8 +180,6 @@ return array( 'DummyCourseCreator' => $baseDir . '/main/coursecopy/classes/DummyCourseCreator.class.php', 'Encoding' => $baseDir . '/main/inc/lib/system/text/encoding.class.php', 'EncodingConverter' => $baseDir . '/main/inc/lib/system/text/encoding_converter.class.php', - 'Entity' => $baseDir . '/main/inc/lib/entity.class.php', - 'EntityRepository' => $baseDir . '/main/inc/lib/entity_repository.class.php', 'EvalForm' => $baseDir . '/main/gradebook/lib/fe/evalform.class.php', 'EvalLink' => $baseDir . '/main/gradebook/lib/be/evallink.class.php', 'Evaluation' => $baseDir . '/main/gradebook/lib/be/evaluation.class.php', @@ -211,7 +200,6 @@ return array( 'FacebookApiException' => $baseDir . '/main/auth/external_login/facebook-php-sdk/src/base_facebook.php', 'FileManager' => $baseDir . '/main/inc/lib/fileManager.lib.php', 'FileReader' => $baseDir . '/main/inc/lib/system/io/file_reader.class.php', - 'FileStore' => $baseDir . '/main/inc/lib/file_store.class.php', 'FileWriter' => $baseDir . '/main/inc/lib/system/io/file_writer.class.php', 'FillBlanks' => $baseDir . '/main/exercice/fill_blanks.class.php', 'FlatViewDataGenerator' => $baseDir . '/main/gradebook/lib/flatview_data_generator.class.php', @@ -364,7 +352,6 @@ return array( 'Inline_Positioner' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/inline_positioner.cls.php', 'Inline_Renderer' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/inline_renderer.cls.php', 'IntlDateFormatter' => $vendorDir . '/symfony/intl/Symfony/Component/Intl/Resources/stubs/IntlDateFormatter.php', - 'Javascript' => $baseDir . '/main/inc/lib/javascript.class.php', 'Javascript_Embedder' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/javascript_embedder.cls.php', 'KeyAuth' => $baseDir . '/main/auth/key/key_auth.class.php', 'LearnpathLink' => $baseDir . '/main/gradebook/lib/be/learnpathlink.class.php', @@ -444,12 +431,7 @@ return array( 'MediaQuestion' => $baseDir . '/main/exercice/media_question.class.php', 'MessageManager' => $baseDir . '/main/inc/lib/message.lib.php', 'Model' => $baseDir . '/main/inc/lib/model.lib.php', - 'Model\\Course' => $baseDir . '/main/inc/lib/course.class.php', 'Model\\Document' => $baseDir . '/main/inc/lib/document.class.php', - 'Model\\ItemProperty' => $baseDir . '/main/inc/lib/item_property.class.php', - 'Model\\ItemPropertyRepository' => $baseDir . '/main/inc/lib/item_property.class.php', - 'Model\\StudentPublication' => $baseDir . '/main/inc/lib/student_publication.class.php', - 'Model\\StudentPublicationRepository' => $baseDir . '/main/inc/lib/student_publication.class.php', 'MultipleAnswer' => $baseDir . '/main/exercice/multiple_answer.class.php', 'MultipleAnswerCombination' => $baseDir . '/main/exercice/multiple_answer_combination.class.php', 'MultipleAnswerCombinationTrueFalse' => $baseDir . '/main/exercice/multiple_answer_combination_true_false.class.php', @@ -486,7 +468,6 @@ return array( 'PGTStorageDB' => $baseDir . '/main/auth/cas/lib/CAS/PGTStorage/pgt-db.php', 'PGTStorageFile' => $baseDir . '/main/auth/cas/lib/CAS/PGTStorage/pgt-file.php', 'PHP_Evaluator' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/php_evaluator.cls.php', - 'Page' => $baseDir . '/main/inc/lib/page.class.php', 'PageController' => $baseDir . '/main/inc/lib/page.lib.php', 'Page_Cache' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/page_cache.cls.php', 'Page_Frame_Decorator' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/page_frame_decorator.cls.php', @@ -528,15 +509,11 @@ return array( 'QuizQuestion' => $baseDir . '/main/coursecopy/classes/QuizQuestion.class.php', 'QuizQuestionOption' => $baseDir . '/main/coursecopy/classes/QuizQuestionOption.class.php', 'RSSCache' => $baseDir . '/main/inc/lib/magpierss/rss_cache.inc', - 'Redirect' => $baseDir . '/main/inc/lib/redirect.class.php', 'Renderer' => $baseDir . '/main/inc/lib/phpdocx/pdf/include/renderer.cls.php', 'Resource' => $baseDir . '/main/coursecopy/classes/Resource.class.php', - 'Response' => $baseDir . '/main/inc/lib/response.class.php', 'Result' => $baseDir . '/main/gradebook/lib/be/result.class.php', - 'ResultSet' => $baseDir . '/main/inc/lib/result_set.class.php', 'ResultTable' => $baseDir . '/main/gradebook/lib/fe/resulttable.class.php', 'ResultsDataGenerator' => $baseDir . '/main/gradebook/lib/results_data_generator.class.php', - 'Rights' => $baseDir . '/main/inc/lib/rights.lib.php', 'S3SoapClient' => $baseDir . '/main/inc/lib/elfinder/php/elFinderVolumeS3.class.php', 'SVG' => $vendorDir . '/mpdf/mpdf/classes/svg.php', 'ScoreDisplay' => $baseDir . '/main/gradebook/lib/scoredisplay.class.php', @@ -684,7 +661,6 @@ return array( 'UniqueAnswerNoOption' => $baseDir . '/main/exercice/unique_answer_no_option.class.php', 'Uri' => $baseDir . '/main/inc/lib/uri.class.php', 'UrlManager' => $baseDir . '/main/inc/lib/urlmanager.lib.php', - 'UserApiKeyManager' => $baseDir . '/main/inc/lib/user_api_key_manager.class.php', 'UserDataGenerator' => $baseDir . '/main/gradebook/lib/user_data_generator.class.php', 'UserForm' => $baseDir . '/main/gradebook/lib/fe/userform.class.php', 'UserGroup' => $baseDir . '/main/inc/lib/usergroup.lib.php', diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php index b6ead455f1..0e4beb230b 100644 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -44,6 +44,7 @@ return array( 'Symfony\\Bridge\\Monolog\\' => array($vendorDir . '/symfony/monolog-bridge'), 'Symfony\\Bridge\\Doctrine\\' => array($vendorDir . '/symfony/doctrine-bridge'), 'SwfTools' => array($vendorDir . '/swftools/swftools/src'), + 'Sunra\\PhpSimple\\HtmlDomParser' => array($vendorDir . '/sunra/php-simple-html-dom-parser/Src'), 'Silex\\Provider\\' => array($vendorDir . '/silex/web-profiler'), 'SilexOpauth' => array($vendorDir . '/icehero/silex-opauth/src'), 'SilexAssetic' => array($vendorDir . '/mheap/silex-assetic/src'), diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 0ab3fdf783..64e92feb92 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581 +class ComposerAutoloaderInitda8df92eadc454c2aaf367aac7d7b7ce { private static $loader; @@ -19,9 +19,9 @@ class ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitda8df92eadc454c2aaf367aac7d7b7ce', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitda8df92eadc454c2aaf367aac7d7b7ce', 'loadClassLoader')); $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); @@ -45,14 +45,14 @@ class ComposerAutoloaderInit68de3dbf4a2a3e667c00f4d8391e6581 $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $file) { - composerRequire68de3dbf4a2a3e667c00f4d8391e6581($file); + composerRequireda8df92eadc454c2aaf367aac7d7b7ce($file); } return $loader; } } -function composerRequire68de3dbf4a2a3e667c00f4d8391e6581($file) +function composerRequireda8df92eadc454c2aaf367aac7d7b7ce($file) { require $file; } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 77d07774a5..0443c2d399 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -5746,5 +5746,50 @@ "assetic", "silex" ] + }, + { + "name": "sunra/php-simple-html-dom-parser", + "version": "v1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sunra/php-simple-html-dom-parser.git", + "reference": "a0b80ace086c7e09085669205e1b3c2c9c7a453c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sunra/php-simple-html-dom-parser/zipball/a0b80ace086c7e09085669205e1b3c2c9c7a453c", + "reference": "a0b80ace086c7e09085669205e1b3c2c9c7a453c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-05-04 14:32:03", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Sunra\\PhpSimple\\HtmlDomParser": "Src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sunra", + "email": "sunra@yandex.ru", + "homepage": "https://github.com/sunra" + } + ], + "description": "Composer adaptation of: A HTML DOM parser written in PHP5+ let you manipulate HTML in a very easy way! Require PHP 5+. Supports invalid HTML. Find tags on an HTML page with selectors just like jQuery. Extract contents from HTML in a single line.", + "homepage": "https://github.com/sunra/php-simple-html-dom-parser", + "keywords": [ + "dom", + "html", + "parser" + ] } ] diff --git a/vendor/sunra/php-simple-html-dom-parser/README.md b/vendor/sunra/php-simple-html-dom-parser/README.md new file mode 100644 index 0000000000..4bb1f8f829 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/README.md @@ -0,0 +1,38 @@ +php-simple-html-dom-parser +========================== + +Version 1.5 + +Adaptation for Composer and PSR-0 of: + +A HTML DOM parser written in PHP5+ let you manipulate HTML in a very easy way! +Require PHP 5+. +Supports invalid HTML. +Find tags on an HTML page with selectors just like jQuery. +Extract contents from HTML in a single line. + +http://simplehtmldom.sourceforge.net/ + + +Install +------- + + composer.phar require + - package name: "sunra/php-simple-html-dom-parser": "dev-master" + + +Usage +----- + +```php +use Sunra\PhpSimple\HtmlDomParser; + +... +$dom = HtmlDomParser::str_get_html( $str ); +or +$dom = HtmlDomParser::file_get_html( $file_name ); + +$elems = $dom->find($elem_name); +... + +``` diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/HtmlDomParser.php b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/HtmlDomParser.php new file mode 100644 index 0000000000..b210580dcf --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/HtmlDomParser.php @@ -0,0 +1,20 @@ + + + + + FootballScoresLive - Previous Results + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + FootballScoresLive logo + + + + + +
    + + For FREE goals to your mobile + Click Here + + + + + +
    + + + +   +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Add this page to your favourites + +
    Season Ticket Goal Alerts - coming soon
    Mobile Goal Alerts
    + English Leagues
    English National Team - coming soon
    + + Scottish Leagues
    + + Spanish Leagues
    + + Italian Leagues
    Live Scores
    Football Supermarket - coming soon
    Previous Results
    Future Fixtures
    League Tables - coming soon
    Sponsored Links - coming soon
     
    Personalise
    + +
    + +
    +
    + + + +
    + + + + + + + + + +
    +
    + + + + +
    + +
    +
    +
    + + + + + + + +
    + + English Leagues goal alerts by SMS + + Scottish Leagues goal alerts by SMS + + Spanish Leagues goal alerts by SMS + + Italian Leagues goal alerts by SMS
    +
    +
    +
    +
    +
    +
    + + +
    + +

    Historical Football Archive - Updated Daily!

    +
    +

    Missed the details of your football club's latest performance? + Whether you support Manchester United or Cambridge United, Premiership club or Conference club, + you will find it all here, with our fantastic soccer library of results over the last two weeks. + These pages contain the history & detailed results package for every football game from around the world + from the last 14 match days, including not only every goal scored, but, game results, names of goal scorer, + match results, red cards and plenty more! This service is automatically updated every 24 hours, + simply select a date from the drop down menu :- +

    + + +

    + + + + + + + + + +
    +  SCOTLAND - DIVISION 2
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 19:45 + Pst - +   + +   + +   + STRANRAER00PETERHEAD +   + +   + +   + +   + +   +
    +
    + +
    + +
    + + + + + + + + + + +
    +  ENGLAND - FA TROPHY - FIRST ROUND
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 19:45 + FT - +   + +   + +   + SWINDON SUPERMARINE10EASTBOURNE BOROUGH +   + +   + +   + + + + 74goalEDENBOROUGH +
    +
    + +
    + +
    + + + + + + + + + + +
    +  EUROPE (UEFA) - UEFA CUP - GROUP STAGE
    +
    + + + + + + + + + + + + + + + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 19:45 + FT - +   + +   + +   + AC MILAN22VfL WOLFSBURG +   + +   + +   + +   + + 81goalSAGLIK +
    + 19:45 + FT - +   + + 1 + +   + AJAX22SLAVIA PRAHA +   + +   + +   + +   + + 90penaltySUAREZ +
    + 19:45 + FT - +   + +   + +   + CLUB BRUGGE01FC COPENHAGEN +   + +   + +   + +   + + 58goalSANTIN +
    + 19:45 + FT - +   + +   + +   + DEP. LA CORUNA10NANCY +   + +   + +   + +   + + 74goalBODIPO +
    + 19:45 + FT - +   + +   + +   + FEYENOORD01LECH POZNAN +   + +   + +   + +   + + 26goalDJURDJEVIC +
    + 19:45 + FT - +   + +   + +   + HAMBURGER SV31ASTON VILLA + 1 + +   + +   + +   + + 84red cardSIDWELL +
    + 19:45 + FT - +   + +   + +   + PORTSMOUTH30HEERENVEEN +   + +   + +   + +   + + 90goalHREIDARSSON +
    + 19:45 + FT - +   + +   + +   + ST ETIENNE22VALENCIA +   + +   + +   + +   + + 72goalZIGIC +
    +
    + +
    + +
    + + + + + + + + + + +
    +  INTERNATIONAL TOURNAMENTS - OMAN FOUR NATIONS TOUR
    +
    + + + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 14:00 + FT - +   + +   + +   + OMAN31CHINA +   + +   + +   + + + + 84goalSALEH +
    + 16:30 + FT - +   + +   + +   + ECUADOR10IRAN + 1 + +   + +   + + + + 90red cardNEJAD +
    +
    + +
    + +
    + + + + + + + + + + +
    +  ITALY - FA CUP - ROUND 16
    +
    + + + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 16:00 + FT - +   + +   + +   + FIORENTINA01TORINO +   + +   + +   + + + + 19goalBIANCHI +
    + 20:00 + FT - +   + +   + +   + ROMA20BOLOGNA +   + +   + +   + + + + 86goalVUCINIC +
    +
    + +
    + +
    + + + + + + + + + + +
    +  ITALY - SERIE C1A
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 13:30 + FT - +   + +   + +   + PRO SESTO00LECCO +   + +   + +   + +   + +   +
    +
    + +
    + +
    + + + + + + + + + + +
    +  ITALY - SERIE C2A
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 13:30 + FT - +   + +   + +   + MEZZOCORONA13SAMBONIFACESE +   + +   + +   + +   + + 74goal  +
    +
    + +
    + +
    + + + + + + + + + + +
    +  SCOTLAND - FA CUP - THIRD ROUND
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 13:30 + FT - +   + +   + +   + LOCHEE UTD11AYR UTD +   + +   + +   + + + + 86goalHAGAN +
    +
    + +
    + +
    + + + + + + + + + + +
    +  WORLD (FIFA) - FIFA CLUB WORLD CHAMPIONSHIP - SEMI
    +
    + + + + + + +
    StartStatMinRPOHome AwayRPOLatest
    + 10:30 + FT - +   + +   + +   + PACHUCA (MEX)02LIGA D.U. QUITO(ECU) +   + +   + +   + + + + 26goalBOLANOS +
    +
    + +
    + +
    + + + + +
    + + + + +
    + View complete archive of FootballScoresLive.com Match Results +
    + +
    + + + + + + + +
    + + + +
    +  |  + Disclaimer |  + Privacy |  + About Us |  + News Archive |  +
    + +
    + + + + + +
    + + \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/index.php b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/index.php new file mode 100644 index 0000000000..189aa5a156 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/index.php @@ -0,0 +1,144 @@ +find('html', 0); +if ($l!==null) + $lang = $l->lang; +if ($lang!='') + $lang = 'lang="'.$lang.'"'; + +$charset = $html->find('meta[http-equiv*=content-type]', 0); +$target = array(); +$query = ''; + +if (isset($_REQUEST['query'])) { + $query = $_REQUEST['query']; + $target = $html->find($query); +} + +function stat_dom($dom) { + $count_text = 0; + $count_comm = 0; + $count_elem = 0; + $count_tag_end = 0; + $count_unknown = 0; + + foreach($dom->nodes as $n) { + if ($n->nodetype==HDOM_TYPE_TEXT) + ++$count_text; + if ($n->nodetype==HDOM_TYPE_COMMENT) + ++$count_comm; + if ($n->nodetype==HDOM_TYPE_ELEMENT) + ++$count_elem; + if ($n->nodetype==HDOM_TYPE_ENDTAG) + ++$count_tag_end; + if ($n->nodetype==HDOM_TYPE_UNKNOWN) + ++$count_unknown; + } + + echo 'Total: '. count($dom->nodes). + ', Text: '.$count_text. + ', Commnet: '.$count_comm. + ', Tag: '.$count_elem. + ', End Tag: '.$count_tag_end. + ', Unknown: '.$count_unknown; +} + +function dump_my_html_tree($node, $show_attr=true, $deep=0, $last=true) { + $count = count($node->nodes); + if ($count>0) { + if($last) + echo '\n"; +} +?> + + +> + + '; + else if ($charset) + echo $charset; + else + echo ''; + ?> + Simple HTML DOM Query Test + + + + + + + + +
    +

    Simple HTML DOM Test

    +
    + find: + +
    +
    + HTML STAT ()
    +
    +
    +
      + +
    +
    + + \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default-line.gif b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default-line.gif new file mode 100644 index 0000000000..37114d3068 Binary files /dev/null and b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default-line.gif differ diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default.gif b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default.gif new file mode 100644 index 0000000000..a12ac52ffe Binary files /dev/null and b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/images/treeview-default.gif differ diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/jquery.js b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/jquery.js new file mode 100644 index 0000000000..b660baab4a --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/app/js/jquery.js @@ -0,0 +1,3363 @@ +(function(){ +/* + * jQuery 1.2.2b2 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-12-20 14:36:56 +0100 (Don, 20 Dez 2007) $ + * $Rev: 4251 $ + */ + +// Map over jQuery in case of overwrite +if ( window.jQuery ) + var _jQuery = window.jQuery; + +var jQuery = window.jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.prototype.init( selector, context ); +}; + +// Map over the $ in case of overwrite +if ( window.$ ) + var _$ = window.$; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +// A simple way to check for HTML strings or ID strings +// (both of which we optimize for) +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +// Is it a simple selector +var isSimple = /^.[^:#\[\.]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + return this; + + // Handle HTML strings + } else if ( typeof selector == "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Make sure an element was located + if ( elem ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + else { + this[0] = elem; + this.length = 1; + return this; + } + + else + selector = []; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return new jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray( + // HANDLE: $(array) + selector.constructor == Array && selector || + + // HANDLE: $(arraylike) + // Watch for when an array-like object, contains DOM nodes, is passed in as the selector + (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || + + // HANDLE: $(*) + [ selector ] ); + }, + + // The current version of jQuery being used + jquery: "@VERSION", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // The number of elements contained in the matched element set + length: 0, + + // 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 == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + 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( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // 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 ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + var ret = -1; + + // Locate the position of the desired element + this.each(function(i){ + if ( this == elem ) + ret = i; + }); + + return ret; + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( name.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text != "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) + // The elements to wrap the target around + jQuery( html, this[0].ownerDocument ) + .clone() + .insertBefore( this[0] ) + .map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, false, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, true, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + find: function( selector ) { + var elems = jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + }); + + return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? + jQuery.unique( elems ) : + elems ); + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var clone = this.cloneNode(true), + container = document.createElement("div"), + container2 = document.createElement("div"); + container.appendChild(clone); + container2.innerHTML = container.innerHTML; + return container2.firstChild; + } else + return this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if ( events === true ) + this.find("*").andSelf().each(function(i){ + var events = jQuery.data( this, "events" ); + + for ( var type in events ) + for ( var handler in events[ type ] ) + jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); + }); + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, this ) ); + }, + + not: function( selector ) { + if ( selector.constructor == String ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ) ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return !selector ? this : this.pushStack( jQuery.merge( + this.get(), + selector.constructor == String ? + jQuery( selector ).get() : + selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? + selector : [selector] ) ); + }, + + is: function( selector ) { + return selector ? + jQuery.multiFilter( selector, this ).length > 0 : + false; + }, + + hasClass: function( selector ) { + return this.is( "." + selector ); + }, + + val: function( value ) { + if ( value == undefined ) { + + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var 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 + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + + // Everything else, we just grab the value + } else + return (this[0].value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = value.constructor == Array ? + value : + [ value ]; + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value == undefined ? + (this.length ? + this[0].innerHTML : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, reverse, callback ) { + var clone = this.length > 1, elems; + + return this.each(function(){ + if ( !elems ) { + elems = jQuery.clean( args, this.ownerDocument ); + + if ( reverse ) + elems.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); + + var scripts = jQuery( [] ); + + jQuery.each(elems, function(){ + var elem = clone ? + jQuery( this ).clone( true )[0] : + this; + + // execute all scripts after the elements have been injected + if ( jQuery.nodeName( elem, "script" ) ) { + scripts = scripts.add( elem ); + } else { + // Remove any inner scripts for later evaluation + if ( elem.nodeType == 1 ) + scripts = scripts.add( jQuery( "script", elem ).remove() ); + + // Inject the elements into the document + callback.call( obj, elem ); + } + }); + + scripts.each( evalScript ); + }); + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.prototype.init.prototype = jQuery.prototype; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( target.constructor == 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" && typeof target != "function" ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == 1 ) { + target = this; + i = 0; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + // Prevent never-ending loop + if ( target === options[ name ] ) + continue; + + // Recurse if we're merging object values + if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) + target[ name ] = jQuery.extend( target[ name ], options[ name ] ); + + // Don't bring in undefined values + else if ( options[ name ] != undefined ) + target[ name ] = options[ name ]; + + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // This may seem like some crazy code, but trust me when I say that this + // is the only cross-browser way to do this. --John + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + data = jQuery.trim( data ); + + if ( data ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.browser.msie ) + script.text = data; + else + script.appendChild( document.createTextNode( data ) ); + + head.appendChild( script ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data != undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? + jQuery.cache[ id ][ name ] : + id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + + for ( name in jQuery.cache[ id ] ) + break; + + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( object, callback, args ) { + if ( args ) { + if ( object.length == undefined ) + for ( var name in object ) + callback.apply( object[ name ], args ); + else + for ( var i = 0, length = object.length; i < length; i++ ) + if ( callback.apply( object[ i ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( object.length == undefined ) + for ( var name in object ) + callback.call( object[ name ], name, object[ name ] ); + else + for ( var i = 0, length = object.length, value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames != undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use is(".class") + has: function( elem, className ) { + return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + var padding = 0, border = 0; + jQuery.each( which, function() { + padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + val -= Math.round(padding + border); + } + + if ( jQuery(elem).is(":visible") ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, val); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret; + + // A helper method for determining if an element's values are broken + function color( elem ) { + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle( elem, null ); + return !ret || ret.getPropertyValue("color") == ""; + } + + // We need to handle opacity special in IE + if ( name == "opacity" && jQuery.browser.msie ) { + ret = jQuery.attr( elem.style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + // Opera sometimes will give the wrong display answer, this fixes it, see #2037 + if ( jQuery.browser.opera && name == "display" ) { + var save = elem.style.display; + elem.style.display = "block"; + elem.style.display = save; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && elem.style[ name ] ) + ret = elem.style[ name ]; + + else if ( document.defaultView && document.defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); + + if ( getComputedStyle && !color( elem ) ) + ret = getComputedStyle.getPropertyValue( name ); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + var swap = [], stack = []; + + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( var i = 0; i < stack.length; i++ ) + if ( color( stack[ i ] ) ) { + swap[ i ] = stack[ i ].style.display; + stack[ i ].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = name == "display" && swap[ stack.length - 1 ] != null ? + "none" : + ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; + + // Finally, revert the display styles back + for ( var i = 0; i < swap.length; i++ ) + if ( swap[ i ] != null ) + stack[ i ].style.display = swap[ i ]; + } + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // 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 + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = ret || 0; + ret = elem.style.pixelLeft + "px"; + + // Revert the changed values + elem.style.left = style; + elem.runtimeStyle.left = runtimeStyle; + } + } + + return ret; + }, + + clean: function( elems, context ) { + var ret = []; + context = context || document; + // !context.createElement fails in IE with an error but returns typeof 'object' + if (typeof context.createElement == 'undefined') + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + jQuery.each(elems, function(i, elem){ + if ( !elem ) + return; + + if ( elem.constructor == Number ) + elem = elem.toString(); + + // Convert html string into DOM nodes + if ( typeof elem == "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
    " ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and + + + + +

    PHP Simple HTML DOM Parser Manual

    +
    +

    Index

    + + + +

    Quick Start

    + Top +
    + +
    +
    + // Create DOM from URL or file
    + $html = file_get_html('http://www.google.com/');
    +
    + // Find all images
    + foreach($html->find('img') as $element)
    +       echo $element->src . '<br>';
    +
    +// Find all links
    +foreach($html->find('a') as $element)
    +       echo $element->href . '<br>';
    +
    +
    +
    + // Create DOM from string
    + $html = str_get_html('<div id="hello">Hello</div><div id="world">World</div>');
    +
    +
    + +$html->find('div', 1)->class = 'bar';
    +
    +$html->find('div[id=hello]', 0)->innertext = 'foo';
    +
    + echo $html; // Output: <div id="hello">foo</div><div id="world" class="bar">World</div>
    +
    +
    +

    + // Dump contents (without tags) from HTML
    + echo file_get_html('http://www.google.com/')->plaintext; +
    +
    +
    +
    +
    +
    + // Create DOM from URL
    + $html = file_get_html('http://slashdot.org/');
    +
    + // Find all article blocks
    + foreach($html->find('div.article') as $article) {
    +    $item['title']     = $article->find('div.title', 0)->plaintext;
    +    $item['intro']    = $article->find('div.intro', 0)->plaintext;
    +    $item['details'] = $article->find('div.details', 0)->plaintext;
    +    $articles[] = $item;
    + }
    +
    + print_r($articles); +
    +
    +
    + + +

    How to create HTML DOM object?

    + Top +
    + +
    +
    // Create a DOM object from a string
    + $html = str_get_html('<html><body>Hello!</body></html>');
    +
    + // Create a DOM object from a URL
    + $html = file_get_html('http://www.google.com/');
    +
    + // Create a DOM object from a HTML file
    + $html = file_get_html('test.htm');
    +
    +
    +
    +
    // Create a DOM object
    + $html = new simple_html_dom();
    +
    + // Load HTML from a string
    + $html->load('<html><body>Hello!</body></html>');
    +
    + // Load HTML from a URL
    + $html->load_file('http://www.google.com/');
    +
    + // Load HTML from a HTML file
    + $html->load_file('test.htm');
    +
    +
    + +

    How to find HTML elements?

    + Top +
    + +
    +
    // Find all anchors, returns a array of element objects
    + $ret = $html->find('a');
    +
    + // Find (N)th anchor, returns element object or null if not found (zero based)
    + $ret = $html->find('a', 0);
    +
    + // Find lastest anchor, returns element object or null if not found (zero based)
    +$ret = $html->find('a', -1);
    +
    + // Find all <div> with the id attribute
    +$ret = $html->find('div[id]');
    +
    + // Find all <div> which attribute id=foo
    +$ret = $html->find('div[id=foo]');
    +
    +
    +
    +
    // Find all element which id=foo
    + $ret = $html->find('#foo');
    +
    + // Find all element which class=foo
    + $ret = $html->find('.foo');
    +
    + // Find all element has attribute id
    +$ret = $html->find('*[id]');
    +
    + // Find all anchors and images
    +$ret = $html->find('a, img');
    +
    + // Find all anchors and images with the "title" attribute
    + $ret = $html->find('a[title], img[title]');
    +
    +
    +
    +
    + Supports these operators in attribute selectors:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FilterDescription
    [attribute]Matches elements that have the specified attribute.
    [!attribute]Matches elements that don't have the specified attribute.
    [attribute=value]Matches elements that have the specified attribute with a certain value.
    [attribute!=value]Matches elements that don't have the specified attribute with a certain value.
    [attribute^=value]Matches elements that have the specified attribute and it starts with a certain value.
    [attribute$=value]Matches elements that have the specified attribute and it ends with a certain value.
    [attribute*=value]Matches elements that have the specified attribute and it contains a certain value.
    +
    +
    +
    +
    // Find all <li> in <ul>
    + $es = $html->find('ul li');
    +
    + // Find Nested <div> tags
    + $es = $html->find('div div div');
    +
    + // Find all <td> in <table> which class=hello
    + $es = $html->find('table.hello td');
    +
    + // Find all td tags with attribite align=center in table tags
    + $es = $html->find(''table td[align=center]');
    +
    +
    +
    +
    // Find all text blocks
    + $es = $html->find('text');
    +
    + // Find all comment (<!--...-->) blocks
    + $es = $html->find('comment');
    +
    +
    +
    +
    // Find all <li> in <ul>
    + foreach($html->find('ul') as $ul)
    + {
    +       foreach($ul->find('li') as $li)
    +       {
    +             // do something...
    +       }
    + }
    +
    + // Find first <li> in first <ul>
    + $e = $html->find('ul', 0)->find('li', 0);
    +
    +
    +
    + +

    How to access the HTML element's attributes?

    + Top +
    + +
    +
    + // Get a attribute ( If the attribute is non-value attribute (eg. checked, selected...), it will returns true or false)
    + $value = $e->href;
    +
    + // Set a attribute(If the attribute is non-value attribute (eg. checked, selected...), set it's value as true or false)
    + $e->href = 'my link';
    +
    + // Remove a attribute, set it's value as null!
    + $e->href = null;
    +
    + // Determine whether a attribute exist?
    +if(isset($e->href))
    +        echo 'href exist!';
    +
    +
    +
    +
    // Example
    + $html = str_get_html("<div>foo <b>bar</b></div>");
    + $e = $html->find("div", 0);
    +
    + echo $e->tag; // Returns: " div"
    + echo $e->outertext; // Returns: " <div>foo <b>bar</b></div>"
    + echo $e->innertext; // Returns: " foo <b>bar</b>"
    + echo $e->plaintext; // Returns: " foo bar"
    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    Attribute NameUsage
    $e->tagRead or write the tag name of element.
    $e->outertextRead or write the outer HTML text of element.
    $e->innertextRead or write the inner HTML text of element.
    $e->plaintextRead or write the plain text of element.
    +
    +
    +
    +
    // Extract contents from HTML
    +echo $html->plaintext;
    +
    + + // Wrap a element
    + $e->outertext = '<div class="wrap">' . $e->outertext . '<div>';
    +
    + // Remove a element, set it's outertext as an empty string
    + $e->outertext = '';
    +
    + // Append a element
    +$e->outertext = $e->outertext . '<div>foo<div>';
    +
    +// Insert a element
    +$e->outertext = '<div>foo<div>' . $e->outertext;
    +
    +
    +
    + +

    How to traverse the DOM tree?

    + Top +
    + +
    +
    // If you are not so familiar with HTML DOM, check this link to learn more...
    +
    + // Example
    + echo $html->find("#div1", 0)->children(1)->children(1)->children(2)->id;
    + // or
    + echo $html->getElementById("div1")->childNodes(1)->childNodes(1)->childNodes(2)->getAttribute('id');
    +
    +
    +
    You can also call methods with Camel naming convertions.
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Method Description
    +
    mixed
    $e->children ( [int $index] )
    Returns the Nth child object if index is set, otherwise return an array of children.
    +
    element
    $e->parent ()
    Returns the parent of element.
    +
    element
    $e->first_child ()
    Returns the first child of element, or null if not found.
    +
    element
    $e->last_child ()
    Returns the last child of element, or null if not found.
    +
    element
    $e->next_sibling ()
    Returns the next sibling of element, or null if not found.
    +
    element
    $e->prev_sibling ()
    Returns the previous sibling of element, or null if not found.
    +
    +
    + +
    + +

    How to dump contents of DOM object?

    + Top +
    + +
    +
    // Dumps the internal DOM tree back into string
    + $str = $html->save();
    +
    + // Dumps the internal DOM tree back into a file
    + $html->save('result.htm');
    +
    +
    +
    // Dumps the internal DOM tree back into string
    + $str = $html;
    +
    + // Print it!
    + echo $html;
    +
    +
    +
    + +

    How to customize the parsing behavior?

    + Top +
    + +
    +
    // Write a function with parameter "$element"
    + function my_callback($element) {
    +         // Hide all <b> tags
    +        if ($element->tag=='b')
    +                 $element->outertext = '';
    + }
    +
    + // Register the callback function with it's function name
    + $html->set_callback('my_callback');
    +
    + // Callback function will be invoked while dumping
    + echo $html; +
    +
    +
    + +

    + Author: S.C. Chen (me578022@gmail.com)
    +Original idea is from Jose Solorzano's HTML Parser for PHP 4.
    +Contributions by: Contributions by: Yousuke Kumakura, Vadim Voituk, Antcs
    +
    +
    + + + \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_api.htm b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_api.htm new file mode 100644 index 0000000000..365870f4b4 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_api.htm @@ -0,0 +1,320 @@ + + + + +PHP Simple HTML DOM Parser: API Reference + + + +

    PHP Simple HTML DOM Parser Manual

    +
    +

    Index

    + + +

    API Reference

    + Top +
    Helper functions + + + + + + + + + + + + + +
    NameDescription
    object str_get_html ( string $content )Creates a DOM object from a string.
    object file_get_html ( string $filename )Creates a DOM object from a file or a URL.
    +
    + DOM methods & properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name Description
    +
    void
    + __construct ( [string $filename] )
    Constructor, set the filename parameter will automatically load the contents, either text or file/url.
    +
    string
    + plaintext
    Returns the contents extracted from HTML.
    +
    void
    + clear ()
    Clean up memory.
    +
    void
    + load ( string $content )
    Load contents from a string.
    +
    string
    + save ( [string $filename] )
    Dumps the internal DOM tree back into a string. If the $filename is set, result string will save to file.
    +
    void
    + load_file ( string $filename )
    Load contents from a from a file or a URL.
    +
    void
    + set_callback ( string $function_name )
    Set a callback function.
    +
    mixed
    + find ( string $selector [, int $index] )
    Find elements by the CSS selector. Returns the Nth element object if index is set, otherwise return an array of object.
    +
    + Element methods & properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescription
    +
    string
    + [attribute]
    Read or write element's attribure value.
    +
    string
    + tag
    Read or write the tag name of element.
    +
    string
    + outertext
    Read or write the outer HTML text of element.
    +
    string
    + innertext
    Read or write the inner HTML text of element.
    +
    string
    + plaintext
    Read or write the plain text of element.
    +
    mixed
    + find ( string $selector [, int $index] )
    Find children by the CSS selector. Returns the Nth element object if index is set, otherwise, return an array of object.
    +
    + DOM
    traversing
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescription
    +
    mixed
    + $e->children ( [int $index] )
    Returns the Nth child object if index is set, otherwise return an array of children.
    +
    element
    + $e->parent ()
    Returns the parent of element.
    +
    element
    + $e->first_child ()
    Returns the first child of element, or null if not found.
    +
    element
    + $e->last_child ()
    Returns the last child of element, or null if not found.
    +
    element
    + $e->next_sibling ()
    Returns the next sibling of element, or null if not found.
    +
    element
    + $e->prev_sibling ()
    Returns the previous sibling of element, or null if not found.
    +
    + +

    Camel naming convertions

    + Top +
    You can also call methods with W3C STANDARD camel naming convertions.
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MethodMapping
    +
    array
    + $e->getAllAttributes ()
    +
    array
    + $e->attr
    +
    string
    + $e->getAttribute ( $name )
    +
    string
    + $e->attribute
    +
    void
    + $e->setAttribute ( $name, $value )
    +
    void
    + $value = $e->attribute
    +
    bool
    + $e->hasAttribute ( $name )
    +
    bool
    + isset($e->attribute)
    +
    void
    + $e->removeAttribute ( $name )
    +
    void
    + $e->attribute = null
    +
    element
    + $e->getElementById ( $id )
    +
    mixed
    + $e->find ( "#$id", 0 )
    +
    mixed
    + $e->getElementsById ( $id [,$index] )
    +
    mixed
    + $e->find ( "#$id" [, int $index] )
    +
    element
    + $e->getElementByTagName ($name )
    +
    mixed
    + $e->find ( $name, 0 )
    +
    mixed
    + $e->getElementsByTagName ( $name [, $index] )
    +
    mixed
    + $e->find ( $name [, int $index] )
    +
    element
    + $e->parentNode ()
    +
    element
    + $e->parent ()
    +
    mixed
    + $e->childNodes ( [$index] )
    +
    mixed
    + $e->children ( [int $index] )
    +
    element
    + $e->firstChild ()
    +
    element
    + $e->first_child ()
    +
    element
    + $e->lastChild ()
    +
    element
    + $e->last_child ()
    +
    element
    + $e->nextSibling ()
    +
    element
    + $e->next_sibling ()
    +
    element
    + $e->previousSibling ()
    +
    element
    + $e->prev_sibling ()
    +
    +

    + Author: S.C. Chen (me578022@gmail.com)
    +Original idea is from Jose Solorzano's HTML Parser for PHP 4.
    +Contributions by: Contributions by: Yousuke Kumakura, Vadim Voituk, Antcs
    +
    +
    + + + \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_faq.htm b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_faq.htm new file mode 100644 index 0000000000..3763eaa78c --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/manual/manual_faq.htm @@ -0,0 +1,91 @@ + + + + +PHP Simple HTML DOM Parser: FAQ + + + +

    PHP Simple HTML DOM Parser Manual

    +
    +

    FAQ

    + +
    + +

    Problem with finding

    + Top +
    Q: Element not found in such case:
    + $html->find('div[style=padding: 0px 2px;] span[class=rf]');
    +
    + A: If there is blank in selectors, quote it!  
    + $html->find('div[style="padding: 0px 2px;"] span[class=rf]');
    + +

    Problem with hosting

    + Top +
    Q: On my local server everything works fine, but when I put it on my esternal server it doesn't work. 
    +
    + A: The "file_get_dom" function is a wrapper of "file_get_contents" function,  you must set "allow_url_fopen" as TRUE in "php.ini" to allow accessing files via HTTP or FTP. However, some hosting venders disabled PHP's "allow_url_fopen" flag for security issues... PHP provides excellent support for "curl" library to do the same job, Use curl to get the page, then call "str_get_dom" to create DOM object. 
    +
    + Example: 
    +  
    + $curl = curl_init(); 
    + curl_setopt($curl, CURLOPT_URL, 'http://????????');  
    + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);  
    + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);  
    + $str = curl_exec($curl);  
    + curl_close($curl);  
    +  
    + $html= str_get_html($str); 
    + ... 
    + +
    +

    Behind a proxy

    + Top +
    Q: My server is behind a Proxy and i can't use file_get_contents b/c it returns a unauthorized error.
    +
    + A: Thanks for Shaggy to provide the solution: 
    +  
    + // Define a context for HTTP. 
    + $context = array
    + ( 
    +       'http' => array
    +       ( 
    +              'proxy' => 'addresseproxy:portproxy', // This needs to be the server and the port of the NTLM Authentication Proxy Server. 
    +              'request_fulluri' => true, 
    +       ), 
    + ); 
    +
    + $context = stream_context_create($context); 
    +  
    + $html= file_get_html('http://www.php.net', false, $context); 
    + ...
    +
    +
    + +

    Memory leak!

    + Top +
    Q: This script is leaking memory seriously... After it finished running, it's not cleaning up dom object properly from memory.. 
    +
    + A: Due to php5 circular references memory leak, after creating DOM object, you must call $dom->clear() to free memory if call file_get_dom() more then once. 
    +
    + Example: 
    +
    + $html = file_get_html(...); 
    + // do something... 
    + $html->clear(); 
    + unset($html);
    +
    + Author: S.C. Chen (me578022@gmail.com)
    +Original idea is from Jose Solorzano's HTML Parser for PHP 4.
    +Contributions by: Yousuke Kumakura, Vadim Voituk, Antcs
    +
    +
    + + + diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/simple_html_dom.php b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/simple_html_dom.php new file mode 100644 index 0000000000..9a7b2f6689 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/simple_html_dom.php @@ -0,0 +1,1721 @@ +size is the "real" number of bytes the dom was created from. + * but for most purposes, it's a really good estimation. + * Paperg - Added the forceTagsClosed to the dom constructor. Forcing tags closed is great for malformed html, but it CAN lead to parsing errors. + * Allow the user to tell us how much they trust the html. + * Paperg add the text and plaintext to the selectors for the find syntax. plaintext implies text in the innertext of a node. text implies that the tag is a text node. + * This allows for us to find tags based on the text they contain. + * Create find_ancestor_tag to see if a tag is - at any level - inside of another specific tag. + * Paperg: added parse_charset so that we know about the character set of the source document. + * NOTE: If the user's system has a routine called get_last_retrieve_url_contents_content_type availalbe, we will assume it's returning the content-type header from the + * last transfer or curl_exec, and we will parse that and use it in preference to any other method of charset detection. + * + * Found infinite loop in the case of broken html in restore_noise. Rewrote to protect from that. + * PaperG (John Schlick) Added get_display_size for "IMG" tags. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author S.C. Chen + * @author John Schlick + * @author Rus Carroll + * @version 1.5 ($Rev: 196 $) + * @package PlaceLocalInclude + * @subpackage simple_html_dom + */ + +/** + * All of the Defines for the classes below. + * @author S.C. Chen + */ +define('HDOM_TYPE_ELEMENT', 1); +define('HDOM_TYPE_COMMENT', 2); +define('HDOM_TYPE_TEXT', 3); +define('HDOM_TYPE_ENDTAG', 4); +define('HDOM_TYPE_ROOT', 5); +define('HDOM_TYPE_UNKNOWN', 6); +define('HDOM_QUOTE_DOUBLE', 0); +define('HDOM_QUOTE_SINGLE', 1); +define('HDOM_QUOTE_NO', 3); +define('HDOM_INFO_BEGIN', 0); +define('HDOM_INFO_END', 1); +define('HDOM_INFO_QUOTE', 2); +define('HDOM_INFO_SPACE', 3); +define('HDOM_INFO_TEXT', 4); +define('HDOM_INFO_INNER', 5); +define('HDOM_INFO_OUTER', 6); +define('HDOM_INFO_ENDSPACE',7); +define('DEFAULT_TARGET_CHARSET', 'UTF-8'); +define('DEFAULT_BR_TEXT', "\r\n"); +define('DEFAULT_SPAN_TEXT', " "); +define('MAX_FILE_SIZE', 600000); +// helper functions +// ----------------------------------------------------------------------------- +// get html dom from file +// $maxlen is defined in the code as PHP_STREAM_COPY_ALL which is defined as -1. +function file_get_html($url, $use_include_path = false, $context=null, $offset = -1, $maxLen=-1, $lowercase = true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) +{ + // We DO force the tags to be terminated. + $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText); + // For sourceforge users: uncomment the next line and comment the retreive_url_contents line 2 lines down if it is not already done. + $contents = file_get_contents($url, $use_include_path, $context, $offset); + // Paperg - use our own mechanism for getting the contents as we want to control the timeout. + //$contents = retrieve_url_contents($url); + if (empty($contents) || strlen($contents) > MAX_FILE_SIZE) + { + return false; + } + // The second parameter can force the selectors to all be lowercase. + $dom->load($contents, $lowercase, $stripRN); + return $dom; +} + +// get html dom from string +function str_get_html($str, $lowercase=true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) +{ + $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText); + if (empty($str) || strlen($str) > MAX_FILE_SIZE) + { + $dom->clear(); + return false; + } + $dom->load($str, $lowercase, $stripRN); + return $dom; +} + +// dump html dom tree +function dump_html_tree($node, $show_attr=true, $deep=0) +{ + $node->dump($node); +} + + +/** + * simple html dom node + * PaperG - added ability for "find" routine to lowercase the value of the selector. + * PaperG - added $tag_start to track the start position of the tag in the total byte index + * + * @package PlaceLocalInclude + */ +class simple_html_dom_node +{ + public $nodetype = HDOM_TYPE_TEXT; + public $tag = 'text'; + public $attr = array(); + public $children = array(); + public $nodes = array(); + public $parent = null; + // The "info" array - see HDOM_INFO_... for what each element contains. + public $_ = array(); + public $tag_start = 0; + private $dom = null; + + function __construct($dom) + { + $this->dom = $dom; + $dom->nodes[] = $this; + } + + function __destruct() + { + $this->clear(); + } + + function __toString() + { + return $this->outertext(); + } + + // clean up memory due to php5 circular references memory leak... + function clear() + { + $this->dom = null; + $this->nodes = null; + $this->parent = null; + $this->children = null; + } + + // dump node's tree + function dump($show_attr=true, $deep=0) + { + $lead = str_repeat(' ', $deep); + + echo $lead.$this->tag; + if ($show_attr && count($this->attr)>0) + { + echo '('; + foreach ($this->attr as $k=>$v) + echo "[$k]=>\"".$this->$k.'", '; + echo ')'; + } + echo "\n"; + + if ($this->nodes) + { + foreach ($this->nodes as $c) + { + $c->dump($show_attr, $deep+1); + } + } + } + + + // Debugging function to dump a single dom node with a bunch of information about it. + function dump_node($echo=true) + { + + $string = $this->tag; + if (count($this->attr)>0) + { + $string .= '('; + foreach ($this->attr as $k=>$v) + { + $string .= "[$k]=>\"".$this->$k.'", '; + } + $string .= ')'; + } + if (count($this->_)>0) + { + $string .= ' $_ ('; + foreach ($this->_ as $k=>$v) + { + if (is_array($v)) + { + $string .= "[$k]=>("; + foreach ($v as $k2=>$v2) + { + $string .= "[$k2]=>\"".$v2.'", '; + } + $string .= ")"; + } else { + $string .= "[$k]=>\"".$v.'", '; + } + } + $string .= ")"; + } + + if (isset($this->text)) + { + $string .= " text: (" . $this->text . ")"; + } + + $string .= " HDOM_INNER_INFO: '"; + if (isset($node->_[HDOM_INFO_INNER])) + { + $string .= $node->_[HDOM_INFO_INNER] . "'"; + } + else + { + $string .= ' NULL '; + } + + $string .= " children: " . count($this->children); + $string .= " nodes: " . count($this->nodes); + $string .= " tag_start: " . $this->tag_start; + $string .= "\n"; + + if ($echo) + { + echo $string; + return; + } + else + { + return $string; + } + } + + // returns the parent of node + // If a node is passed in, it will reset the parent of the current node to that one. + function parent($parent=null) + { + // I am SURE that this doesn't work properly. + // It fails to unset the current node from it's current parents nodes or children list first. + if ($parent !== null) + { + $this->parent = $parent; + $this->parent->nodes[] = $this; + $this->parent->children[] = $this; + } + + return $this->parent; + } + + // verify that node has children + function has_child() + { + return !empty($this->children); + } + + // returns children of node + function children($idx=-1) + { + if ($idx===-1) + { + return $this->children; + } + if (isset($this->children[$idx])) return $this->children[$idx]; + return null; + } + + // returns the first child of node + function first_child() + { + if (count($this->children)>0) + { + return $this->children[0]; + } + return null; + } + + // returns the last child of node + function last_child() + { + if (($count=count($this->children))>0) + { + return $this->children[$count-1]; + } + return null; + } + + // returns the next sibling of node + function next_sibling() + { + if ($this->parent===null) + { + return null; + } + + $idx = 0; + $count = count($this->parent->children); + while ($idx<$count && $this!==$this->parent->children[$idx]) + { + ++$idx; + } + if (++$idx>=$count) + { + return null; + } + return $this->parent->children[$idx]; + } + + // returns the previous sibling of node + function prev_sibling() + { + if ($this->parent===null) return null; + $idx = 0; + $count = count($this->parent->children); + while ($idx<$count && $this!==$this->parent->children[$idx]) + ++$idx; + if (--$idx<0) return null; + return $this->parent->children[$idx]; + } + + // function to locate a specific ancestor tag in the path to the root. + function find_ancestor_tag($tag) + { + global $debugObject; + if (is_object($debugObject)) { $debugObject->debugLogEntry(1); } + + // Start by including ourselves in the comparison. + $returnDom = $this; + + while (!is_null($returnDom)) + { + if (is_object($debugObject)) { $debugObject->debugLog(2, "Current tag is: " . $returnDom->tag); } + + if ($returnDom->tag == $tag) + { + break; + } + $returnDom = $returnDom->parent; + } + return $returnDom; + } + + // get dom node's inner html + function innertext() + { + if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER]; + if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + $ret = ''; + foreach ($this->nodes as $n) + $ret .= $n->outertext(); + return $ret; + } + + // get dom node's outer text (with tag) + function outertext() + { + global $debugObject; + if (is_object($debugObject)) + { + $text = ''; + if ($this->tag == 'text') + { + if (!empty($this->text)) + { + $text = " with text: " . $this->text; + } + } + $debugObject->debugLog(1, 'Innertext of tag: ' . $this->tag . $text); + } + + if ($this->tag==='root') return $this->innertext(); + + // trigger callback + if ($this->dom && $this->dom->callback!==null) + { + call_user_func_array($this->dom->callback, array($this)); + } + + if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER]; + if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + // render begin tag + if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]) + { + $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup(); + } else { + $ret = ""; + } + + // render inner text + if (isset($this->_[HDOM_INFO_INNER])) + { + // If it's a br tag... don't return the HDOM_INNER_INFO that we may or may not have added. + if ($this->tag != "br") + { + $ret .= $this->_[HDOM_INFO_INNER]; + } + } else { + if ($this->nodes) + { + foreach ($this->nodes as $n) + { + $ret .= $this->convert_text($n->outertext()); + } + } + } + + // render end tag + if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END]!=0) + $ret .= 'tag.'>'; + return $ret; + } + + // get dom node's plain text + function text() + { + if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER]; + switch ($this->nodetype) + { + case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + case HDOM_TYPE_COMMENT: return ''; + case HDOM_TYPE_UNKNOWN: return ''; + } + if (strcasecmp($this->tag, 'script')===0) return ''; + if (strcasecmp($this->tag, 'style')===0) return ''; + + $ret = ''; + // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed for some span tags, and some p tags) $this->nodes is set to NULL. + // NOTE: This indicates that there is a problem where it's set to NULL without a clear happening. + // WHY is this happening? + if (!is_null($this->nodes)) + { + foreach ($this->nodes as $n) + { + $ret .= $this->convert_text($n->text()); + } + + // If this node is a span... add a space at the end of it so multiple spans don't run into each other. This is plaintext after all. + if ($this->tag == "span") + { + $ret .= $this->dom->default_span_text; + } + + + } + return $ret; + } + + function xmltext() + { + $ret = $this->innertext(); + $ret = str_ireplace('', '', $ret); + return $ret; + } + + // build node's text with tag + function makeup() + { + // text, comment, unknown + if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + $ret = '<'.$this->tag; + $i = -1; + + foreach ($this->attr as $key=>$val) + { + ++$i; + + // skip removed attribute + if ($val===null || $val===false) + continue; + + $ret .= $this->_[HDOM_INFO_SPACE][$i][0]; + //no value attr: nowrap, checked selected... + if ($val===true) + $ret .= $key; + else { + switch ($this->_[HDOM_INFO_QUOTE][$i]) + { + case HDOM_QUOTE_DOUBLE: $quote = '"'; break; + case HDOM_QUOTE_SINGLE: $quote = '\''; break; + default: $quote = ''; + } + $ret .= $key.$this->_[HDOM_INFO_SPACE][$i][1].'='.$this->_[HDOM_INFO_SPACE][$i][2].$quote.$val.$quote; + } + } + $ret = $this->dom->restore_noise($ret); + return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>'; + } + + // find elements by css selector + //PaperG - added ability for find to lowercase the value of the selector. + function find($selector, $idx=null, $lowercase=false) + { + $selectors = $this->parse_selector($selector); + if (($count=count($selectors))===0) return array(); + $found_keys = array(); + + // find each selector + for ($c=0; $c<$count; ++$c) + { + // The change on the below line was documented on the sourceforge code tracker id 2788009 + // used to be: if (($levle=count($selectors[0]))===0) return array(); + if (($levle=count($selectors[$c]))===0) return array(); + if (!isset($this->_[HDOM_INFO_BEGIN])) return array(); + + $head = array($this->_[HDOM_INFO_BEGIN]=>1); + + // handle descendant selectors, no recursive! + for ($l=0; $l<$levle; ++$l) + { + $ret = array(); + foreach ($head as $k=>$v) + { + $n = ($k===-1) ? $this->dom->root : $this->dom->nodes[$k]; + //PaperG - Pass this optional parameter on to the seek function. + $n->seek($selectors[$c][$l], $ret, $lowercase); + } + $head = $ret; + } + + foreach ($head as $k=>$v) + { + if (!isset($found_keys[$k])) + $found_keys[$k] = 1; + } + } + + // sort keys + ksort($found_keys); + + $found = array(); + foreach ($found_keys as $k=>$v) + $found[] = $this->dom->nodes[$k]; + + // return nth-element or array + if (is_null($idx)) return $found; + else if ($idx<0) $idx = count($found) + $idx; + return (isset($found[$idx])) ? $found[$idx] : null; + } + + // seek for given conditions + // PaperG - added parameter to allow for case insensitive testing of the value of a selector. + protected function seek($selector, &$ret, $lowercase=false) + { + global $debugObject; + if (is_object($debugObject)) { $debugObject->debugLogEntry(1); } + + list($tag, $key, $val, $exp, $no_key) = $selector; + + // xpath index + if ($tag && $key && is_numeric($key)) + { + $count = 0; + foreach ($this->children as $c) + { + if ($tag==='*' || $tag===$c->tag) { + if (++$count==$key) { + $ret[$c->_[HDOM_INFO_BEGIN]] = 1; + return; + } + } + } + return; + } + + $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0; + if ($end==0) { + $parent = $this->parent; + while (!isset($parent->_[HDOM_INFO_END]) && $parent!==null) { + $end -= 1; + $parent = $parent->parent; + } + $end += $parent->_[HDOM_INFO_END]; + } + + for ($i=$this->_[HDOM_INFO_BEGIN]+1; $i<$end; ++$i) { + $node = $this->dom->nodes[$i]; + + $pass = true; + + if ($tag==='*' && !$key) { + if (in_array($node, $this->children, true)) + $ret[$i] = 1; + continue; + } + + // compare tag + if ($tag && $tag!=$node->tag && $tag!=='*') {$pass=false;} + // compare key + if ($pass && $key) { + if ($no_key) { + if (isset($node->attr[$key])) $pass=false; + } else { + if (($key != "plaintext") && !isset($node->attr[$key])) $pass=false; + } + } + // compare value + if ($pass && $key && $val && $val!=='*') { + // If they have told us that this is a "plaintext" search then we want the plaintext of the node - right? + if ($key == "plaintext") { + // $node->plaintext actually returns $node->text(); + $nodeKeyValue = $node->text(); + } else { + // this is a normal search, we want the value of that attribute of the tag. + $nodeKeyValue = $node->attr[$key]; + } + if (is_object($debugObject)) {$debugObject->debugLog(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue);} + + //PaperG - If lowercase is set, do a case insensitive test of the value of the selector. + if ($lowercase) { + $check = $this->match($exp, strtolower($val), strtolower($nodeKeyValue)); + } else { + $check = $this->match($exp, $val, $nodeKeyValue); + } + if (is_object($debugObject)) {$debugObject->debugLog(2, "after match: " . ($check ? "true" : "false"));} + + // handle multiple class + if (!$check && strcasecmp($key, 'class')===0) { + foreach (explode(' ',$node->attr[$key]) as $k) { + // Without this, there were cases where leading, trailing, or double spaces lead to our comparing blanks - bad form. + if (!empty($k)) { + if ($lowercase) { + $check = $this->match($exp, strtolower($val), strtolower($k)); + } else { + $check = $this->match($exp, $val, $k); + } + if ($check) break; + } + } + } + if (!$check) $pass = false; + } + if ($pass) $ret[$i] = 1; + unset($node); + } + // It's passed by reference so this is actually what this function returns. + if (is_object($debugObject)) {$debugObject->debugLog(1, "EXIT - ret: ", $ret);} + } + + protected function match($exp, $pattern, $value) { + global $debugObject; + if (is_object($debugObject)) {$debugObject->debugLogEntry(1);} + + switch ($exp) { + case '=': + return ($value===$pattern); + case '!=': + return ($value!==$pattern); + case '^=': + return preg_match("/^".preg_quote($pattern,'/')."/", $value); + case '$=': + return preg_match("/".preg_quote($pattern,'/')."$/", $value); + case '*=': + if ($pattern[0]=='/') { + return preg_match($pattern, $value); + } + return preg_match("/".$pattern."/i", $value); + } + return false; + } + + protected function parse_selector($selector_string) { + global $debugObject; + if (is_object($debugObject)) {$debugObject->debugLogEntry(1);} + + // pattern of CSS selectors, modified from mootools + // Paperg: Add the colon to the attrbute, so that it properly finds like google does. + // Note: if you try to look at this attribute, yo MUST use getAttribute since $dom->x:y will fail the php syntax check. +// Notice the \[ starting the attbute? and the @? following? This implies that an attribute can begin with an @ sign that is not captured. +// This implies that an html attribute specifier may start with an @ sign that is NOT captured by the expression. +// farther study is required to determine of this should be documented or removed. +// $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; + $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; + preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER); + if (is_object($debugObject)) {$debugObject->debugLog(2, "Matches Array: ", $matches);} + + $selectors = array(); + $result = array(); + //print_r($matches); + + foreach ($matches as $m) { + $m[0] = trim($m[0]); + if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue; + // for browser generated xpath + if ($m[1]==='tbody') continue; + + list($tag, $key, $val, $exp, $no_key) = array($m[1], null, null, '=', false); + if (!empty($m[2])) {$key='id'; $val=$m[2];} + if (!empty($m[3])) {$key='class'; $val=$m[3];} + if (!empty($m[4])) {$key=$m[4];} + if (!empty($m[5])) {$exp=$m[5];} + if (!empty($m[6])) {$val=$m[6];} + + // convert to lowercase + if ($this->dom->lowercase) {$tag=strtolower($tag); $key=strtolower($key);} + //elements that do NOT have the specified attribute + if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;} + + $result[] = array($tag, $key, $val, $exp, $no_key); + if (trim($m[7])===',') { + $selectors[] = $result; + $result = array(); + } + } + if (count($result)>0) + $selectors[] = $result; + return $selectors; + } + + function __get($name) { + if (isset($this->attr[$name])) + { + return $this->convert_text($this->attr[$name]); + } + switch ($name) { + case 'outertext': return $this->outertext(); + case 'innertext': return $this->innertext(); + case 'plaintext': return $this->text(); + case 'xmltext': return $this->xmltext(); + default: return array_key_exists($name, $this->attr); + } + } + + function __set($name, $value) { + switch ($name) { + case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value; + case 'innertext': + if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value; + return $this->_[HDOM_INFO_INNER] = $value; + } + if (!isset($this->attr[$name])) { + $this->_[HDOM_INFO_SPACE][] = array(' ', '', ''); + $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; + } + $this->attr[$name] = $value; + } + + function __isset($name) { + switch ($name) { + case 'outertext': return true; + case 'innertext': return true; + case 'plaintext': return true; + } + //no value attr: nowrap, checked selected... + return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]); + } + + function __unset($name) { + if (isset($this->attr[$name])) + unset($this->attr[$name]); + } + + // PaperG - Function to convert the text from one character set to another if the two sets are not the same. + function convert_text($text) + { + global $debugObject; + if (is_object($debugObject)) {$debugObject->debugLogEntry(1);} + + $converted_text = $text; + + $sourceCharset = ""; + $targetCharset = ""; + + if ($this->dom) + { + $sourceCharset = strtoupper($this->dom->_charset); + $targetCharset = strtoupper($this->dom->_target_charset); + } + if (is_object($debugObject)) {$debugObject->debugLog(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset);} + + if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0)) + { + // Check if the reported encoding could have been incorrect and the text is actually already UTF-8 + if ((strcasecmp($targetCharset, 'UTF-8') == 0) && ($this->is_utf8($text))) + { + $converted_text = $text; + } + else + { + $converted_text = iconv($sourceCharset, $targetCharset, $text); + } + } + + // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output. + if ($targetCharset == 'UTF-8') + { + if (substr($converted_text, 0, 3) == "\xef\xbb\xbf") + { + $converted_text = substr($converted_text, 3); + } + if (substr($converted_text, -3) == "\xef\xbb\xbf") + { + $converted_text = substr($converted_text, 0, -3); + } + } + + return $converted_text; + } + + /** + * Returns true if $string is valid UTF-8 and false otherwise. + * + * @param mixed $str String to be tested + * @return boolean + */ + static function is_utf8($str) + { + $c=0; $b=0; + $bits=0; + $len=strlen($str); + for($i=0; $i<$len; $i++) + { + $c=ord($str[$i]); + if($c > 128) + { + if(($c >= 254)) return false; + elseif($c >= 252) $bits=6; + elseif($c >= 248) $bits=5; + elseif($c >= 240) $bits=4; + elseif($c >= 224) $bits=3; + elseif($c >= 192) $bits=2; + else return false; + if(($i+$bits) > $len) return false; + while($bits > 1) + { + $i++; + $b=ord($str[$i]); + if($b < 128 || $b > 191) return false; + $bits--; + } + } + } + return true; + } + /* + function is_utf8($string) + { + //this is buggy + return (utf8_encode(utf8_decode($string)) == $string); + } + */ + + /** + * Function to try a few tricks to determine the displayed size of an img on the page. + * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types. + * + * @author John Schlick + * @version April 19 2012 + * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out. + */ + function get_display_size() + { + global $debugObject; + + $width = -1; + $height = -1; + + if ($this->tag !== 'img') + { + return false; + } + + // See if there is aheight or width attribute in the tag itself. + if (isset($this->attr['width'])) + { + $width = $this->attr['width']; + } + + if (isset($this->attr['height'])) + { + $height = $this->attr['height']; + } + + // Now look for an inline style. + if (isset($this->attr['style'])) + { + // Thanks to user gnarf from stackoverflow for this regular expression. + $attributes = array(); + preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->attr['style'], $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $attributes[$match[1]] = $match[2]; + } + + // If there is a width in the style attributes: + if (isset($attributes['width']) && $width == -1) + { + // check that the last two characters are px (pixels) + if (strtolower(substr($attributes['width'], -2)) == 'px') + { + $proposed_width = substr($attributes['width'], 0, -2); + // Now make sure that it's an integer and not something stupid. + if (filter_var($proposed_width, FILTER_VALIDATE_INT)) + { + $width = $proposed_width; + } + } + } + + // If there is a width in the style attributes: + if (isset($attributes['height']) && $height == -1) + { + // check that the last two characters are px (pixels) + if (strtolower(substr($attributes['height'], -2)) == 'px') + { + $proposed_height = substr($attributes['height'], 0, -2); + // Now make sure that it's an integer and not something stupid. + if (filter_var($proposed_height, FILTER_VALIDATE_INT)) + { + $height = $proposed_height; + } + } + } + + } + + // Future enhancement: + // Look in the tag to see if there is a class or id specified that has a height or width attribute to it. + + // Far future enhancement + // Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width + // Note that in this case, the class or id will have the img subselector for it to apply to the image. + + // ridiculously far future development + // If the class or id is specified in a SEPARATE css file thats not on the page, go get it and do what we were just doing for the ones on the page. + + $result = array('height' => $height, + 'width' => $width); + return $result; + } + + // camel naming conventions + function getAllAttributes() {return $this->attr;} + function getAttribute($name) {return $this->__get($name);} + function setAttribute($name, $value) {$this->__set($name, $value);} + function hasAttribute($name) {return $this->__isset($name);} + function removeAttribute($name) {$this->__set($name, null);} + function getElementById($id) {return $this->find("#$id", 0);} + function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);} + function getElementByTagName($name) {return $this->find($name, 0);} + function getElementsByTagName($name, $idx=null) {return $this->find($name, $idx);} + function parentNode() {return $this->parent();} + function childNodes($idx=-1) {return $this->children($idx);} + function firstChild() {return $this->first_child();} + function lastChild() {return $this->last_child();} + function nextSibling() {return $this->next_sibling();} + function previousSibling() {return $this->prev_sibling();} + function hasChildNodes() {return $this->has_child();} + function nodeName() {return $this->tag;} + function appendChild($node) {$node->parent($this); return $node;} + +} + +/** + * simple html dom parser + * Paperg - in the find routine: allow us to specify that we want case insensitive testing of the value of the selector. + * Paperg - change $size from protected to public so we can easily access it + * Paperg - added ForceTagsClosed in the constructor which tells us whether we trust the html or not. Default is to NOT trust it. + * + * @package PlaceLocalInclude + */ +class simple_html_dom +{ + public $root = null; + public $nodes = array(); + public $callback = null; + public $lowercase = false; + // Used to keep track of how large the text was when we started. + public $original_size; + public $size; + protected $pos; + protected $doc; + protected $char; + protected $cursor; + protected $parent; + protected $noise = array(); + protected $token_blank = " \t\r\n"; + protected $token_equal = ' =/>'; + protected $token_slash = " />\r\n\t"; + protected $token_attr = ' >'; + // Note that this is referenced by a child node, and so it needs to be public for that node to see this information. + public $_charset = ''; + public $_target_charset = ''; + protected $default_br_text = ""; + public $default_span_text = ""; + + // use isset instead of in_array, performance boost about 30%... + protected $self_closing_tags = array('img'=>1, 'br'=>1, 'input'=>1, 'meta'=>1, 'link'=>1, 'hr'=>1, 'base'=>1, 'embed'=>1, 'spacer'=>1); + protected $block_tags = array('root'=>1, 'body'=>1, 'form'=>1, 'div'=>1, 'span'=>1, 'table'=>1); + // Known sourceforge issue #2977341 + // B tags that are not closed cause us to return everything to the end of the document. + protected $optional_closing_tags = array( + 'tr'=>array('tr'=>1, 'td'=>1, 'th'=>1), + 'th'=>array('th'=>1), + 'td'=>array('td'=>1), + 'li'=>array('li'=>1), + 'dt'=>array('dt'=>1, 'dd'=>1), + 'dd'=>array('dd'=>1, 'dt'=>1), + 'dl'=>array('dd'=>1, 'dt'=>1), + 'p'=>array('p'=>1), + 'nobr'=>array('nobr'=>1), + 'b'=>array('b'=>1), + 'option'=>array('option'=>1), + ); + + function __construct($str=null, $lowercase=true, $forceTagsClosed=true, $target_charset=DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) + { + if ($str) + { + if (preg_match("/^http:\/\//i",$str) || is_file($str)) + { + $this->load_file($str); + } + else + { + $this->load($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText); + } + } + // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html. + if (!$forceTagsClosed) { + $this->optional_closing_array=array(); + } + $this->_target_charset = $target_charset; + } + + function __destruct() + { + $this->clear(); + } + + // load html from string + function load($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) + { + global $debugObject; + + // prepare + $this->prepare($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText); + // strip out comments + $this->remove_noise("''is"); + // strip out cdata + $this->remove_noise("''is", true); + // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037 + // Script tags removal now preceeds style tag removal. + // strip out + + + +
    +

    W3C + +

    Selectors

    + +

    W3C Working Draft 15 December 2005

    + +
    + +
    This version: + +
    + http://www.w3.org/TR/2005/WD-css3-selectors-20051215 + +
    Latest version: + +
    + http://www.w3.org/TR/css3-selectors + +
    Previous version: + +
    + http://www.w3.org/TR/2001/CR-css3-selectors-20011113 + +
    Editors: + +
    Daniel Glazman (Invited Expert)
    + +
    Tantek Çelik (Invited Expert) + +
    Ian Hickson (Google) + +
    Peter Linss (former editor, Netscape/AOL) + +
    John Williams (former editor, Quark, Inc.) + +
    + +
    + +
    + +

    Abstract

    + +

    Selectors are patterns that match against elements in a + tree. Selectors have been optimized for use with HTML and XML, and + are designed to be usable in performance-critical code.

    + +

    CSS (Cascading + Style Sheets) is a language for describing the rendering of HTML and XML documents on + screen, on paper, in speech, etc. CSS uses Selectors for binding + style properties to elements in the document. This document + describes extensions to the selectors defined in CSS level 2. These + extended selectors will be used by CSS level 3. + +

    Selectors define the following function:

    + +
    expression ∗ element → boolean
    + +

    That is, given an element and a selector, this specification + defines whether that element matches the selector.

    + +

    These expressions can also be used, for instance, to select a set + of elements, or a single element from a set of elements, by + evaluating the expression across all the elements in a + subtree. STTS (Simple Tree Transformation Sheets), a + language for transforming XML trees, uses this mechanism. [STTS]

    + +

    Status of this document

    + +

    This section describes the status of this document at the + time of its publication. Other documents may supersede this + document. A list of current W3C publications and the latest revision + of this technical report can be found in the W3C technical reports index at + http://www.w3.org/TR/.

    + +

    This document describes the selectors that already exist in CSS1 and CSS2, and + also proposes new selectors for CSS3 and other languages that may need them.

    + +

    The CSS Working Group doesn't expect that all implementations of + CSS3 will have to implement all selectors. Instead, there will + probably be a small number of variants of CSS3, called profiles. For + example, it may be that only a profile for interactive user agents + will include all of the selectors.

    + +

    This specification is a last call working draft for the the CSS Working Group + (Style Activity). This + document is a revision of the Candidate + Recommendation dated 2001 November 13, and has incorporated + implementation feedback received in the past few years. It is + expected that this last call will proceed straight to Proposed + Recommendation stage since it is believed that interoperability will + be demonstrable.

    + +

    All persons are encouraged to review and implement this + specification and return comments to the (archived) + public mailing list www-style + (see instructions). W3C + Members can also send comments directly to the CSS Working + Group. + The deadline for comments is 14 January 2006.

    + +

    This is still a draft document and may be updated, replaced, or + obsoleted by other documents at any time. It is inappropriate to + cite a W3C Working Draft as other than "work in progress". + +

    This document may be available in translation. + The English version of this specification is the only normative + version. + +

    + +

    Table of contents

    + + + +
    + +

    1. Introduction

    + +

    1.1. Dependencies

    + +

    Some features of this specification are specific to CSS, or have + particular limitations or rules specific to CSS. In this + specification, these have been described in terms of CSS2.1. [CSS21]

    + +

    1.2. Terminology

    + +

    All of the text of this specification is normative except + examples, notes, and sections explicitly marked as + non-normative.

    + +

    1.3. Changes from CSS2

    + +

    This section is non-normative.

    + +

    The main differences between the selectors in CSS2 and those in + Selectors are: + +

      + +
    • the list of basic definitions (selector, group of selectors, + simple selector, etc.) has been changed; in particular, what was + referred to in CSS2 as a simple selector is now called a sequence + of simple selectors, and the term "simple selector" is now used for + the components of this sequence
    • + +
    • an optional namespace component is now allowed in type element + selectors, the universal selector and attribute selectors
    • + +
    • a new combinator has been introduced
    • + +
    • new simple selectors including substring matching attribute + selectors, and new pseudo-classes
    • + +
    • new pseudo-elements, and introduction of the "::" convention + for pseudo-elements
    • + +
    • the grammar has been rewritten
    • + +
    • profiles to be added to specifications integrating Selectors + and defining the set of selectors which is actually supported by + each specification
    • + +
    • Selectors are now a CSS3 Module and an independent + specification; other specifications can now refer to this document + independently of CSS
    • + +
    • the specification now has its own test suite
    • + +
    + +

    2. Selectors

    + +

    This section is non-normative, as it merely summarizes the +following sections.

    + +

    A Selector represents a structure. This structure can be used as a +condition (e.g. in a CSS rule) that determines which elements a +selector matches in the document tree, or as a flat description of the +HTML or XML fragment corresponding to that structure.

    + +

    Selectors may range from simple element names to rich contextual +representations.

    + +

    The following table summarizes the Selector syntax:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PatternMeaningDescribed in sectionFirst defined in CSS level
    *any elementUniversal + selector2
    Ean element of type EType selector1
    E[foo]an E element with a "foo" attributeAttribute + selectors2
    E[foo="bar"]an E element whose "foo" attribute value is exactly + equal to "bar"Attribute + selectors2
    E[foo~="bar"]an E element whose "foo" attribute value is a list of + space-separated values, one of which is exactly equal to "bar"Attribute + selectors2
    E[foo^="bar"]an E element whose "foo" attribute value begins exactly + with the string "bar"Attribute + selectors3
    E[foo$="bar"]an E element whose "foo" attribute value ends exactly + with the string "bar"Attribute + selectors3
    E[foo*="bar"]an E element whose "foo" attribute value contains the + substring "bar"Attribute + selectors3
    E[hreflang|="en"]an E element whose "hreflang" attribute has a hyphen-separated + list of values beginning (from the left) with "en"Attribute + selectors2
    E:rootan E element, root of the documentStructural + pseudo-classes3
    E:nth-child(n)an E element, the n-th child of its parentStructural + pseudo-classes3
    E:nth-last-child(n)an E element, the n-th child of its parent, counting + from the last oneStructural + pseudo-classes3
    E:nth-of-type(n)an E element, the n-th sibling of its typeStructural + pseudo-classes3
    E:nth-last-of-type(n)an E element, the n-th sibling of its type, counting + from the last oneStructural + pseudo-classes3
    E:first-childan E element, first child of its parentStructural + pseudo-classes2
    E:last-childan E element, last child of its parentStructural + pseudo-classes3
    E:first-of-typean E element, first sibling of its typeStructural + pseudo-classes3
    E:last-of-typean E element, last sibling of its typeStructural + pseudo-classes3
    E:only-childan E element, only child of its parentStructural + pseudo-classes3
    E:only-of-typean E element, only sibling of its typeStructural + pseudo-classes3
    E:emptyan E element that has no children (including text + nodes)Structural + pseudo-classes3
    E:link
    E:visited
    an E element being the source anchor of a hyperlink of + which the target is not yet visited (:link) or already visited + (:visited)The link + pseudo-classes1
    E:active
    E:hover
    E:focus
    an E element during certain user actionsThe user + action pseudo-classes1 and 2
    E:targetan E element being the target of the referring URIThe target + pseudo-class3
    E:lang(fr)an element of type E in language "fr" (the document + language specifies how language is determined)The :lang() + pseudo-class2
    E:enabled
    E:disabled
    a user interface element E which is enabled or + disabledThe UI element states + pseudo-classes3
    E:checkeda user interface element E which is checked (for instance a radio-button or checkbox)The UI element states + pseudo-classes3
    E::first-linethe first formatted line of an E elementThe ::first-line + pseudo-element1
    E::first-letterthe first formatted letter of an E elementThe ::first-letter + pseudo-element1
    E::selectionthe portion of an E element that is currently + selected/highlighted by the userThe UI element + fragments pseudo-elements3
    E::beforegenerated content before an E elementThe ::before + pseudo-element2
    E::aftergenerated content after an E elementThe ::after + pseudo-element2
    E.warningan E element whose class is +"warning" (the document language specifies how class is determined).Class + selectors1
    E#myidan E element with ID equal to "myid".ID + selectors1
    E:not(s)an E element that does not match simple selector sNegation + pseudo-class3
    E Fan F element descendant of an E elementDescendant + combinator1
    E > Fan F element child of an E elementChild + combinator2
    E + Fan F element immediately preceded by an E elementAdjacent sibling combinator2
    E ~ Fan F element preceded by an E elementGeneral sibling combinator3
    + +

    The meaning of each selector is derived from the table above by +prepending "matches" to the contents of each cell in the "Meaning" +column.

    + +

    3. Case sensitivity

    + +

    The case sensitivity of document language element names, attribute +names, and attribute values in selectors depends on the document +language. For example, in HTML, element names are case-insensitive, +but in XML, they are case-sensitive.

    + +

    4. Selector syntax

    + +

    A selector is a chain of one +or more sequences of simple selectors +separated by combinators.

    + +

    A sequence of simple selectors +is a chain of simple selectors +that are not separated by a combinator. It +always begins with a type selector or a +universal selector. No other type +selector or universal selector is allowed in the sequence.

    + +

    A simple selector is either a type selector, universal selector, attribute selector, class selector, ID selector, content selector, or pseudo-class. One pseudo-element may be appended to the last +sequence of simple selectors.

    + +

    Combinators are: white space, "greater-than +sign" (U+003E, >), "plus sign" (U+002B, ++) and "tilde" (U+007E, ~). White +space may appear between a combinator and the simple selectors around +it. Only the characters "space" (U+0020), "tab" +(U+0009), "line feed" (U+000A), "carriage return" (U+000D), and "form +feed" (U+000C) can occur in white space. Other space-like characters, +such as "em-space" (U+2003) and "ideographic space" (U+3000), are +never part of white space.

    + +

    The elements of a document tree that are represented by a selector +are the subjects of the selector. A +selector consisting of a single sequence of simple selectors +represents any element satisfying its requirements. Prepending another +sequence of simple selectors and a combinator to a sequence imposes +additional matching constraints, so the subjects of a selector are +always a subset of the elements represented by the last sequence of +simple selectors.

    + +

    An empty selector, containing no sequence of simple selectors and +no pseudo-element, is an invalid +selector.

    + +

    5. Groups of selectors

    + +

    When several selectors share the same declarations, they may be +grouped into a comma-separated list. (A comma is U+002C.)

    + +
    +

    CSS examples:

    +

    In this example, we condense three rules with identical +declarations into one. Thus,

    +
    h1 { font-family: sans-serif }
    +h2 { font-family: sans-serif }
    +h3 { font-family: sans-serif }
    +

    is equivalent to:

    +
    h1, h2, h3 { font-family: sans-serif }
    +
    + +

    Warning: the equivalence is true in this example +because all the selectors are valid selectors. If just one of these +selectors were invalid, the entire group of selectors would be +invalid. This would invalidate the rule for all three heading +elements, whereas in the former case only one of the three individual +heading rules would be invalidated.

    + + +

    6. Simple selectors

    + +

    6.1. Type selector

    + +

    A type selector is the name of a document language +element type. A type selector represents an instance of the element +type in the document tree.

    + +
    +

    Example:

    +

    The following selector represents an h1 element in the document tree:

    +
    h1
    +
    + + +

    6.1.1. Type selectors and namespaces

    + +

    Type selectors allow an optional namespace ([XMLNAMES]) component. A namespace prefix +that has been previously declared may be prepended to the element name +separated by the namespace separator "vertical bar" +(U+007C, |).

    + +

    The namespace component may be left empty to indicate that the +selector is only to represent elements with no declared namespace.

    + +

    An asterisk may be used for the namespace prefix, indicating that +the selector represents elements in any namespace (including elements +with no namespace).

    + +

    Element type selectors that have no namespace component (no +namespace separator), represent elements without regard to the +element's namespace (equivalent to "*|") unless a default +namespace has been declared. If a default namespace has been declared, +the selector will represent only elements in the default +namespace.

    + +

    A type selector containing a namespace prefix that has not been +previously declared is an invalid selector. +The mechanism for declaring a namespace prefix is left up to the +language implementing Selectors. In CSS, such a mechanism is defined +in the General Syntax module.

    + +

    In a namespace-aware client, element type selectors will only match +against the local part +of the element's qualified +name. See below for notes about matching +behaviors in down-level clients.

    + +

    In summary:

    + +
    +
    ns|E
    +
    elements with name E in namespace ns
    +
    *|E
    +
    elements with name E in any namespace, including those without any + declared namespace
    +
    |E
    +
    elements with name E without any declared namespace
    +
    E
    +
    if no default namespace has been specified, this is equivalent to *|E. + Otherwise it is equivalent to ns|E where ns is the default namespace.
    +
    + +
    +

    CSS examples:

    + +
    @namespace foo url(http://www.example.com);
    + foo|h1 { color: blue }
    + foo|* { color: yellow }
    + |h1 { color: red }
    + *|h1 { color: green }
    + h1 { color: green }
    + +

    The first rule will match only h1 elements in the + "http://www.example.com" namespace.

    + +

    The second rule will match all elements in the + "http://www.example.com" namespace.

    + +

    The third rule will match only h1 elements without + any declared namespace.

    + +

    The fourth rule will match h1 elements in any + namespace (including those without any declared namespace).

    + +

    The last rule is equivalent to the fourth rule because no default + namespace has been defined.

    + +
    + +

    6.2. Universal selector

    + +

    The universal selector, written "asterisk" +(*), represents the qualified name of any element +type. It represents any single element in the document tree in any +namespace (including those without any declared namespace) if no +default namespace has been specified. If a default namespace has been +specified, see Universal selector and +Namespaces below.

    + +

    If the universal selector is not the only component of a sequence +of simple selectors, the * may be omitted.

    + +
    +

    Examples:

    +
      +
    • *[hreflang|=en] and [hreflang|=en] are equivalent,
    • +
    • *.warning and .warning are equivalent,
    • +
    • *#myid and #myid are equivalent.
    • +
    +
    + +

    Note: it is recommended that the +*, representing the universal selector, not be +omitted.

    + +

    6.2.1. Universal selector and namespaces

    + +

    The universal selector allows an optional namespace component. It +is used as follows:

    + +
    +
    ns|*
    +
    all elements in namespace ns
    +
    *|*
    +
    all elements
    +
    |*
    +
    all elements without any declared namespace
    +
    *
    +
    if no default namespace has been specified, this is equivalent to *|*. + Otherwise it is equivalent to ns|* where ns is the default namespace.
    +
    + +

    A universal selector containing a namespace prefix that has not +been previously declared is an invalid +selector. The mechanism for declaring a namespace prefix is left up +to the language implementing Selectors. In CSS, such a mechanism is +defined in the General Syntax module.

    + + +

    6.3. Attribute selectors

    + +

    Selectors allow the representation of an element's attributes. When +a selector is used as an expression to match against an element, +attribute selectors must be considered to match an element if that +element has an attribute that matches the attribute represented by the +attribute selector.

    + +

    6.3.1. Attribute presence and values +selectors

    + +

    CSS2 introduced four attribute selectors:

    + +
    +
    [att] +
    Represents an element with the att attribute, whatever the value of + the attribute.
    +
    [att=val]
    +
    Represents an element with the att attribute whose value is exactly + "val".
    +
    [att~=val]
    +
    Represents an element with the att attribute whose value is a whitespace-separated list of words, one of + which is exactly "val". If "val" contains whitespace, it will never + represent anything (since the words are separated by + spaces).
    +
    [att|=val] +
    Represents an element with the att attribute, its value either + being exactly "val" or beginning with "val" immediately followed by + "-" (U+002D). This is primarily intended to allow language subcode + matches (e.g., the hreflang attribute on the + link element in HTML) as described in RFC 3066 ([RFC3066]). For lang (or + xml:lang) language subcode matching, please see the :lang pseudo-class.
    +
    + +

    Attribute values must be identifiers or strings. The +case-sensitivity of attribute names and values in selectors depends on +the document language.

    + +
    + +

    Examples:

    + +

    The following attribute selector represents an h1 + element that carries the title attribute, whatever its + value:

    + +
    h1[title]
    + +

    In the following example, the selector represents a + span element whose class attribute has + exactly the value "example":

    + +
    span[class="example"]
    + +

    Multiple attribute selectors can be used to represent several + attributes of an element, or several conditions on the same + attribute. Here, the selector represents a span element + whose hello attribute has exactly the value "Cleveland" + and whose goodbye attribute has exactly the value + "Columbus":

    + +
    span[hello="Cleveland"][goodbye="Columbus"]
    + +

    The following selectors illustrate the differences between "=" + and "~=". The first selector will represent, for example, the value + "copyright copyleft copyeditor" on a rel attribute. The + second selector will only represent an a element with + an href attribute having the exact value + "http://www.w3.org/".

    + +
    a[rel~="copyright"]
    +a[href="http://www.w3.org/"]
    + +

    The following selector represents a link element + whose hreflang attribute is exactly "fr".

    + +
    link[hreflang=fr]
    + +

    The following selector represents a link element for + which the values of the hreflang attribute begins with + "en", including "en", "en-US", and "en-cockney":

    + +
    link[hreflang|="en"]
    + +

    Similarly, the following selectors represents a + DIALOGUE element whenever it has one of two different + values for an attribute character:

    + +
    DIALOGUE[character=romeo]
    +DIALOGUE[character=juliet]
    + +
    + +

    6.3.2. Substring matching attribute +selectors

    + +

    Three additional attribute selectors are provided for matching +substrings in the value of an attribute:

    + +
    +
    [att^=val]
    +
    Represents an element with the att attribute whose value begins + with the prefix "val".
    +
    [att$=val] +
    Represents an element with the att attribute whose value ends with + the suffix "val".
    +
    [att*=val] +
    Represents an element with the att attribute whose value contains + at least one instance of the substring "val".
    +
    + +

    Attribute values must be identifiers or strings. The +case-sensitivity of attribute names in selectors depends on the +document language.

    + +
    +

    Examples:

    +

    The following selector represents an HTML object, referencing an + image:

    +
    object[type^="image/"]
    +

    The following selector represents an HTML anchor a with an + href attribute whose value ends with ".html".

    +
    a[href$=".html"]
    +

    The following selector represents an HTML paragraph with a title + attribute whose value contains the substring "hello"

    +
    p[title*="hello"]
    +
    + +

    6.3.3. Attribute selectors and namespaces

    + +

    Attribute selectors allow an optional namespace component to the +attribute name. A namespace prefix that has been previously declared +may be prepended to the attribute name separated by the namespace +separator "vertical bar" (|). In keeping with +the Namespaces in the XML recommendation, default namespaces do not +apply to attributes, therefore attribute selectors without a namespace +component apply only to attributes that have no declared namespace +(equivalent to "|attr"). An asterisk may be used for the +namespace prefix indicating that the selector is to match all +attribute names without regard to the attribute's namespace. + +

    An attribute selector with an attribute name containing a namespace +prefix that has not been previously declared is an invalid selector. The mechanism for declaring +a namespace prefix is left up to the language implementing Selectors. +In CSS, such a mechanism is defined in the General Syntax module. + +

    +

    CSS examples:

    +
    @namespace foo "http://www.example.com";
    +[foo|att=val] { color: blue }
    +[*|att] { color: yellow }
    +[|att] { color: green }
    +[att] { color: green }
    + +

    The first rule will match only elements with the attribute + att in the "http://www.example.com" namespace with the + value "val".

    + +

    The second rule will match only elements with the attribute + att regardless of the namespace of the attribute + (including no declared namespace).

    + +

    The last two rules are equivalent and will match only elements + with the attribute att where the attribute is not + declared to be in a namespace.

    + +
    + +

    6.3.4. Default attribute values in DTDs

    + +

    Attribute selectors represent explicitly set attribute values in +the document tree. Default attribute values may be defined in a DTD or +elsewhere, but cannot always be selected by attribute +selectors. Selectors should be designed so that they work even if the +default values are not included in the document tree.

    + +

    More precisely, a UA is not required to read an "external +subset" of the DTD but is required to look for default +attribute values in the document's "internal subset." (See [XML10] for definitions of these subsets.)

    + +

    A UA that recognizes an XML namespace [XMLNAMES] is not required to use its +knowledge of that namespace to treat default attribute values as if +they were present in the document. (For example, an XHTML UA is not +required to use its built-in knowledge of the XHTML DTD.)

    + +

    Note: Typically, implementations +choose to ignore external subsets.

    + +
    +

    Example:

    + +

    Consider an element EXAMPLE with an attribute "notation" that has a +default value of "decimal". The DTD fragment might be

    + +
    <!ATTLIST EXAMPLE notation (decimal,octal) "decimal">
    + +

    If the style sheet contains the rules

    + +
    EXAMPLE[notation=decimal] { /*... default property settings ...*/ }
    +EXAMPLE[notation=octal]   { /*... other settings...*/ }
    + +

    the first rule will not match elements whose "notation" attribute +is set by default, i.e. not set explicitly. To catch all cases, the +attribute selector for the default value must be dropped:

    + +
    EXAMPLE                   { /*... default property settings ...*/ }
    +EXAMPLE[notation=octal]   { /*... other settings...*/ }
    + +

    Here, because the selector EXAMPLE[notation=octal] is +more specific than the tag +selector alone, the style declarations in the second rule will override +those in the first for elements that have a "notation" attribute value +of "octal". Care has to be taken that all property declarations that +are to apply only to the default case are overridden in the non-default +cases' style rules.

    + +
    + +

    6.4. Class selectors

    + +

    Working with HTML, authors may use the period (U+002E, +.) notation as an alternative to the ~= +notation when representing the class attribute. Thus, for +HTML, div.value and div[class~=value] have +the same meaning. The attribute value must immediately follow the +"period" (.).

    + +

    UAs may apply selectors using the period (.) notation in XML +documents if the UA has namespace-specific knowledge that allows it to +determine which attribute is the "class" attribute for the +respective namespace. One such example of namespace-specific knowledge +is the prose in the specification for a particular namespace (e.g. SVG +1.0 [SVG] describes the SVG +"class" attribute and how a UA should interpret it, and +similarly MathML 1.01 [MATH] describes the MathML +"class" attribute.)

    + +
    +

    CSS examples:

    + +

    We can assign style information to all elements with + class~="pastoral" as follows:

    + +
    *.pastoral { color: green }  /* all elements with class~=pastoral */
    + +

    or just

    + +
    .pastoral { color: green }  /* all elements with class~=pastoral */
    + +

    The following assigns style only to H1 elements with + class~="pastoral":

    + +
    H1.pastoral { color: green }  /* H1 elements with class~=pastoral */
    + +

    Given these rules, the first H1 instance below would not have + green text, while the second would:

    + +
    <H1>Not green</H1>
    +<H1 class="pastoral">Very green</H1>
    + +
    + +

    To represent a subset of "class" values, each value must be preceded +by a ".", in any order.

    + +
    + +

    CSS example:

    + +

    The following rule matches any P element whose "class" attribute + has been assigned a list of whitespace-separated values that includes + "pastoral" and "marine":

    + +
    p.pastoral.marine { color: green }
    + +

    This rule matches when class="pastoral blue aqua + marine" but does not match for class="pastoral + blue".

    + +
    + +

    Note: Because CSS gives considerable +power to the "class" attribute, authors could conceivably design their +own "document language" based on elements with almost no associated +presentation (such as DIV and SPAN in HTML) and assigning style +information through the "class" attribute. Authors should avoid this +practice since the structural elements of a document language often +have recognized and accepted meanings and author-defined classes may +not.

    + +

    Note: If an element has multiple +class attributes, their values must be concatenated with spaces +between the values before searching for the class. As of this time the +working group is not aware of any manner in which this situation can +be reached, however, so this behavior is explicitly non-normative in +this specification.

    + +

    6.5. ID selectors

    + +

    Document languages may contain attributes that are declared to be +of type ID. What makes attributes of type ID special is that no two +such attributes can have the same value in a document, regardless of +the type of the elements that carry them; whatever the document +language, an ID typed attribute can be used to uniquely identify its +element. In HTML all ID attributes are named "id"; XML applications +may name ID attributes differently, but the same restriction +applies.

    + +

    An ID-typed attribute of a document language allows authors to +assign an identifier to one element instance in the document tree. W3C +ID selectors represent an element instance based on its identifier. An +ID selector contains a "number sign" (U+0023, +#) immediately followed by the ID value, which must be an +identifier.

    + +

    Selectors does not specify how a UA knows the ID-typed attribute of +an element. The UA may, e.g., read a document's DTD, have the +information hard-coded or ask the user. + +

    +

    Examples:

    +

    The following ID selector represents an h1 element + whose ID-typed attribute has the value "chapter1":

    +
    h1#chapter1
    +

    The following ID selector represents any element whose ID-typed + attribute has the value "chapter1":

    +
    #chapter1
    +

    The following selector represents any element whose ID-typed + attribute has the value "z98y".

    +
    *#z98y
    +
    + +

    Note. In XML 1.0 [XML10], the information about which attribute +contains an element's IDs is contained in a DTD or a schema. When +parsing XML, UAs do not always read the DTD, and thus may not know +what the ID of an element is (though a UA may have namespace-specific +knowledge that allows it to determine which attribute is the ID +attribute for that namespace). If a style sheet designer knows or +suspects that a UA may not know what the ID of an element is, he +should use normal attribute selectors instead: +[name=p371] instead of #p371. Elements in +XML 1.0 documents without a DTD do not have IDs at all.

    + +

    If an element has multiple ID attributes, all of them must be +treated as IDs for that element for the purposes of the ID +selector. Such a situation could be reached using mixtures of xml:id, +DOM3 Core, XML DTDs, and namespace-specific knowledge.

    + +

    6.6. Pseudo-classes

    + +

    The pseudo-class concept is introduced to permit selection based on +information that lies outside of the document tree or that cannot be +expressed using the other simple selectors.

    + +

    A pseudo-class always consists of a "colon" +(:) followed by the name of the pseudo-class and +optionally by a value between parentheses.

    + +

    Pseudo-classes are allowed in all sequences of simple selectors +contained in a selector. Pseudo-classes are allowed anywhere in +sequences of simple selectors, after the leading type selector or +universal selector (possibly omitted). Pseudo-class names are +case-insensitive. Some pseudo-classes are mutually exclusive, while +others can be applied simultaneously to the same +element. Pseudo-classes may be dynamic, in the sense that an element +may acquire or lose a pseudo-class while a user interacts with the +document.

    + + +

    6.6.1. Dynamic pseudo-classes

    + +

    Dynamic pseudo-classes classify elements on characteristics other +than their name, attributes, or content, in principle characteristics +that cannot be deduced from the document tree.

    + +

    Dynamic pseudo-classes do not appear in the document source or +document tree.

    + + +
    The link pseudo-classes: :link and :visited
    + +

    User agents commonly display unvisited links differently from +previously visited ones. Selectors +provides the pseudo-classes :link and +:visited to distinguish them:

    + +
      +
    • The :link pseudo-class applies to links that have + not yet been visited.
    • +
    • The :visited pseudo-class applies once the link has + been visited by the user.
    • +
    + +

    After some amount of time, user agents may choose to return a +visited link to the (unvisited) ':link' state.

    + +

    The two states are mutually exclusive.

    + +
    + +

    Example:

    + +

    The following selector represents links carrying class + external and already visited:

    + +
    a.external:visited
    + +
    + +

    Note: It is possible for style sheet +authors to abuse the :link and :visited pseudo-classes to determine +which sites a user has visited without the user's consent. + +

    UAs may therefore treat all links as unvisited links, or implement +other measures to preserve the user's privacy while rendering visited +and unvisited links differently.

    + +
    The user action pseudo-classes +:hover, :active, and :focus
    + +

    Interactive user agents sometimes change the rendering in response +to user actions. Selectors provides +three pseudo-classes for the selection of an element the user is +acting on.

    + +
      + +
    • The :hover pseudo-class applies while the user + designates an element with a pointing device, but does not activate + it. For example, a visual user agent could apply this pseudo-class + when the cursor (mouse pointer) hovers over a box generated by the + element. User agents not that do not support interactive + media do not have to support this pseudo-class. Some conforming + user agents that support interactive + media may not be able to support this pseudo-class (e.g., a pen + device that does not detect hovering).
    • + +
    • The :active pseudo-class applies while an element + is being activated by the user. For example, between the times the + user presses the mouse button and releases it.
    • + +
    • The :focus pseudo-class applies while an element + has the focus (accepts keyboard or mouse events, or other forms of + input).
    • + +
    + +

    There may be document language or implementation specific limits on +which elements can become :active or acquire +:focus.

    + +

    These pseudo-classes are not mutually exclusive. An element may +match several pseudo-classes at the same time.

    + +

    Selectors doesn't define if the parent of an element that is +':active' or ':hover' is also in that state.

    + +
    +

    Examples:

    +
    a:link    /* unvisited links */
    +a:visited /* visited links */
    +a:hover   /* user hovers */
    +a:active  /* active links */
    +

    An example of combining dynamic pseudo-classes:

    +
    a:focus
    +a:focus:hover
    +

    The last selector matches a elements that are in + the pseudo-class :focus and in the pseudo-class :hover.

    +
    + +

    Note: An element can be both ':visited' +and ':active' (or ':link' and ':active').

    + +

    6.6.2. The target pseudo-class :target

    + +

    Some URIs refer to a location within a resource. This kind of URI +ends with a "number sign" (#) followed by an anchor +identifier (called the fragment identifier).

    + +

    URIs with fragment identifiers link to a certain element within the +document, known as the target element. For instance, here is a URI +pointing to an anchor named section_2 in an HTML +document:

    + +
    http://example.com/html/top.html#section_2
    + +

    A target element can be represented by the :target +pseudo-class. If the document's URI has no fragment identifier, then +the document has no target element.

    + +
    +

    Example:

    +
    p.note:target
    +

    This selector represents a p element of class + note that is the target element of the referring + URI.

    +
    + +
    +

    CSS example:

    +

    Here, the :target pseudo-class is used to make the + target element red and place an image before it, if there is one:

    +
    *:target { color : red }
    +*:target::before { content : url(target.png) }
    +
    + +

    6.6.3. The language pseudo-class :lang

    + +

    If the document language specifies how the human language of an +element is determined, it is possible to write selectors that +represent an element based on its language. For example, in HTML [HTML4], the language is determined by a +combination of the lang attribute, the meta +element, and possibly by information from the protocol (such as HTTP +headers). XML uses an attribute called xml:lang, and +there may be other document language-specific methods for determining +the language.

    + +

    The pseudo-class :lang(C) represents an element that +is in language C. Whether an element is represented by a +:lang() selector is based solely on the identifier C +being either equal to, or a hyphen-separated substring of, the +element's language value, in the same way as if performed by the '|=' operator in attribute +selectors. The identifier C does not have to be a valid language +name.

    + +

    C must not be empty. (If it is, the selector is invalid.)

    + +

    Note: It is recommended that +documents and protocols indicate language using codes from RFC 3066 [RFC3066] or its successor, and by means of +"xml:lang" attributes in the case of XML-based documents [XML10]. See +"FAQ: Two-letter or three-letter language codes."

    + +
    +

    Examples:

    +

    The two following selectors represent an HTML document that is in + Belgian, French, or German. The two next selectors represent + q quotations in an arbitrary element in Belgian, French, + or German.

    +
    html:lang(fr-be)
    +html:lang(de)
    +:lang(fr-be) > q
    +:lang(de) > q
    +
    + +

    6.6.4. The UI element states pseudo-classes

    + +
    The :enabled and :disabled pseudo-classes
    + +

    The :enabled pseudo-class allows authors to customize +the look of user interface elements that are enabled — which the +user can select or activate in some fashion (e.g. clicking on a button +with a mouse). There is a need for such a pseudo-class because there +is no way to programmatically specify the default appearance of say, +an enabled input element without also specifying what it +would look like when it was disabled.

    + +

    Similar to :enabled, :disabled allows the +author to specify precisely how a disabled or inactive user interface +element should look.

    + +

    Most elements will be neither enabled nor disabled. An element is +enabled if the user can either activate it or transfer the focus to +it. An element is disabled if it could be enabled, but the user cannot +presently activate it or transfer focus to it.

    + + +
    The :checked pseudo-class
    + +

    Radio and checkbox elements can be toggled by the user. Some menu +items are "checked" when the user selects them. When such elements are +toggled "on" the :checked pseudo-class applies. The +:checked pseudo-class initially applies to such elements +that have the HTML4 selected and checked +attributes as described in Section +17.2.1 of HTML4, but of course the user can toggle "off" such +elements in which case the :checked pseudo-class would no +longer apply. While the :checked pseudo-class is dynamic +in nature, and is altered by user action, since it can also be based +on the presence of the semantic HTML4 selected and +checked attributes, it applies to all media. + + +

    The :indeterminate pseudo-class
    + +
    + +

    Radio and checkbox elements can be toggled by the user, but are +sometimes in an indeterminate state, neither checked nor unchecked. +This can be due to an element attribute, or DOM manipulation.

    + +

    A future version of this specification may introduce an +:indeterminate pseudo-class that applies to such elements. +

    + +
    + + +

    6.6.5. Structural pseudo-classes

    + +

    Selectors introduces the concept of structural +pseudo-classes to permit selection based on extra information that lies in +the document tree but cannot be represented by other simple selectors or +combinators. + +

    Note that standalone pieces of PCDATA (text nodes in the DOM) are +not counted when calculating the position of an element in the list of +children of its parent. When calculating the position of an element in +the list of children of its parent, the index numbering starts at 1. + + +

    :root pseudo-class
    + +

    The :root pseudo-class represents an element that is +the root of the document. In HTML 4, this is always the +HTML element. + + +

    :nth-child() pseudo-class
    + +

    The +:nth-child(an+b) +pseudo-class notation represents an element that has +an+b-1 siblings +before it in the document tree, for a given positive +integer or zero value of n, and has a parent element. In +other words, this matches the bth child of an element after +all the children have been split into groups of a elements +each. For example, this allows the selectors to address every other +row in a table, and could be used to alternate the color +of paragraph text in a cycle of four. The a and +b values must be zero, negative integers or positive +integers. The index of the first child of an element is 1. + +

    In addition to this, :nth-child() can take +'odd' and 'even' as arguments instead. +'odd' has the same signification as 2n+1, +and 'even' has the same signification as 2n. + + +

    +

    Examples:

    +
    tr:nth-child(2n+1) /* represents every odd row of an HTML table */
    +tr:nth-child(odd)  /* same */
    +tr:nth-child(2n)   /* represents every even row of an HTML table */
    +tr:nth-child(even) /* same */
    +
    +/* Alternate paragraph colours in CSS */
    +p:nth-child(4n+1) { color: navy; }
    +p:nth-child(4n+2) { color: green; }
    +p:nth-child(4n+3) { color: maroon; }
    +p:nth-child(4n+4) { color: purple; }
    +
    + +

    When a=0, no repeating is used, so for example +:nth-child(0n+5) matches only the fifth child. When +a=0, the an part need not be +included, so the syntax simplifies to +:nth-child(b) and the last example simplifies +to :nth-child(5). + +

    +

    Examples:

    +
    foo:nth-child(0n+1)   /* represents an element foo, first child of its parent element */
    +foo:nth-child(1)      /* same */
    +
    + +

    When a=1, the number may be omitted from the rule. + +

    +

    Examples:

    +

    The following selectors are therefore equivalent:

    +
    bar:nth-child(1n+0)   /* represents all bar elements, specificity (0,1,1) */
    +bar:nth-child(n+0)    /* same */
    +bar:nth-child(n)      /* same */
    +bar                   /* same but lower specificity (0,0,1) */
    +
    + +

    If b=0, then every ath element is picked. In +such a case, the b part may be omitted. + +

    +

    Examples:

    +
    tr:nth-child(2n+0) /* represents every even row of an HTML table */
    +tr:nth-child(2n) /* same */
    +
    + +

    If both a and b are equal to zero, the +pseudo-class represents no element in the document tree.

    + +

    The value a can be negative, but only the positive +values of an+b, for +n≥0, may represent an element in the document +tree.

    + +
    +

    Example:

    +
    html|tr:nth-child(-n+6)  /* represents the 6 first rows of XHTML tables */
    +
    + +

    When the value b is negative, the "+" character in the +expression must be removed (it is effectively replaced by the "-" +character indicating the negative value of b).

    + +
    +

    Examples:

    +
    :nth-child(10n-1)  /* represents the 9th, 19th, 29th, etc, element */
    +:nth-child(10n+9)  /* Same */
    +:nth-child(10n+-1) /* Syntactically invalid, and would be ignored */
    +
    + + +
    :nth-last-child() pseudo-class
    + +

    The :nth-last-child(an+b) +pseudo-class notation represents an element that has +an+b-1 siblings +after it in the document tree, for a given positive +integer or zero value of n, and has a parent element. See +:nth-child() pseudo-class for the syntax of its argument. +It also accepts the 'even' and 'odd' values +as arguments. + + +

    +

    Examples:

    +
    tr:nth-last-child(-n+2)    /* represents the two last rows of an HTML table */
    +
    +foo:nth-last-child(odd)    /* represents all odd foo elements in their parent element,
    +                              counting from the last one */
    +
    + + +
    :nth-of-type() pseudo-class
    + +

    The :nth-of-type(an+b) +pseudo-class notation represents an element that has +an+b-1 siblings with the same +element name before it in the document tree, for a +given zero or positive integer value of n, and has a +parent element. In other words, this matches the bth child +of that type after all the children of that type have been split into +groups of a elements each. See :nth-child() pseudo-class +for the syntax of its argument. It also accepts the +'even' and 'odd' values. + + +

    +

    CSS example:

    +

    This allows an author to alternate the position of floated images:

    +
    img:nth-of-type(2n+1) { float: right; }
    +img:nth-of-type(2n) { float: left; }
    +
    + + +
    :nth-last-of-type() pseudo-class
    + +

    The :nth-last-of-type(an+b) +pseudo-class notation represents an element that has +an+b-1 siblings with the same +element name after it in the document tree, for a +given zero or positive integer value of n, and has a +parent element. See :nth-child() pseudo-class for the +syntax of its argument. It also accepts the 'even' and 'odd' values. + + +

    +

    Example:

    +

    To represent all h2 children of an XHTML + body except the first and last, one could use the + following selector:

    +
    body > h2:nth-of-type(n+2):nth-last-of-type(n+2)
    +

    In this case, one could also use :not(), although the + selector ends up being just as long:

    +
    body > h2:not(:first-of-type):not(:last-of-type)
    +
    + + +
    :first-child pseudo-class
    + +

    Same as :nth-child(1). The :first-child pseudo-class +represents an element that is the first child of some other element. + + +

    +

    Examples:

    +

    The following selector represents a p element that is + the first child of a div element:

    +
    div > p:first-child
    +

    This selector can represent the p inside the + div of the following fragment:

    +
    <p> The last P before the note.</p>
    +<div class="note">
    +   <p> The first P inside the note.</p>
    +</div>
    but cannot represent the second p in the following +fragment: +
    <p> The last P before the note.</p>
    +<div class="note">
    +   <h2> Note </h2>
    +   <p> The first P inside the note.</p>
    +</div>
    +

    The following two selectors are usually equivalent:

    +
    * > a:first-child /* a is first child of any element */
    +a:first-child /* Same (assuming a is not the root element) */
    +
    + +
    :last-child pseudo-class
    + +

    Same as :nth-last-child(1). The :last-child pseudo-class +represents an element that is the last child of some other element. + +

    +

    Example:

    +

    The following selector represents a list item li that + is the last child of an ordered list ol. +

    ol > li:last-child
    +
    + +
    :first-of-type pseudo-class
    + +

    Same as :nth-of-type(1). The :first-of-type pseudo-class +represents an element that is the first sibling of its type in the list of +children of its parent element. + +

    +

    Example:

    +

    The following selector represents a definition title +dt inside a definition list dl, this +dt being the first of its type in the list of children of +its parent element.

    +
    dl dt:first-of-type
    +

    It is a valid description for the first two dt +elements in the following example but not for the third one:

    +
    <dl>
    + <dt>gigogne</dt>
    + <dd>
    +  <dl>
    +   <dt>fusée</dt>
    +   <dd>multistage rocket</dd>
    +   <dt>table</dt>
    +   <dd>nest of tables</dd>
    +  </dl>
    + </dd>
    +</dl>
    +
    + +
    :last-of-type pseudo-class
    + +

    Same as :nth-last-of-type(1). The +:last-of-type pseudo-class represents an element that is +the last sibling of its type in the list of children of its parent +element.

    + +
    +

    Example:

    +

    The following selector represents the last data cell + td of a table row.

    +
    tr > td:last-of-type
    +
    + +
    :only-child pseudo-class
    + +

    Represents an element that has a parent element and whose parent +element has no other element children. Same as +:first-child:last-child or +:nth-child(1):nth-last-child(1), but with a lower +specificity.

    + +
    :only-of-type pseudo-class
    + +

    Represents an element that has a parent element and whose parent +element has no other element children with the same element name. Same +as :first-of-type:last-of-type or +:nth-of-type(1):nth-last-of-type(1), but with a lower +specificity.

    + + +
    :empty pseudo-class
    + +

    The :empty pseudo-class represents an element that has +no children at all. In terms of the DOM, only element nodes and text +nodes (including CDATA nodes and entity references) whose data has a +non-zero length must be considered as affecting emptiness; comments, +PIs, and other nodes must not affect whether an element is considered +empty or not.

    + +
    +

    Examples:

    +

    p:empty is a valid representation of the following fragment:

    +
    <p></p>
    +

    foo:empty is not a valid representation for the + following fragments:

    +
    <foo>bar</foo>
    +
    <foo><bar>bla</bar></foo>
    +
    <foo>this is not <bar>:empty</bar></foo>
    +
    + +

    6.6.6. Blank

    + +

    This section intentionally left blank.

    + + +

    6.6.7. The negation pseudo-class

    + +

    The negation pseudo-class, :not(X), is a +functional notation taking a simple +selector (excluding the negation pseudo-class itself and +pseudo-elements) as an argument. It represents an element that is not +represented by the argument. + + + +

    +

    Examples:

    +

    The following CSS selector matches all button + elements in an HTML document that are not disabled.

    +
    button:not([DISABLED])
    +

    The following selector represents all but FOO + elements.

    +
    *:not(FOO)
    +

    The following group of selectors represents all HTML elements + except links.

    +
    html|*:not(:link):not(:visited)
    +
    + +

    Default namespace declarations do not affect the argument of the +negation pseudo-class unless the argument is a universal selector or a +type selector.

    + +
    +

    Examples:

    +

    Assuming that the default namespace is bound to + "http://example.com/", the following selector represents all + elements that are not in that namespace:

    +
    *|*:not(*)
    +

    The following CSS selector matches any element being hovered, + regardless of its namespace. In particular, it is not limited to + only matching elements in the default namespace that are not being + hovered, and elements not in the default namespace don't match the + rule when they are being hovered.

    +
    *|*:not(:hover)
    +
    + +

    Note: the :not() pseudo allows +useless selectors to be written. For instance :not(*|*), +which represents no element at all, or foo:not(bar), +which is equivalent to foo but with a higher +specificity.

    + +

    7. Pseudo-elements

    + +

    Pseudo-elements create abstractions about the document tree beyond +those specified by the document language. For instance, document +languages do not offer mechanisms to access the first letter or first +line of an element's content. Pseudo-elements allow designers to refer +to this otherwise inaccessible information. Pseudo-elements may also +provide designers a way to refer to content that does not exist in the +source document (e.g., the ::before and +::after pseudo-elements give access to generated +content).

    + +

    A pseudo-element is made of two colons (::) followed +by the name of the pseudo-element.

    + +

    This :: notation is introduced by the current document +in order to establish a discrimination between pseudo-classes and +pseudo-elements. For compatibility with existing style sheets, user +agents must also accept the previous one-colon notation for +pseudo-elements introduced in CSS levels 1 and 2 (namely, +:first-line, :first-letter, +:before and :after). This compatibility is +not allowed for the new pseudo-elements introduced in CSS level 3.

    + +

    Only one pseudo-element may appear per selector, and if present it +must appear after the sequence of simple selectors that represents the +subjects of the selector. A +future version of this specification may allow multiple +pesudo-elements per selector.

    + +

    7.1. The ::first-line pseudo-element

    + +

    The ::first-line pseudo-element describes the contents +of the first formatted line of an element. + +

    +

    CSS example:

    +
    p::first-line { text-transform: uppercase }
    +

    The above rule means "change the letters of the first line of every +paragraph to uppercase".

    +
    + +

    The selector p::first-line does not match any real +HTML element. It does match a pseudo-element that conforming user +agents will insert at the beginning of every paragraph.

    + +

    Note that the length of the first line depends on a number of +factors, including the width of the page, the font size, etc. Thus, +an ordinary HTML paragraph such as:

    + +
    +<P>This is a somewhat long HTML 
    +paragraph that will be broken into several 
    +lines. The first line will be identified
    +by a fictional tag sequence. The other lines 
    +will be treated as ordinary lines in the 
    +paragraph.</P>
    +
    + +

    the lines of which happen to be broken as follows: + +

    +THIS IS A SOMEWHAT LONG HTML PARAGRAPH THAT
    +will be broken into several lines. The first
    +line will be identified by a fictional tag 
    +sequence. The other lines will be treated as 
    +ordinary lines in the paragraph.
    +
    + +

    This paragraph might be "rewritten" by user agents to include the +fictional tag sequence for ::first-line. This +fictional tag sequence helps to show how properties are inherited.

    + +
    +<P><P::first-line> This is a somewhat long HTML 
    +paragraph that </P::first-line> will be broken into several
    +lines. The first line will be identified 
    +by a fictional tag sequence. The other lines 
    +will be treated as ordinary lines in the 
    +paragraph.</P>
    +
    + +

    If a pseudo-element breaks up a real element, the desired effect +can often be described by a fictional tag sequence that closes and +then re-opens the element. Thus, if we mark up the previous paragraph +with a span element:

    + +
    +<P><SPAN class="test"> This is a somewhat long HTML
    +paragraph that will be broken into several
    +lines.</SPAN> The first line will be identified
    +by a fictional tag sequence. The other lines 
    +will be treated as ordinary lines in the 
    +paragraph.</P>
    +
    + +

    the user agent could simulate start and end tags for +span when inserting the fictional tag sequence for +::first-line. + +

    +<P><P::first-line><SPAN class="test"> This is a
    +somewhat long HTML
    +paragraph that will </SPAN></P::first-line><SPAN class="test"> be
    +broken into several
    +lines.</SPAN> The first line will be identified
    +by a fictional tag sequence. The other lines
    +will be treated as ordinary lines in the 
    +paragraph.</P>
    +
    + +

    In CSS, the ::first-line pseudo-element can only be +attached to a block-level element, an inline-block, a table-caption, +or a table-cell.

    + +

    The "first formatted line" of an +element may occur inside a +block-level descendant in the same flow (i.e., a block-level +descendant that is not positioned and not a float). E.g., the first +line of the div in <DIV><P>This +line...</P></DIV> is the first line of the p (assuming +that both p and div are block-level). + +

    The first line of a table-cell or inline-block cannot be the first +formatted line of an ancestor element. Thus, in <DIV><P +STYLE="display: inline-block">Hello<BR>Goodbye</P> +etcetera</DIV> the first formatted line of the +div is not the line "Hello". + +

    Note that the first line of the p in this +fragment: <p><br>First... doesn't contain any +letters (assuming the default style for br in HTML +4). The word "First" is not on the first formatted line. + +

    A UA should act as if the fictional start tags of the +::first-line pseudo-elements were nested just inside the +innermost enclosing block-level element. (Since CSS1 and CSS2 were +silent on this case, authors should not rely on this behavior.) Here +is an example. The fictional tag sequence for

    + +
    +<DIV>
    +  <P>First paragraph</P>
    +  <P>Second paragraph</P>
    +</DIV>
    +
    + +

    is

    + +
    +<DIV>
    +  <P><DIV::first-line><P::first-line>First paragraph</P::first-line></DIV::first-line></P>
    +  <P><P::first-line>Second paragraph</P::first-line></P>
    +</DIV>
    +
    + +

    The ::first-line pseudo-element is similar to an +inline-level element, but with certain restrictions. In CSS, the +following properties apply to a ::first-line +pseudo-element: font properties, color property, background +properties, 'word-spacing', 'letter-spacing', 'text-decoration', +'vertical-align', 'text-transform', 'line-height'. UAs may apply other +properties as well.

    + + +

    7.2. The ::first-letter pseudo-element

    + +

    The ::first-letter pseudo-element represents the first +letter of the first line of a block, if it is not preceded by any +other content (such as images or inline tables) on its line. The +::first-letter pseudo-element may be used for "initial caps" and "drop +caps", which are common typographical effects. This type of initial +letter is similar to an inline-level element if its 'float' property +is 'none'; otherwise, it is similar to a floated element.

    + +

    In CSS, these are the properties that apply to ::first-letter +pseudo-elements: font properties, 'text-decoration', 'text-transform', +'letter-spacing', 'word-spacing' (when appropriate), 'line-height', +'float', 'vertical-align' (only if 'float' is 'none'), margin +properties, padding properties, border properties, color property, +background properties. UAs may apply other properties as well. To +allow UAs to render a typographically correct drop cap or initial cap, +the UA may choose a line-height, width and height based on the shape +of the letter, unlike for normal elements.

    + +
    +

    Example:

    +

    This example shows a possible rendering of an initial cap. Note +that the 'line-height' that is inherited by the ::first-letter +pseudo-element is 1.1, but the UA in this example has computed the +height of the first letter differently, so that it doesn't cause any +unnecessary space between the first two lines. Also note that the +fictional start tag of the first letter is inside the span, and thus +the font weight of the first letter is normal, not bold as the span: +

    +p { line-height: 1.1 }
    +p::first-letter { font-size: 3em; font-weight: normal }
    +span { font-weight: bold }
    +...
    +<p><span>Het hemelsche</span> gerecht heeft zich ten lange lesten<br>
    +Erbarremt over my en mijn benaeuwde vesten<br>
    +En arme burgery, en op mijn volcx gebed<br>
    +En dagelix geschrey de bange stad ontzet.
    +
    +
    +

    Image illustrating the ::first-letter pseudo-element +

    +
    + +
    +

    The following CSS will make a drop cap initial letter span about two lines:

    + +
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    +<HTML>
    + <HEAD>
    +  <TITLE>Drop cap initial letter</TITLE>
    +  <STYLE type="text/css">
    +   P               { font-size: 12pt; line-height: 1.2 }
    +   P::first-letter { font-size: 200%; font-weight: bold; float: left }
    +   SPAN            { text-transform: uppercase }
    +  </STYLE>
    + </HEAD>
    + <BODY>
    +  <P><SPAN>The first</SPAN> few words of an article
    +    in The Economist.</P>
    + </BODY>
    +</HTML>
    +
    + +

    This example might be formatted as follows:

    + +
    +

    Image illustrating the combined effect of the ::first-letter and ::first-line pseudo-elements

    +
    + +

    The fictional tag sequence is:

    + +
    +<P>
    +<SPAN>
    +<P::first-letter>
    +T
    +</P::first-letter>he first
    +</SPAN> 
    +few words of an article in the Economist.
    +</P>
    +
    + +

    Note that the ::first-letter pseudo-element tags abut +the content (i.e., the initial character), while the ::first-line +pseudo-element start tag is inserted right after the start tag of the +block element.

    + +

    In order to achieve traditional drop caps formatting, user agents +may approximate font sizes, for example to align baselines. Also, the +glyph outline may be taken into account when formatting.

    + +

    Punctuation (i.e, characters defined in Unicode in the "open" (Ps), +"close" (Pe), "initial" (Pi). "final" (Pf) and "other" (Po) +punctuation classes), that precedes or follows the first letter should +be included. [UNICODE]

    + +
    +

    Quotes that precede the
+first letter should be included.

    +
    + +

    The ::first-letter also applies if the first letter is +in fact a digit, e.g., the "6" in "67 million dollars is a lot of +money."

    + +

    In CSS, the ::first-letter pseudo-element applies to +block, list-item, table-cell, table-caption, and inline-block +elements. A future version of this specification +may allow this pesudo-element to apply to more element +types.

    + +

    The ::first-letter pseudo-element can be used with all +such elements that contain text, or that have a descendant in the same +flow that contains text. A UA should act as if the fictional start tag +of the ::first-letter pseudo-element is just before the first text of +the element, even if that first text is in a descendant.

    + +
    +

    Example:

    +

    The fictional tag sequence for this HTMLfragment: +

    <div>
    +<p>The first text.
    +

    is: +

    <div>
    +<p><div::first-letter><p::first-letter>T</...></...>he first text.
    +
    + +

    The first letter of a table-cell or inline-block cannot be the +first letter of an ancestor element. Thus, in <DIV><P +STYLE="display: inline-block">Hello<BR>Goodbye</P> +etcetera</DIV> the first letter of the div is not the +letter "H". In fact, the div doesn't have a first letter. + +

    The first letter must occur on the first formatted line. For example, in +this fragment: <p><br>First... the first line +doesn't contain any letters and ::first-letter doesn't +match anything (assuming the default style for br in HTML +4). In particular, it does not match the "F" of "First." + +

    In CSS, if an element is a list item ('display: list-item'), the +::first-letter applies to the first letter in the +principal box after the marker. UAs may ignore +::first-letter on list items with 'list-style-position: +inside'. If an element has ::before or +::after content, the ::first-letter applies +to the first letter of the element including that content. + +

    +

    Example:

    +

    After the rule 'p::before {content: "Note: "}', the selector +'p::first-letter' matches the "N" of "Note".

    +
    + +

    Some languages may have specific rules about how to treat certain +letter combinations. In Dutch, for example, if the letter combination +"ij" appears at the beginning of a word, both letters should be +considered within the ::first-letter pseudo-element. + +

    If the letters that would form the ::first-letter are not in the +same element, such as "'T" in <p>'<em>T..., the UA +may create a ::first-letter pseudo-element from one of the elements, +both elements, or simply not create a pseudo-element.

    + +

    Similarly, if the first letter(s) of the block are not at the start +of the line (for example due to bidirectional reordering), then the UA +need not create the pseudo-element(s). + +

    +

    Example:

    +

    The following example illustrates +how overlapping pseudo-elements may interact. The first letter of +each P element will be green with a font size of '24pt'. The rest of +the first formatted line will be 'blue' while the rest of the +paragraph will be 'red'.

    + +
    p { color: red; font-size: 12pt }
    +p::first-letter { color: green; font-size: 200% }
    +p::first-line { color: blue }
    +
    +<P>Some text that ends up on two lines</P>
    + +

    Assuming that a line break will occur before the word "ends", the +fictional tag +sequence for this fragment might be:

    + +
    <P>
    +<P::first-line>
    +<P::first-letter> 
    +S 
    +</P::first-letter>ome text that 
    +</P::first-line> 
    +ends up on two lines 
    +</P>
    + +

    Note that the ::first-letter element is inside the ::first-line +element. Properties set on ::first-line are inherited by +::first-letter, but are overridden if the same property is set on +::first-letter.

    +
    + + +

    7.3. The ::selection pseudo-element

    + +

    The ::selection pseudo-element applies to the portion +of a document that has been highlighted by the user. This also +applies, for example, to selected text within an editable text +field. This pseudo-element should not be confused with the :checked pseudo-class (which used to be +named :selected) + +

    Although the ::selection pseudo-element is dynamic in +nature, and is altered by user action, it is reasonable to expect that +when a UA re-renders to a static medium (such as a printed page, see +[CSS21]) which was originally rendered to a +dynamic medium (like screen), the UA may wish to transfer the current +::selection state to that other medium, and have all the +appropriate formatting and rendering take effect as well. This is not +required — UAs may omit the ::selection +pseudo-element for static media. + +

    These are the CSS properties that apply to ::selection +pseudo-elements: color, background, cursor (optional), outline +(optional). The computed value of the 'background-image' property on +::selection may be ignored. + + +

    7.4. The ::before and ::after pseudo-elements

    + +

    The ::before and ::after pseudo-elements +can be used to describe generated content before or after an element's +content. They are explained in CSS 2.1 [CSS21].

    + +

    When the ::first-letter and ::first-line +pseudo-elements are combined with ::before and +::after, they apply to the first letter or line of the +element including the inserted text.

    + +

    8. Combinators

    + +

    8.1. Descendant combinator

    + +

    At times, authors may want selectors to describe an element that is +the descendant of another element in the document tree (e.g., "an +EM element that is contained within an H1 +element"). Descendant combinators express such a relationship. A +descendant combinator is white space that +separates two sequences of simple selectors. A selector of the form +"A B" represents an element B that is an +arbitrary descendant of some ancestor element A. + +

    +

    Examples:

    +

    For example, consider the following selector:

    +
    h1 em
    +

    It represents an em element being the descendant of + an h1 element. It is a correct and valid, but partial, + description of the following fragment:

    +
    <h1>This <span class="myclass">headline
    +is <em>very</em> important</span></h1>
    +

    The following selector:

    +
    div * p
    +

    represents a p element that is a grandchild or later + descendant of a div element. Note the whitespace on + either side of the "*" is not part of the universal selector; the + whitespace is a combinator indicating that the DIV must be the + ancestor of some element, and that that element must be an ancestor + of the P.

    +

    The following selector, which combines descendant combinators and + attribute selectors, represents an + element that (1) has the href attribute set and (2) is + inside a p that is itself inside a div:

    +
    div p *[href]
    +
    + +

    8.2. Child combinators

    + +

    A child combinator describes a childhood relationship +between two elements. A child combinator is made of the +"greater-than sign" (>) character and +separates two sequences of simple selectors. + + +

    +

    Examples:

    +

    The following selector represents a p element that is + child of body:

    +
    body > p
    +

    The following example combines descendant combinators and child + combinators.

    +
    div ol>li p
    +

    It represents a p element that is a descendant of an + li element; the li element must be the + child of an ol element; the ol element must + be a descendant of a div. Notice that the optional white + space around the ">" combinator has been left out.

    +
    + +

    For information on selecting the first child of an element, please +see the section on the :first-child pseudo-class +above.

    + +

    8.3. Sibling combinators

    + +

    There are two different sibling combinators: the adjacent sibling +combinator and the general sibling combinator. In both cases, +non-element nodes (e.g. text between elements) are ignored when +considering adjacency of elements.

    + +

    8.3.1. Adjacent sibling combinator

    + +

    The adjacent sibling combinator is made of the "plus +sign" (U+002B, +) character that separates two +sequences of simple selectors. The elements represented by the two +sequences share the same parent in the document tree and the element +represented by the first sequence immediately precedes the element +represented by the second one.

    + +
    +

    Examples:

    +

    The following selector represents a p element + immediately following a math element:

    +
    math + p
    +

    The following selector is conceptually similar to the one in the + previous example, except that it adds an attribute selector — it + adds a constraint to the h1 element, that it must have + class="opener":

    +
    h1.opener + h2
    +
    + + +

    8.3.2. General sibling combinator

    + +

    The general sibling combinator is made of the "tilde" +(U+007E, ~) character that separates two sequences of +simple selectors. The elements represented by the two sequences share +the same parent in the document tree and the element represented by +the first sequence precedes (not necessarily immediately) the element +represented by the second one.

    + +
    +

    Example:

    +
    h1 ~ pre
    +

    represents a pre element following an h1. It + is a correct and valid, but partial, description of:

    +
    <h1>Definition of the function a</h1>
    +<p>Function a(x) has to be applied to all figures in the table.</p>
    +<pre>function a(x) = 12x/13.5</pre>
    +
    + +

    9. Calculating a selector's specificity

    + +

    A selector's specificity is calculated as follows:

    + +
      +
    • count the number of ID selectors in the selector (= a)
    • +
    • count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= b)
    • +
    • count the number of element names in the selector (= c)
    • +
    • ignore pseudo-elements
    • +
    + +

    Selectors inside the negation pseudo-class +are counted like any other, but the negation itself does not count as +a pseudo-class.

    + +

    Concatenating the three numbers a-b-c (in a number system with a +large base) gives the specificity.

    + +
    +

    Examples:

    +
    *               /* a=0 b=0 c=0 -> specificity =   0 */
    +LI              /* a=0 b=0 c=1 -> specificity =   1 */
    +UL LI           /* a=0 b=0 c=2 -> specificity =   2 */
    +UL OL+LI        /* a=0 b=0 c=3 -> specificity =   3 */
    +H1 + *[REL=up]  /* a=0 b=1 c=1 -> specificity =  11 */
    +UL OL LI.red    /* a=0 b=1 c=3 -> specificity =  13 */
    +LI.red.level    /* a=0 b=2 c=1 -> specificity =  21 */
    +#x34y           /* a=1 b=0 c=0 -> specificity = 100 */
    +#s12:not(FOO)   /* a=1 b=0 c=1 -> specificity = 101 */
    +
    +
    + +

    Note: the specificity of the styles +specified in an HTML style attribute is described in CSS +2.1. [CSS21].

    + +

    10. The grammar of Selectors

    + +

    10.1. Grammar

    + +

    The grammar below defines the syntax of Selectors. It is globally +LL(1) and can be locally LL(2) (but note that most UA's should not use +it directly, since it doesn't express the parsing conventions). The +format of the productions is optimized for human consumption and some +shorthand notations beyond Yacc (see [YACC]) +are used:

    + +
      +
    • *: 0 or more +
    • +: 1 or more +
    • ?: 0 or 1 +
    • |: separates alternatives +
    • [ ]: grouping
    • +
    + +

    The productions are:

    + +
    selectors_group
    +  : selector [ COMMA S* selector ]*
    +  ;
    +
    +selector
    +  : simple_selector_sequence [ combinator simple_selector_sequence ]*
    +  ;
    +
    +combinator
    +  /* combinators can be surrounded by white space */
    +  : PLUS S* | GREATER S* | TILDE S* | S+
    +  ;
    +
    +simple_selector_sequence
    +  : [ type_selector | universal ]
    +    [ HASH | class | attrib | pseudo | negation ]*
    +  | [ HASH | class | attrib | pseudo | negation ]+
    +  ;
    +
    +type_selector
    +  : [ namespace_prefix ]? element_name
    +  ;
    +
    +namespace_prefix
    +  : [ IDENT | '*' ]? '|'
    +  ;
    +
    +element_name
    +  : IDENT
    +  ;
    +
    +universal
    +  : [ namespace_prefix ]? '*'
    +  ;
    +
    +class
    +  : '.' IDENT
    +  ;
    +
    +attrib
    +  : '[' S* [ namespace_prefix ]? IDENT S*
    +        [ [ PREFIXMATCH |
    +            SUFFIXMATCH |
    +            SUBSTRINGMATCH |
    +            '=' |
    +            INCLUDES |
    +            DASHMATCH ] S* [ IDENT | STRING ] S*
    +        ]? ']'
    +  ;
    +
    +pseudo
    +  /* '::' starts a pseudo-element, ':' a pseudo-class */
    +  /* Exceptions: :first-line, :first-letter, :before and :after. */
    +  /* Note that pseudo-elements are restricted to one per selector and */
    +  /* occur only in the last simple_selector_sequence. */
    +  : ':' ':'? [ IDENT | functional_pseudo ]
    +  ;
    +
    +functional_pseudo
    +  : FUNCTION S* expression ')'
    +  ;
    +
    +expression
    +  /* In CSS3, the expressions are identifiers, strings, */
    +  /* or of the form "an+b" */
    +  : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
    +  ;
    +
    +negation
    +  : NOT S* negation_arg S* ')'
    +  ;
    +
    +negation_arg
    +  : type_selector | universal | HASH | class | attrib | pseudo
    +  ;
    + + +

    10.2. Lexical scanner

    + +

    The following is the tokenizer, written in Flex (see +[FLEX]) notation. The tokenizer is +case-insensitive.

    + +

    The two occurrences of "\377" represent the highest character +number that current versions of Flex can deal with (decimal 255). They +should be read as "\4177777" (decimal 1114111), which is the highest +possible code point in Unicode/ISO-10646. [UNICODE]

    + +
    %option case-insensitive
    +
    +ident     [-]?{nmstart}{nmchar}*
    +name      {nmchar}+
    +nmstart   [_a-z]|{nonascii}|{escape}
    +nonascii  [^\0-\177]
    +unicode   \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
    +escape    {unicode}|\\[^\n\r\f0-9a-f]
    +nmchar    [_a-z0-9-]|{nonascii}|{escape}
    +num       [0-9]+|[0-9]*\.[0-9]+
    +string    {string1}|{string2}
    +string1   \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*\"
    +string2   \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*\'
    +invalid   {invalid1}|{invalid2}
    +invalid1  \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*
    +invalid2  \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*
    +nl        \n|\r\n|\r|\f
    +w         [ \t\r\n\f]*
    +
    +%%
    +
    +[ \t\r\n\f]+     return S;
    +
    +"~="             return INCLUDES;
    +"|="             return DASHMATCH;
    +"^="             return PREFIXMATCH;
    +"$="             return SUFFIXMATCH;
    +"*="             return SUBSTRINGMATCH;
    +{ident}          return IDENT;
    +{string}         return STRING;
    +{ident}"("       return FUNCTION;
    +{num}            return NUMBER;
    +"#"{name}        return HASH;
    +{w}"+"           return PLUS;
    +{w}">"           return GREATER;
    +{w}","           return COMMA;
    +{w}"~"           return TILDE;
    +":not("          return NOT;
    +@{ident}         return ATKEYWORD;
    +{invalid}        return INVALID;
    +{num}%           return PERCENTAGE;
    +{num}{ident}     return DIMENSION;
    +"<!--"           return CDO;
    +"-->"            return CDC;
    +
    +"url("{w}{string}{w}")"                           return URI;
    +"url("{w}([!#$%&*-~]|{nonascii}|{escape})*{w}")"  return URI;
    +U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?                return UNICODE_RANGE;
    +
    +\/\*[^*]*\*+([^/*][^*]*\*+)*\/                    /* ignore comments */
    +
    +.                return *yytext;
    + + + +

    11. Namespaces and down-level clients

    + +

    An important issue is the interaction of CSS selectors with XML +documents in web clients that were produced prior to this +document. Unfortunately, due to the fact that namespaces must be +matched based on the URI which identifies the namespace, not the +namespace prefix, some mechanism is required to identify namespaces in +CSS by their URI as well. Without such a mechanism, it is impossible +to construct a CSS style sheet which will properly match selectors in +all cases against a random set of XML documents. However, given +complete knowledge of the XML document to which a style sheet is to be +applied, and a limited use of namespaces within the XML document, it +is possible to construct a style sheet in which selectors would match +elements and attributes correctly.

    + +

    It should be noted that a down-level CSS client will (if it +properly conforms to CSS forward compatible parsing rules) ignore all +@namespace at-rules, as well as all style rules that make +use of namespace qualified element type or attribute selectors. The +syntax of delimiting namespace prefixes in CSS was deliberately chosen +so that down-level CSS clients would ignore the style rules rather +than possibly match them incorrectly.

    + +

    The use of default namespaces in CSS makes it possible to write +element type selectors that will function in both namespace aware CSS +clients as well as down-level clients. It should be noted that +down-level clients may incorrectly match selectors against XML +elements in other namespaces.

    + +

    The following are scenarios and examples in which it is possible to +construct style sheets which would function properly in web clients +that do not implement this proposal.

    + +
      +
    1. + +

      The XML document does not use namespaces.

      + +
        + +
      • In this case, it is obviously not necessary to declare or use + namespaces in the style sheet. Standard CSS element type and + attribute selectors will function adequately in a down-level + client.
      • + +
      • In a CSS namespace aware client, the default behavior of + element selectors matching without regard to namespace will + function properly against all elements, since no namespaces are + present. However, the use of specific element type selectors that + match only elements that have no namespace ("|name") + will guarantee that selectors will match only XML elements that do + not have a declared namespace.
      • + +
      + +
    2. + +
    3. + +

      The XML document defines a single, default namespace used + throughout the document. No namespace prefixes are used in element + names.

      + +
        + +
      • In this case, a down-level client will function as if + namespaces were not used in the XML document at all. Standard CSS + element type and attribute selectors will match against all + elements.
      • + +
      + +
    4. + +
    5. + +

      The XML document does not use a default namespace, all + namespace prefixes used are known to the style sheet author, and + there is a direct mapping between namespace prefixes and namespace + URIs. (A given prefix may only be mapped to one namespace URI + throughout the XML document; there may be multiple prefixes mapped + to the same URI).

      + +
        + +
      • In this case, the down-level client will view and match + element type and attribute selectors based on their fully + qualified name, not the local part as outlined in the Type selectors and Namespaces section. CSS + selectors may be declared using an escaped colon "\:" + to describe the fully qualified names, e.g. + "html\:h1" will match + <html:h1>. Selectors using the qualified name + will only match XML elements that use the same prefix. Other + namespace prefixes used in the XML that are mapped to the same URI + will not match as expected unless additional CSS style rules are + declared for them.
      • + +
      • Note that selectors declared in this fashion will + only match in down-level clients. A CSS namespace aware + client will match element type and attribute selectors based on + the name's local part. Selectors declared with the fully + qualified name will not match (unless there is no namespace prefix + in the fully qualified name).
      • + +
      + +
    6. + +
    + +

    In other scenarios: when the namespace prefixes used in the XML are +not known in advance by the style sheet author; or a combination of +elements with no namespace are used in conjunction with elements using +a default namespace; or the same namespace prefix is mapped to +different namespace URIs within the same document, or in +different documents; it is impossible to construct a CSS style sheet +that will function properly against all elements in those documents, +unless, the style sheet is written using a namespace URI syntax (as +outlined in this document or similar) and the document is processed by +a CSS and XML namespace aware client.

    + +

    12. Profiles

    + +

    Each specification using Selectors must define the subset of W3C +Selectors it allows and excludes, and describe the local meaning of +all the components of that subset.

    + +

    Non normative examples: + +

    + + + + + + + + + + + + + + + +
    Selectors profile
    SpecificationCSS level 1
    Acceptstype selectors
    class selectors
    ID selectors
    :link, + :visited and :active pseudo-classes
    descendant combinator +
    ::first-line and ::first-letter pseudo-elements
    Excludes + +

    universal selector
    attribute selectors
    :hover and :focus + pseudo-classes
    :target pseudo-class
    :lang() pseudo-class
    all UI + element states pseudo-classes
    all structural + pseudo-classes
    negation pseudo-class
    all + UI element fragments pseudo-elements
    ::before and ::after + pseudo-elements
    child combinators
    sibling combinators + +

    namespaces

    Extra constraintsonly one class selector allowed per sequence of simple + selectors


    + + + + + + + + + + + + + + + +
    Selectors profile
    SpecificationCSS level 2
    Acceptstype selectors
    universal selector
    attribute presence and + values selectors
    class selectors
    ID selectors
    :link, :visited, + :active, :hover, :focus, :lang() and :first-child pseudo-classes +
    descendant combinator
    child combinator
    adjacent sibling + combinator
    ::first-line and ::first-letter pseudo-elements
    ::before + and ::after pseudo-elements
    Excludes + +

    content selectors
    substring matching attribute + selectors
    :target pseudo-classes
    all UI element + states pseudo-classes
    all structural pseudo-classes other + than :first-child
    negation pseudo-class
    all UI element + fragments pseudo-elements
    general sibling combinators + +

    namespaces

    Extra constraintsmore than one class selector per sequence of simple selectors (CSS1 + constraint) allowed
    + +

    In CSS, selectors express pattern matching rules that determine which style +rules apply to elements in the document tree. + +

    The following selector (CSS level 2) will match all anchors a +with attribute name set inside a section 1 header h1: +

    h1 a[name]
    + +

    All CSS declarations attached to such a selector are applied to elements +matching it.

    + +
    + + + + + + + + + + + + + + + + +
    Selectors profile
    SpecificationSTTS 3
    Accepts + +

    type selectors
    universal selectors
    attribute selectors
    class + selectors
    ID selectors
    all structural pseudo-classes
    + all combinators + +

    namespaces

    Excludesnon-accepted pseudo-classes
    pseudo-elements
    Extra constraintssome selectors and combinators are not allowed in fragment + descriptions on the right side of STTS declarations.
    + +

    Selectors can be used in STTS 3 in two different + manners: +

      +
    1. a selection mechanism equivalent to CSS selection mechanism: declarations + attached to a given selector are applied to elements matching that selector, +
    2. fragment descriptions that appear on the right side of declarations. +
    + +

    13. Conformance and requirements

    + +

    This section defines conformance with the present specification only. + +

    The inability of a user agent to implement part of this specification due to +the limitations of a particular device (e.g., non interactive user agents will +probably not implement dynamic pseudo-classes because they make no sense without +interactivity) does not imply non-conformance. + +

    All specifications reusing Selectors must contain a Profile listing the +subset of Selectors it accepts or excludes, and describing the constraints +it adds to the current specification. + +

    Invalidity is caused by a parsing error, e.g. an unrecognized token or a token +which is not allowed at the current parsing point. + +

    User agents must observe the rules for handling parsing errors: +

      +
    • a simple selector containing an undeclared namespace prefix is invalid
    • +
    • a selector containing an invalid simple selector, an invalid combinator + or an invalid token is invalid.
    • +
    • a group of selectors containing an invalid selector is invalid.
    • +
    + +

    Specifications reusing Selectors must define how to handle parsing +errors. (In the case of CSS, the entire rule in which the selector is +used is dropped.)

    + + + +

    14. Tests

    + +

    This specification has a test +suite allowing user agents to verify their basic conformance to +the specification. This test suite does not pretend to be exhaustive +and does not cover all possible combined cases of Selectors.

    + +

    15. Acknowledgements

    + +

    The CSS working group would like to thank everyone who has sent +comments on this specification over the years.

    + +

    The working group would like to extend special thanks to Donna +McManus, Justin Baker, Joel Sklar, and Molly Ives Brower who perfermed +the final editorial review.

    + +

    16. References

    + +
    + +
    [CSS1] +
    Bert Bos, Håkon Wium Lie; "Cascading Style Sheets, level 1", W3C Recommendation, 17 Dec 1996, revised 11 Jan 1999 +
    (http://www.w3.org/TR/REC-CSS1) + +
    [CSS21] +
    Bert Bos, Tantek Çelik, Ian Hickson, Håkon Wium Lie, editors; "Cascading Style Sheets, level 2 revision 1", W3C Working Draft, 13 June 2005 +
    (http://www.w3.org/TR/CSS21) + +
    [CWWW] +
    Martin J. Dürst, François Yergeau, Misha Wolf, Asmus Freytag, Tex Texin, editors; "Character Model for the World Wide Web", W3C Recommendation, 15 February 2005 +
    (http://www.w3.org/TR/charmod/) + +
    [FLEX] +
    "Flex: The Lexical Scanner Generator", Version 2.3.7, ISBN 1882114213 + +
    [HTML4] +
    Dave Ragget, Arnaud Le Hors, Ian Jacobs, editors; "HTML 4.01 Specification", W3C Recommendation, 24 December 1999 +
    (http://www.w3.org/TR/html4/) + +
    [MATH] +
    Patrick Ion, Robert Miner, editors; "Mathematical Markup Language (MathML) 1.01", W3C Recommendation, revision of 7 July 1999 +
    (http://www.w3.org/TR/REC-MathML/) + +
    [RFC3066] +
    H. Alvestrand; "Tags for the Identification of Languages", Request for Comments 3066, January 2001 +
    (http://www.ietf.org/rfc/rfc3066.txt) + +
    [STTS] +
    Daniel Glazman; "Simple Tree Transformation Sheets 3", Electricité de France, submission to the W3C, 11 November 1998 +
    (http://www.w3.org/TR/NOTE-STTS3) + +
    [SVG] +
    Jon Ferraiolo, 藤沢 淳, Dean Jackson, editors; "Scalable Vector Graphics (SVG) 1.1 Specification", W3C Recommendation, 14 January 2003 +
    (http://www.w3.org/TR/SVG/) + +
    [UNICODE]
    +
    The Unicode Standard, Version 4.1, The Unicode Consortium. Boston, MA, Addison-Wesley, March 2005. ISBN 0-321-18578-1, as amended by Unicode 4.0.1 and Unicode 4.1.0. +
    (http://www.unicode.org/versions/)
    + +
    [XML10] +
    Tim Bray, Jean Paoli, C. M. Sperberg-McQueen, Eve Maler, François Yergeau, editors; "Extensible Markup Language (XML) 1.0 (Third Edition)", W3C Recommendation, 4 February 2004 +
    (http://www.w3.org/TR/REC-xml/) + +
    [XMLNAMES] +
    Tim Bray, Dave Hollander, Andrew Layman, editors; "Namespaces in XML", W3C Recommendation, 14 January 1999 +
    (http://www.w3.org/TR/REC-xml-names/) + +
    [YACC] +
    S. C. Johnson; "YACC — Yet another compiler compiler", Technical Report, Murray Hill, 1975 + +
    + + diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/std_testcase.php b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/std_testcase.php new file mode 100644 index 0000000000..57d9f10aa8 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/std_testcase.php @@ -0,0 +1,243 @@ +load($str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = null; +$dom->load($str); +assert($dom->save()==$str); + +// ----------------------------------------------------------------------------- +// text test +$str = << + +HTML; +$dom->load($str); +assert(count($dom->find('unknown'))==1); +assert(count($dom->find('text'))==1); + +// ----------------------------------------------------------------------------- +// string quote test +$str = << + okok
    + +
    +
    + +
    +
    +
    +HTML; +$dom->load($str); +$es = $dom->find('input'); +assert(count($es)==4); +assert($es[0]->onclick=='goto("url0")'); +assert($es[1]->onclick=="goto('url1'+'\'')"); +assert($es[2]->onclick=="goto('url2')"); +assert($es[3]->onclick=='goto("url4"+"\"")'); + +// ----------------------------------------------------------------------------- +// clone test +$str = << + okok
    + +
    +
    + +
    +
    +
    +HTML; +$dom->load($str); +$es = $dom->find('input'); +assert(count($es)==4); +assert($es[0]->onclick=='goto("url0")'); +assert($es[1]->onclick=="goto('url1'+'\'')"); +assert($es[2]->onclick=="goto('url2')"); +assert($es[3]->onclick=='goto("url4"+"\"")'); + +unset($es); +$dom2 = clone($dom); +$es = $dom2->find('input'); +assert(count($es)==4); +assert($es[0]->onclick=='goto("url0")'); +assert($es[1]->onclick=="goto('url1'+'\'')"); +assert($es[2]->onclick=="goto('url2')"); +assert($es[3]->onclick=='goto("url4"+"\"")'); + +// ----------------------------------------------- +$str = <<
    +HTML; +$dom->load($str); +assert($dom==$str); +assert($dom->save()==$str); + +// ----------------------------------------------------------------------------- +// monkey test +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<<<>ab +HTML; +$dom->load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +$str = << +HTML; +$dom->load($str); +assert($dom==$str); +assert($dom->save()==$str); +// ----------------------------------------------- +// $str = <<load($str); +// echo $dom; +// assert($dom==$str); +// assert($dom->save()==$str); +// ----------------------------------------------- +$str = <<load($str); +assert($dom==$str); +assert($dom->save()==$str); + +// ----------------------------------------------------------------------------- +// rnadom string test +function str_random($length) +{ + $str = ""; + srand((double)microtime()*1000000); + $char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + $char_list .= "abcdefghijklmnopqrstuvwxyz"; + $char_list .= "1234567890"; + $char_list .= "<>!?[]%^&*()"; + for($i=0; $i<$length; ++$i) + $str .= substr($char_list,(rand()%(strlen($char_list))), 1); + return $str; +} + +for($i=0; $i<60; ++$i) { + $str = str_random($i); + //echo $str."\n
    "; + $dom->load($str, false); + //echo $dom->save()."\n
    "; + assert($dom==$str); +} + +// ----------------------------------------------------------------------------- +// lowercase test +$str = << +HTML; +$dom->load($str); +assert(count($dom->find('img'))==1); +assert(count($dom->find('IMG'))==1); +assert(isset($dom->find('img', 0)->class)); +assert(!isset($dom->find('img', 0)->CLASS)); +assert($dom->find('img', 0)->class=='class0'); +assert($dom==$str); +// ----------------------------------------------- +$str = << +HTML; +$dom->load($str); +assert(count($dom->find('img'))==1); +assert(count($dom->find('IMG'))==1); +assert(isset($dom->find('img', 0)->class)); +assert(!isset($dom->find('img', 0)->CLASS)); +assert($dom->find('img', 0)->class=='class0'); +assert($dom==strtolower($str)); +// ----------------------------------------------- +$str = << +HTML; +$dom->load($str, false); +assert(count($dom->find('img'))==0); +assert(count($dom->find('IMG'))==1); +assert(isset($dom->find('IMG', 0)->CLASS)); +assert(!isset($dom->find('IMG', 0)->class)); +assert($dom->find('IMG', 0)->CLASS=='class0'); +assert($dom==$str); + +// ----------------------------------------------------------------------------- +// tear down +$dom->clear(); +unset($dom); +?> \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/strip_testcase.php b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/strip_testcase.php new file mode 100644 index 0000000000..66a45f6b21 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/Src/Sunra/PhpSimple/simplehtmldom_1_5/testcase/strip_testcase.php @@ -0,0 +1,137 @@ + + +
    +HTML; +$dom->load($str); +assert(count($dom->find('input'))==0); + +// ----------------------------------------------------------------------------- +// test +$str = << + + + + +HTML; +$dom->load($str); +assert(count($dom->find('code'))==1); +assert(count($dom->find('input'))==0); + +// ----------------------------------------------------------------------------- +//
     &  test
    +$str = <<
    +    
    +
    +HTML; +$dom->load($str); +assert(count($dom->find('pre'))==1); +assert(count($dom->find('input'))==0); + +// ----------------------------------------------------------------------------- +// + +HTML; +$dom->load($str); +assert(count($dom->find('style'))==1); +assert(count($dom->find('script'))==3); + +// ----------------------------------------------------------------------------- +// php short tag test +$str = <<hello + +HTML; +$dom->load($str); +assert($dom->find('a', 0)->href===""); +assert($dom->find('input', 0)->value===""); + +// ----------------------------------------------------------------------------- +// noise stripping test +$str = <<--> + + +HTML; +$dom->load($str); +assert(count($dom->find('img'))==1); +assert($dom==$str); +// ----------------------------------------------- +$str = <<ss + + + + + + + + + + + + + +HTML; +$dom->load($str); +assert(count($dom->find('script'))==8); +assert(count($dom->find('style'))==3); +//echo "\n\n\n\n".$dom->save(); +assert($dom==$str); + +// ----------------------------------------------------------------------------- +// tear down +$dom->clear(); +unset($dom); +?> \ No newline at end of file diff --git a/vendor/sunra/php-simple-html-dom-parser/composer.json b/vendor/sunra/php-simple-html-dom-parser/composer.json new file mode 100644 index 0000000000..6d21ae1c59 --- /dev/null +++ b/vendor/sunra/php-simple-html-dom-parser/composer.json @@ -0,0 +1,24 @@ +{ + "name": "sunra/php-simple-html-dom-parser", + "type": "library", + "description": "Composer adaptation of: A HTML DOM parser written in PHP5+ let you manipulate HTML in a very easy way! Require PHP 5+. Supports invalid HTML. Find tags on an HTML page with selectors just like jQuery. Extract contents from HTML in a single line.", + "keywords": ["html", "dom", "parser"], + "homepage": "https://github.com/sunra/php-simple-html-dom-parser", + "license": "MIT", + "authors": [ + { + "homepage": "http://sourceforge.net/projects/simplehtmldom/" + }, + { + "name": "Sunra", + "email": "sunra@yandex.ru", + "homepage": "https://github.com/sunra" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Sunra\\PhpSimple\\HtmlDomParser": "Src/" } + } +} \ No newline at end of file diff --git a/web/ChamiloLMS/css/base.css b/web/ChamiloLMS/css/base.css index 9e0fef0f19..0b066bcbe1 100644 --- a/web/ChamiloLMS/css/base.css +++ b/web/ChamiloLMS/css/base.css @@ -10,7 +10,6 @@ html { } body { - margin-bottom: 60px; background-color: #383e4b; /* The html and body elements cannot have any padding or margin. */ } @@ -202,6 +201,7 @@ footer .container .row { .actions { margin-top: 10px; margin-bottom: 10px; + width:100%; } .actions form { @@ -6767,10 +6767,10 @@ header .navbar { } footer { - position: absolute; + /*position: absolute; bottom: 0; width: 100%; - height: 60px; + height: 60px;*/ background-color: #f5f5f5; }